网络基础 TCP连接的创建与释放

来源:互联网 发布:指南针证券分析软件 编辑:程序博客网 时间:2024/05/22 06:19
 
(一)TCP的连接建立
       下图表示TCP的建立连接的过程。初始时两端的TCP进程都处于CLOSED(关闭)状态。客户端主动打开连接、而服务器则被动打开连接。
        
  
       (1) Client发送一个SYN同步标志位为1的TCP数据包,表示想与目标服务器进行通信,并发送一个同步序列号(如seq=x)。此时的报文段不携带数据,但是要消耗一个SYN。此时TCP客户进程进入SYN_SEND(同步已发送)状态。
       (2) Server如果同意建立连接,则响应一个确认报文。在确认报文中应把SYN和ACK都置为1,确认号ack=x+1,同时也为自己选择一个初始的序号seq=y。Server进程进入SYN_RCVD(同步收到)状态。
       (3) Client收到Server的确认后,还要向B给出确认(为了防止已失效的Client的连接请求报文段突然又传送到了Server)。确认报文段的ACK置1,确认好ack=y+1。 此时Client进入ESTABLISHED(已建立连接)状态。 当Server收到A的确认后,也进入ESTABLISHED状态。
 
(二)TCP的连接释放(参考《计算机网络》)
       TCP连接释放与连接建立相比较稍微有点复杂。数据传输结束后,通信的双方都可以释放连接。
 
       Client与Server都处于ESTABLISHED状态。Client的进程首先向TCP发送连接释放报文段,并停止再发送数据,主动关闭TCP连接。Client把FIN置为1,其序号seq=u(前面已传送过的数据的最后一个字节的序号加1)。 此时Client进入FIN_WAIT_1(终止等待1)状态, 等待Server的确认。FIN报文段不携带数据。
       Server收到连接释放报文段后即发送确认,确认号是ack=u+1,而这个报文段自己的序号是v,等于Server前面已传送的数据的最后一个字节的序号加1。然后Server就进入CLOSE_WAIT(关闭等待)状态。因而此时从Client到Server这个方向的连接就释放了,这时的TCP连接处于半关闭(half-close)状态,即从Server到Client的连接并未关闭。这个状态也许会持续一段时间。此时Client收到Server的确认报文,进入FIN_WAIT2(终止等待2)状态,等待Server发出的连接释放报文段。
       若Server已经没有要想Client发送的数据,其应用程序就通知TCP释放连接。此时Server发出的连接释放报文段必须使FIN=1。 现假定Server的序号为w(在半关闭状态Server可能又发送了一些数据),Server还必须重复上次已经发送过的确认号ack=u+1。这是Server就进入LAST_ACK(最后确认)状态,等待Client的确认。
       Client在收到Server的连接释放报文段后,必须对此发出确认。然后进入TIME_WAIT(时间等待)状态。此时TCP连接还未释放,必须经过时间等待计时器设置的时间2MSL,(Maximum Segment Lifetime,最长报文段寿命 MSL=2min)。
 
        为何Client在TIME_WAIT 状态必须等待2MSL的时间呢?
        第一, 为了保证Client的最后一个ACK报文段能够到达Server。如果这个ACK丢失,则处在LAST_ACK状态的Server收不到FIN+ACK报文段的确认,Server会超时重传这个FIN+ACK报文段,而Client就能在2MSL时间内收到这个重传的FIN+ACK报文段,然后client重传一次确认,重新启动2MSL计时器。
        第二,Client在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本链接持续的时间内产生的所有报文段都从网络中消失。
 
        注意:在Client的TIME_WAIT状态时两端的端口都不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置 SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。

  (三)TCP的有限状态机(图片来源于网上)
          

  (四)TCP的连接释放状态分析
        笔者模拟的网络环境是在LINUX下进行的。 下面的例子都是查看服务器的网络状态:
      
       1.  启动服务器程序server, 服务器的ip为"172.16。130.134",绑定的监听端口为8300。
        通过netstat -a命令查看网络状况如下
        可以看到,服务器套接字处于监听状态,等待客户端的连接。       
 
        2.  启动客户端client,客户端的IP为"172.16.130.240"。

       此时,服务端存在两个套接字,服务器套接字(LISTEN)状态,监听套接字(ESTABLISHED)状态。
       客户端存在一个套接字,处于ESTABLISHED状态。
 
       3. 正常终止,客户端调用close关闭套接字:


       此时客户端发送FIN到服务器,服务器回应ACK,此时服务器进入CLOSE_WAIT状态,客户端进入FIN_WAIT_2状态,具体状态图如下:      
 
      4. 服务器调用close关闭监听套接字:
      此时服务器的监听套接字将直接关闭,客户端进入 TIME_WAIT状态(2MSL后自动关闭):


   (五) 测试源代码

  客户端代码:    
