TCP的状态变迁图如下:
其中的TIME_WAIT状态也成为2MSL等待状态。每个具体的TCP实现必须选择一个报文段最大生存时间MSL(maximum
segment lifetime)。它是任何报文段被丢弃前在网络内的最长时间 。我们知道这个时间是有限的,因为TCP报文段以IP
数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
在RFC793指出MSL为2分钟,然而,实现中的常用值是30秒,1分钟或2分钟。
对一个具体实现锁定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该链接必须在TIME_WAIT
状态停留的时间为2倍的MSL。这样可以让TCP再次发送最后的ACK以防止这个ACK丢失。(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的socket(客户的IP地址和端口号,服务器
的IP地址和端口号不能被使用)。这个连接只能在2MSL结束后才能被使用。
遗憾的是,大多数TCP实现强加了更为严格的限制,在2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。
注意:
1.一个socket对在它处理2MSL等待时,将不能再被使用。尽管许多具体的实现中允许一个进程重新使用仍处于2MSL等待的
端口(通常是设置选项SO_REUSEADDR),但TCP不能允许一个新的连接建立在相同的socket对上。
2.一个端口可同时绑定在UDP和TCP服务上,但是如果一个端口要同时绑定在两个TCP或者UDP服务上,则会出现端口已占用
的错误。因为TCP和UDP的端口在逻辑上是分离的。
实践:
server.c
#include<stdio.h> #include<sys/socket.h> #include<arpa/inet.h> #include<string.h> #define MAX_SIZE 256 int main(void){ int listenfd,connfd; struct sockaddr_in servaddr,clientaddr; int n,addrlen = sizeof(struct sockaddr_in); char addr_p[16]; char rbuf[MAX_SIZE]; char sbuf[MAX_SIZE] = "i am server."; if(inet_pton(AF_INET,"192.168.18.249",&servaddr.sin_addr) < 0){ perror("inet_pton"); return -1; } memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5555); if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ perror("listen"); return -1; } if(bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) == -1){ perror("bind"); return -1; } if(listen(listenfd, 10) == -1){ perror("listen"); return -1; } while(1){ if((connfd = accept(listenfd, (struct sockaddr*)&clientaddr,(socklen_t*)&addrlen)) == -1){ perror("accept"); continue; } break; } if(inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr,addr_p,(socklen_t)sizeof(addr_p)) == NULL){ perror("inet_ntop"); return -1; } printf("connect client ip:%s\n",addr_p); close(connfd); close(listenfd); return 0; }
#include<stdio.h> #include<sys/socket.h> #include<string.h> #include<arpa/inet.h> #define MAX_SIZE 256 int main(void){ int sockfd,n; char rbuf[MAX_SIZE],sbuf[MAX_SIZE]="i am client."; struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5555); if(inet_pton(AF_INET,"192.168.18.249",&servaddr.sin_addr) < 0){ perror("inet_pton"); return -1; } if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ perror("socket"); return -1; } if(connect(sockfd,(struct sockaddr*)&servaddr,(socklen_t)sizeof(servaddr))<0){ perror("connect"); return -1; } if((n =recv(sockfd,rbuf,MAX_SIZE,0)) < 0){ perror("send"); return -1; } rbuf[n]='\0'; printf("%s\n",rbuf); close(sockfd); return 0; }运行结果:
先一台机器上先运行server,然后在另一台上运行client。
server:
root@virtual-machine:~# ./a.out
connect client ip:192.168.18.25
root@virtual-machine:~# ./a.out
bind: Address already in use
root@virtual-machine:~# ./a.out
^C
client:
yan@vm:~$ ./a.out
client连接到server后,一直阻塞读,但是此时server主动关闭了连接,server进入了2MSL等待状态,此时再运行server,提示
地址正在使用,等待了1分钟后再次运行,提示消失。说明已经不在2MSL等待状态。