先仍给出一段测试代码,同CLOSE_WAIT状态分析一文测试代码,main函数如下.测试后,会发现两边有大量的TIME_WAIT连接.
int main(int argc, char* argv[]){
int sender = 0;
int ret = 0;
int listen_sock = 0;
if(argc !=2){
printf("usage: ./test 1/n");
return -1;
}
sender = atoi(argv[1]);
if(sender){
while(1){
ret = socket_connect("10.224.55.145",8765);
if(ret < 0){
return -1;
}else{
close(ret);
}
}
}else{
listen_sock = socket_listen(8765,100);
if(listen_sock < 0){
return -1;
}else{
while(1){
ret = socket_accept(listen_sock,NULL);
if(ret < 0){
return -1;
}else{
close(ret);
}
}
}
}
return 0;
}
CLOSE_WAIT状态分析一文已经介绍了一个通常的TCP连接终止的过程.我们以CLIENT端断开连接重新介绍下:
CLIENT--- FIN --- SERVER
SERVER--- ACK --- CLIENT
SERVER--- FIN --- CLIENT
CLIENT--- ACK --- SERVER
步骤III,SERVER发送FIN报文后进入LAST_ACK状态.步骤IV,CLIENT接收到FIN报文并发出ACK报文后进入TIME_WAIT状态.(当SERVER收到ACK报文后,也即可以进入到CLOSED可用状态了).
下面先解释一下TIME_WAIT状态存在的原因.
MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现都必须选择一个确定的MSL值.TIME_WAIT 状态最大保持时间是2*MSL.
TCP报文在传送过程中可能因为路由故障被迫缓冲延迟,选择非最优路径等等,结果发送方TCP机制开始超时重传.前一个TCP报文可以称为"漫游TCP重复报文",后一个TCP报文可以称为"超时重传TCP重复报文".假设最终的ACK并未被SERVER端收到,则SERVER端将重发FIN,client必须维护TCP状态信息以便可以重发最终的ACK,否则会发送RST,然后server认为发生错误.TCP实现必须可靠地终止连接的两个方向(全双工关闭),client必须进入TIME_WAIT 状态,因为client可能面临重发最终ACK的情形.
如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了, 第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二 个连接.TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT 状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被 丢弃.建立第二个连接的时候,不会混淆.
先调用close()的一方会进入TIME_WAIT状态.
我们写server程序总是在调用bind()之前设置SO_REUSEADDR套接字选项.这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT,可以重用端口.如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息.如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时 SO_REUSEADDR 选项非常有用.必须意识到,会出现上面说的问题,当接收到的连接相关五元组的(协议,本地地址,本地端口,远程地址,远程端口)相同时,程序有可能收到非期望数据.
不过一般的端口分配算法能保证五元组中的远程端口是不一样的.客户端的端口分配一般是自动的,且一般都是从上一次分配的端口号+1开始分配的.所以我们要知道的就是理论上有这种可能,事实上是很不可能.