(一)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)状态。
此时,服务端存在两个套接字,服务器套接字(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;
}