socket网络通信
本文注意基于socket来分析TCP连接建立过程。
先回顾一下TCP连接建立过程:
主机A运行的是TCP客户端程序,主机B运行的是TCP服务器程序,最初两端TCP进程处于Closed态,A主动打开连接,对应客户端connect函数发起连接,B被动接受连接,对应于服务器listen函数。
服务器TCP进程先创建传输控制块TCB,准备接受客户进程的连接请求;
1 客户端进程也首先创建TCB,然后通过connect函数向服务器发送连接请求,同步位SYN=1(不携带数据),初始序列号seq=x,客户端进入SYN-SENT态;
2 服务器一直通过listen监控socket,接收到连接请求后,如同意连接,向客户端发射确认,ACK=1,SYN=1,ack=x+1,seq=y;服务器端进入SYN-RCVD;
3 客户端进程收到确认后,这时connect函数返回,对服务器进程再确认,ACK=1,seq=x+1,ack=y+1,客户端进入连接态ESTABLISHED 可读写;服务器收到再次确认后也进入ESTABLISHED 可读写。
对应socket可总结如下:
总结:客户端的connect在三次握手的第二次返回,而服务器端的accept在第三次握手的第三次返回。
TCP连接释放过程:
TCP进程双方都处于ESTABLISHED态进行读写通信,如果要释放连接:
1 客户端进程向服务器发出释放连接报文段,FIN=1,seq=u,并停止发送数据,此时客户端进入FIN-WAIT-1;
2 服务器收到释放报文请求后,发出确认ACK=1,ack=u+1,seq=v,然后进入CLOSED-WAIT态,客户端收到确认消息后进入FIN-WAIT-2态,并等待服务器发出连接释放报文;
3 服务器发出连接释放请求,FIN=1,ACK=1,ack=u+1,seq=w,并进入LAST-ACK;
4 客户端收到服务器释放连接请求后,发出确认,ACK=1,seq=u+1,ack=w+1,然后进入TIME-WAIT状态,此时还需在等2MSL(最长报文段寿命)后再进入CLOSED。B收到确认消息后立刻进入CLOSED。
对应socket可总结如下:
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M; 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;接收到这个FIN的源发送端TCP对它进行确认。这样每个方向上都有一个FIN和ACK。
最后,通过例程实践,例程内容:
下面编写一个简单的服务器、客户端(使用TCP)——服务器端一直监听本机的6666号端口,如果收到连接请求,将接收请求并接收客户端发来的消息;客户端与服务器端建立连接并发送一条消息
服务器端代码:
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #define MAXLINE 4096 int main(int argc, char** argv) { int listenfd, connfd; struct sockaddr_in servaddr; char buff[4096]; int n; if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); }//调用socket函数 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//注意网络字节序与本机字节序 servaddr.sin_port = htons(6666); if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){ printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); }//将socket绑定IP和端口 if( listen(listenfd, 10) == -1){ printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); }//listen 该端口下的socket,输入队列额度10 printf("======waiting for client's request======\n"); while(1){ if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){ printf("accept socket error: %s(errno: %d)",strerror(errno),errno); continue; }//accept来自客户端进程,注意此时产生一个已建立socket描述符confd n = recv(connfd, buff, MAXLINE, 0);//读入到buff buff[n] = '\0'; printf("recv msg from client: %s\n", buff); close(connfd);//结束 } close(listenfd); } 客户端代码: #include<stdio.h> #include<stdlib.h> #include<string.h> #include<errno.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #define MAXLINE 4096 int main(int argc, char** argv) { int sockfd, n; char recvline[4096], sendline[4096]; struct sockaddr_in servaddr; if( argc != 2){ printf("usage: ./client <ipaddress>\n"); exit(0); } if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ printf("create socket error: %s(errno: %d)\n", strerror(errno),errno); exit(0); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(6666); if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){ printf("inet_pton error for %s\n",argv[1]); exit(0); } if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){ printf("connect error: %s(errno: %d)\n",strerror(errno),errno); exit(0); }//发送请求 printf("send msg to server: \n"); fgets(sendline, 4096, stdin); if( send(sockfd, sendline, strlen(sendline), 0) < 0) { printf("send msg error: %s(errno: %d)\n", strerror(errno), errno); exit(0); }//send函数,发送sendline里内容 close(sockfd); exit(0); } 这是一个最基本socket通信例程,上面的服务器使用的是迭代模式的,即只有处理完一个客户端请求才会去处理下一个客户端的请求,这样的服务器处理能力是很弱的,现实中的服务器都需要有并发处理。