#include <stdio.h>#include <string.h>#include <iostream>#include <string>using namespace std;#ifdef WIN32#include <WinSock2.h>#include <Windows.h>#include <ws2tcpip.h>#else    #include <sys/socket.h>#include <arpa/inet.h>#include <fcntl.h>#include <sys/syscall.h>#include <unistd.h>#include <pthread.h>#define SOCKET int#endifint main(){#ifdef WIN32WORD wVersionRequestion;WSADATA wsaData;int err;wVersionRequestion = MAKEWORD(1,1);err = WSAStartup(wVersionRequestion,&wsaData);if(0 != err){return;}if(1 != LOBYTE(wsaData.wVersion)||1 != HIBYTE(wsaData.wVersion)){WSACleanup();return;}#endif//创建套接字SOCKET socketSer = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;addrSer.sin_port = htons(8700);addrSer.sin_addr.s_addr = inet_addr("172.16.130.240");//绑定套接字int connRet = connect(socketSer,(struct sockaddr*)&addrSer,sizeof(struct sockaddr_in));if(connRet != 0)cout << "connect error!" << endl;  cout << "conn success" << endl;while(1){cout << "state[send recv close]:>>";fflush(stdout);string cmd;getline(std::cin, cmd);if(cmd == string("send")){cout << "Input your send message:";cout << ">>";string cmd;getline(std::cin, cmd);if(cmd == string("close")){close(socketSer);}else{send(socketSer,cmd.c_str(),cmd.length(),0);}}if(cmd == string("recv")){cout << "Waiting for recv message:";char* pRcvBuf =  new char[1024];int si32Ret = recv(socketSer, pRcvBuf, 1024, 0);}if(cmd == string("close")){ close(socketSer);}   }   return 0;}
服务器端代码:
#include <stdio.h>#include <iostream>#include <string>using namespace std;#ifdef WIN32#include <WinSock2.h>#include <Windows.h>#include <ws2tcpip.h>#else    #include <sys/socket.h>#include <arpa/inet.h>#include <fcntl.h>#include <sys/syscall.h>#include <unistd.h>#define SOCKET int#endifint main(){#ifdef WIN32WORD wVersionRequestion;WSADATA wsaData;int err;wVersionRequestion = MAKEWORD(1,1);err = WSAStartup(wVersionRequestion,&wsaData);if(0 != err){return;}if(1 != LOBYTE(wsaData.wVersion)||1 != HIBYTE(wsaData.wVersion)){WSACleanup();return;}#endifSOCKET socketSer = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addrSer;addrSer.sin_family = AF_INET;addrSer.sin_port = htons(8700);addrSer.sin_addr.s_addr = inet_addr("172.16.130.240");bind(socketSer,(struct sockaddr*)&addrSer,sizeof(struct sockaddr_in));listen(socketSer,5);struct sockaddr_in addrClient;int len = sizeof(addrClient);printf("server start........\n");SOCKET sockListen;string cmd;  sockListen = accept(socketSer,(struct sockaddr*)&addrClient,(socklen_t*)&len);while(1){cout << "state[send recv close]:>>";string cmd;getline(std::cin, cmd);if(cmd == string("send")){cout << "Input your send message:";cout << ">>";string cmd;getline(std::cin, cmd);send(sockListen,cmd.c_str(),cmd.length(),0);}if(cmd == string("recv")){cout << "Waiting for recv message:";fflush(stdout);char pRcvBuf[1024] = {0};int si32Ret = recv(sockListen, pRcvBuf, 1024, 0);if( si32Ret == -1){printf("recv error!\n");}else if(si32Ret == 0)  //client close the socket{//close(socketSer);printf("client close the socket!\n");}else{printf("recv: %s\n", pRcvBuf);}}if(cmd == string("close")){close(sockListen);}}return 0;}
 
0 0
原创粉丝点击