之前在《socket选项之SO_REUSEADDR和TCP_NODELAY》一文中简单介绍过断开连接的一方套接字进入的Time-wait状态。现在我们详细的讨论该状态,以解决下面疑问:
1、 Time-wait状态是怎么产生的?
2、客户端在断开连接后会进入Time-wait状态吗?
3、Time-wait状态有什么用?
4、Time-wait状态下的端口号能否设置被其他套接字重新使用?
Time-wait状态是怎么产生的?
首先,第一个问题,Time-wait状态是怎么产生的?在解决这个问题之前我们先简单回顾下TCP连接在断开时的四次握手过程:
图中演示了从主机A主动发起断开连接请求的四次握手过程:
第一次握手:主机A发送FIN数据包,SEQ设置为5000,表示主机B在接收到数据后需要返回一个ACK为5001的数据包,否则主机A重新发送该数据包。
第二次握手:主机B发送ACK为5001的数据包表示成功接收主机A断开连接请求,同时SEQ为6000,但是这是断开连接的过程,主机B不会期望主机A返回ACK为6001的数据包。
第三次握手:主机B准备好了断开连接工作后,向主机A发送FIN数据包,表示请求断开与A的连接,同时SEQ设置为6001,ACK设置为5001。
第四次握手:主机A接收到主机B请求断开连接的数据包之后返回允许断开连接的数据包,SEQ设置为5001,同时ACK设置为6002表示已经成功接收主机B发送的数据包。
我们所讨论的Time-wait状态就是发生在主机A返回最后一个数据包之后,如下图所示,当主机A的套接字进入Time-wait状态后,该套接字绑定的端口号将在这段时间内不能被其他套接字使用,这就是当我们使用Ctrl+C结束服务端程序后,马上再使用该端口启动服务端时会出现bind error的原因(Ctrl+C虽然是强制关闭服务器端,但是断开TCP的连接时也会经历上述四个握手过程)。
客户端在断开连接后会进入Time-wait状态吗?
在验证这个问题之前,先给出这个问题的答案!答案就是:会。只不过客户端的端口号是随机分配的所以我们很少见到bind error的错误,下面我将通过netstat命令验证这个答案。
第一步,使用系统的9190端口启动服务端程序。
[Hyman@Hyman-PC echoSever]$ ./serv 9190
服务端进入阻塞等待连接状态,然后用netstat –ant命令查看目前系统中socket的状态。
请注意红色圈出的部分,就是服务端创建的socket,目前已经进入listen状态。
第二步,使用客户端连接服务器。
[Hyman@Hyman-PC echoSever]$ ./clnt 127.0.0.1 9190
connect succeed
客户端连接成功,然后再使用netstat –ant命令查看目前系统的socket的状态:
服务端的套接字进入established状态,同时出现了客户端的套接字,绑定的端口为43436,也进入了established状态。
第三步,输入Ctrl+C断开客户端的连接,然后用netstat–ant观察套接字的状态
发现客户端的socket进入了TIME_WAIT状态,而此时如果我们对43436端口进行bind就会出现bind错误:
[Hyman@Hyman-PC echoSever]$ ./serv 43436
bind error
Time-wait状态有什么用?
之所以设置Time-wait状态,主要是考虑:万一由主机A发送的最后一次握手的数据包丢失时,主机B会再次发送第三次握手的数据包,若此时主机A关闭了Socket就会出现主机B永远等不到主机A最后一次握手数据包的情况,B也就永远无法关闭。所以在主动发起断开连接请求的主机上设置了这个Time-wait状态。
Time-wait状态下的端口号能否设置被其他套接字重新使用?
这就是《socket选项之SO_REUSEADDR和TCP_NODELAY》一文中提到的方法,通过socket可选项设置函数setsockopt(…)可以实现设置套接字Time-wait状态的开启和关闭。
int option;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void*)option,sizeof(option));
…
设置option为1时可以将Time-wait状态下套接字的端口重新分配给新的套接字。