深入理解Socket通信:网络中进程通信的唯一标识与TCP/IP协议应用
如何在插座通信网络中的过程之间进行交流

本地过程间通信(IPC)有很多方法,但可以将其汇总到以下4个类别中:
过程如何在网络之间进行通信?要解决的第一个问题是如何唯一地识别一个过程,否则沟通将是不可能的!在本地,可以通过PROCES PID唯一识别一个过程,但这在网络中不起作用。实际上,TCP/IP协议系列帮助我们解决了这个问题。网络层的“ IP地址”可以唯一地标识网络中的主机,而传输层的“协议 +端口”可以唯一地标识主机中的应用程序(过程)。通过这种方式,可以使用Triples(IP地址,协议,端口)来标识网络的过程,并且网络中的进程通信可以使用此标志与其他进程进行交互。
使用TCP/IP协议的应用程序通常使用应用程序编程接口:套接字(插座)和UNIX BSD的UNIX系统
V的TLI(已被消除)用于实现网络过程之间的通信。截至目前,几乎所有应用程序都使用插座,现在在互联网时代,流程通信无处不在,这就是为什么“一切都是套接字”的原因。
什么是插座
正如我们已经知道,网络中的过程通过插座通信,那么什么是插座?插座起源于UNIX,UNIX/Linux的基本理念之一是“所有内容都是文件”,可以在“打开 - >读取 - >读写写入/witr/read-> close”模式中操作。插座是此模式的实现。插座是一个特殊文件。某些套接字功能在其上进行操作(读/写IO,打开和关闭)。
插座通信模型
插座的基本操作
1。插座()函数
int socket(int domain, int type, int protocol);
套接字功能对应于普通文件的打开操作。普通文件打开操作返回文件描述单词,socket()用于创建套接字描述符(套接字描述符),该描述符(套接字描述符)唯一地标识了套接字。此套接字描述单词与文件描述单词相同。它用于后续操作。它被用作参数,并使用它来执行一些读写操作。就像您可以传递不同的参数值以打开不同的文件一样。创建套接字时,您还可以指定不同的参数以创建不同的套接字描述符。套接字函数的三个参数是:
Domain:即协议域,也称为协议家族。常用的协议系列包括af_inet,af_inet6,af_local(或af_unix,unix域套接字),af_route等。协议家族确定套接字的地址类型,并且必须在通信中使用相应的地址。例如,AF_INET确定IPv4地址(32位)和端口号(16位)的组合,并且AF_UNIX确定了绝对路径的使用。名称为地址。 类型:指定套接字类型。常见的套接字类型是sock_stream,sock_dgram,sock_raw,sock_packet,sock_seqpacket等(插座的类型是什么?)。 。 协议:因此,名称意味着指定协议。常用的协议包括IPPROTO_TCP,IPPTOTO_UDP,IPPROTO_SCTP,IPPROTO_TIPC等,它们分别对应于TCP传输协议,UDP传输协议,STCP传输协议和TIPC传输协议。
当我们调用插座创建套接字时,返回的套接字描述Word中存在协议家族(AF_XXX)空间中,但没有特定地址。如果要为其分配地址,则必须调用bind()函数,否则系统将在调用connect()和listing()时自动随机分配端口。
2。bind()函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数的三个参数是:
SockFD:也就是说,套接字描述字,它是通过socket()函数创建的,独特地识别套接字。 bind()函数将名称绑定到此描述性词。
ADDR:const struct sockAddr*指向绑定到sockfd的协议地址的指针。创建地址插座时,此地址结构根据地址协议家族而变化。例如,相应的IPv4是:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
ipv6对应的是:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
Unix域对应的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
Addrlen:相应的地址长度。
通常,当服务器启动时,它将绑定一个知名的地址(例如IP地址 +端口号)以提供服务,并且客户可以通过它连接服务器;客户端无需指定它,系统将自动分配端口号。结合其自己的IP地址。这就是为什么服务器在侦听之前会调用bind(),并且客户端不会调用它,但是当连接连接时,将由系统随机生成一个。
3。listing()和连接()函数
如果作为服务器,请在调用socket()和bind()后调用()。如果客户端调用Connect()此时发布连接请求,则服务器将接收请求。
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
收听功能的第一个参数是要侦听的套接字描述单词,第二个参数是可以由相应套接字排队的最大连接数。 socket()函数创建的套接字默认情况下是一个活动类型。侦听功能将套接字更改为被动类型,等待客户端的连接请求。
连接函数的第一个参数是客户端的套接字描述字,第二个参数是服务器的套接字地址,第三个参数是套接字地址的长度。客户通过调用连接函数来建立与TCP服务器的连接。
4。接受()函数
TCP服务器依次调用socket(),bind()和listing()后,它将收听指定的套接字地址。 TCP客户端依次调用Socket()和Connect()之后,它希望TCP服务器发送连接请求。在TCP服务器听取此请求之后,它将调用Accept()函数以接收请求,以便建立连接。之后,可以启动网络I/O操作,也就是说,读写I/O操作类似于普通文件。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接受函数的第一个参数是服务器的套接字描述单词,第二个参数是struct sockaddr*的指针,该指针用于返回客户端的协议地址,第三个参数是协议地址的长度。如果ACCPET成功,其返回值是由内核自动生成的全新描述单词,代表与返回客户端的TCP连接。
注意:接受的第一个参数是服务器的套接字描述Word,该单词由服务器开始调用socket()函数生成,该函数称为侦听套接字描述word;当接受函数返回连接的套接字描述字时。服务器通常仅创建一个收听套接字描述单词,该单词存在于该服务器的整个生命中。内核为服务器进程接受的每个客户端连接创建一个连接的套接字描述字。当服务器完成到某个客户端的服务时,相应的连接套接字说明字已关闭。
5。read(),write()和其他功能
一切都在没有风的情况下完成,因此服务器与客户建立了良好的联系。您可以调用网络I/O进行读写操作,也就是说,您可以实现网络中不同过程之间的通信!网络I/O操作有几个组:
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
建议使用recVMSG()/sendmsg()函数。这两个功能是最通用的I/O函数。实际上,上面的其他功能可以用这两个功能替换。他们的陈述如下:
#include
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
读取功能负责读取FD的内容。读取成功后,读取将返回实际字节读取的实际数量。如果返回值为0,则意味着文件的末尾已读取,少于0表示错误发生了错误。如果错误是EINTR,则意味着读取是由中断引起的。如果是Econnrest,则网络连接存在问题。
写功能将BUF中的NBYTE BYTE内容写入文件描述符fd。成功后,返回书面字节的数量。在故障上返回-1,并设置ERRNO变量。
在网络程序中,我们写入套接字文件描述符时有两种可能性。 1)写的回报值大于0,表明已经写了一些或全部数据。 2)返回的值小于0,此时发生错误。我们需要根据错误类型来处理它。如果错误是EINTR,则意味着在写作时发生中断错误。如果是Epipe,则网络连接存在问题(另一方已关闭连接)。
6。关闭()函数
服务器与客户端建立连接后,将执行一些读写操作。读取和写入操作完成后,必须关闭相应的套接字描述单词。例如,在操作打开的文件后,您必须致电FCLOSE关闭打开的文件。
#include
int close(int fd);
关闭TCP
关闭插座时,套接字的默认行为标记为封闭,然后立即将其返回到呼叫过程。呼叫过程不再使用描述单词,也就是说,它不能用作读取或写入的第一个参数。
注意:关闭操作仅导致相应套接字描述Word -1的参考计数。只有当参考计数为0时,TCP客户端才会向服务器发送终止连接请求。
插座中的三次TCP握手以建立连接
我们知道,TCP需要执行“三向握手”以建立连接,即交换三个数据包。一般过程如下:
一个。客户端将SYN J发送到服务器
b。服务器对客户端响应SYN K,并确认Syn J的ACK J+1。
c。客户希望服务器发送确认ACK K+1
只完成了三个握手,但是插座功能中发生了什么?请参阅下图:
从图可以看出,当客户端调用连接时,连接请求会触发并发送到服务器。
J数据包,此时连接进入阻止状态;服务器会听到连接请求,即接收SYN J数据包,调用Accept函数接收请求并将SYN K发送给客户端,ACK
J+1,然后接受进入阻止状态;客户端接收服务器的SYN K和ACK J+1之后,然后连接返回并SYN
K确认;当服务器接收ACK K+1时,请接受返回,并且三个握手完成并建立了连接。
摘要:客户端连接返回三个握手的第二次,而服务器接受则返回三个握手的第三次。
TCP在插座释放连接中的四次握手
以上引入了TCP在插座中的三次握手建立过程以及所涉及的插座功能。现在,我们介绍了插座中四个握手中释放连接的过程。请参阅下图:
图过程如下:
在Linux下以C语言实施插座通信
socket_client.C
//客户端程序socket_client.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_LINE 1024
#define PORT 8080
#define SERVER_IP_ADDR "192.168.3.77"
struct _client_data
{
uint8_t sockfd;
uint8_t rbuf[MAX_LINE];
uint8_t sbuf[MAX_LINE];
uint32_t buflen;
};
struct _client_data client_data;
ssize_t readline(int fd, char *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = read(fd, &c,1)) == 1) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else
return(-1); /* error, errno set by read() */
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
int client_init(int argc)
{
/*声明套接字和链接服务器地址*/
// int sockfd;
struct sockaddr_in servaddr;
/*判断是否为合法输入*/
if(argc != 2)
{
perror("usage:tcpcli " );
exit(1);
}//if
/*(1) 创建套接字*/
if((client_data.sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1)
{
perror("socket error");
exit(1);
}//if
/*(2) 设置链接服务器地址结构*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if(inet_pton(AF_INET , SERVER_IP_ADDR, &servaddr.sin_addr) < 0)
{
printf("inet_pton error for %s\n", SERVER_IP_ADDR);
exit(1);
}//if
/*(3) 发送链接服务器请求*/
if( connect(client_data.sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0)
{
perror("connect error");
exit(1);
}//if
}
int client_msg_proc()
{
#if 1
client_data.sbuf[0] = 12;
client_data.sbuf[1] = 22;
client_data.sbuf[2] = 32;
#endif
while(fgets(client_data.rbuf, MAX_LINE , stdin) != NULL)
{
write(client_data.sockfd , client_data.sbuf, strlen(client_data.sbuf));
if(readline(client_data.sockfd, client_data.rbuf, MAX_LINE) == 0)
{
perror("server terminated prematurely");
exit(1);
}//if
if(fputs(client_data.rbuf , stdout) == EOF)
{
perror("fputs error");
exit(1);
}//if
}//while
}
void* pthread_client()
{
int ret;
pthread_detach(pthread_self());
ret = client_init(2);
for(;;)
{
ret = client_msg_proc();
}//for
}
void pthread_socket_start() //创建线程函数
{
pthread_t threadhb;
int ret_thrdhb;
ret_thrdhb = pthread_create(&threadhb, NULL, pthread_client, NULL);
if (ret_thrdhb != 0){
printf("线程创建失败\n");
}
else{
printf("线程创建成功\n");
}
}
int main(int arg, char **argc)
{
pthread_socket_start();
sleep(~0l);
return 0;
}
socket_server.c
//服务端程序socket_server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_LINE 1024
#define PORT 8080
#define LISTENQ 6666
struct _server_data{
uint8_t listenfd;
uint8_t connfd;
uint8_t buf[MAX_LINE];
uint32_t buflen;
};
struct _server_data server_data;
int server_init()
{
/*声明服务器地址和客户链接地址*/
struct sockaddr_in servaddr , cliaddr;
pid_t childpid;
/*声明缓冲区*/
socklen_t clilen;
/*(1) 初始化监听套接字listenfd*/
if((server_data.listenfd = socket(AF_INET , SOCK_STREAM , 0)) < 0)
{
perror("socket error");
exit(1);
}//if
/*(2) 设置服务器sockaddr_in结构*/
bzero(&servaddr , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //表明可接受任意IP地址
servaddr.sin_port = htons(PORT);
/*(3) 绑定套接字和端口*/
if(bind(server_data.listenfd , (struct sockaddr*)&servaddr , sizeof(servaddr)) < 0)
{
perror("bind error");
exit(1);
}//if
/*(4) 监听客户请求*/
if(listen(server_data.listenfd , LISTENQ) < 0)
{
perror("listen error");
exit(1);
}//if
clilen = sizeof(cliaddr);
if((server_data.connfd = accept(server_data.listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0 )
{
perror("accept error");
exit(1);
}//if
return 0;
}
int server_msg_proc()
{
/*接受客户请求*/
server_data.buflen = read(server_data.connfd , server_data.buf , MAX_LINE);
if(server_data.buflen > 0)
{
int count = 0;
for(count = 0; count < server_data.buflen; count++)
{
printf("buf[%d] = %d\n", count, server_data.buf[count]);
}
write(server_data.connfd, server_data.buf, server_data.buflen);
return 0;
}//while
return -1;
}
int server_close()
{
close(server_data.connfd);
/*关闭监听套接字*/
close(server_data.listenfd);
}
void* pthread_socket()
{
int ret;
pthread_detach(pthread_self());
ret = server_init(); //创建用于通信的套接字
printf("ret = %d, server_data.connfd = %d, server_data.listenfd = %d\n", ret, server_data.connfd, server_data.listenfd);
for(;;)
{
if(server_data.connfd < 0)
{
printf("初始化失败,请重试!\n");
return 0;
}
while(1)
{
ret = server_msg_proc();
// if(ret = -1)
// {
// printf("数据读取完毕!\n");
// return 0;
// }
}
}//for
server_close();
}
void pthread_socket_start() //创建线程函数
{
pthread_t threadbx;
int ret_thread;
ret_thread = pthread_create(&threadbx, NULL, pthread_socket, NULL);
if (ret_thread != 0){
printf("线程创建失败\n");
}
else{
printf("线程创建成功\n");
}
}
int main(int arg, char **argc)
{
pthread_socket_start();
sleep(~0l);
return 0;
}