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

 
(一)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
#endif

int main()
{
#ifdef WIN32
	WORD 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
#endif

int main()
{
#ifdef WIN32
	WORD 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");


	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;
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值