这个知识点在写代码上,感觉还是挺重要的,打算总结一下吧。 打算从以下5个方面去总结。 一:TIME_WAIT状态是什么 首先,这个是TCP状态转换图里面的某个状态。这个可以参考unix网络编程。 从图中可以看出,在一个客户端与服务器通信的过程当中,主动关闭的一方会进入这个状态。 二:TIME_WAIT状态怎么出现的 好了,其实第一点已经说明了,这个状态是怎么出现的了,就是,主动关闭的一方会进入这个状态。
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SERV_PORT 9000
int main(int argc, char *const *argv)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int result;
result = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(result == -1)
{
char *perrorinfo = strerror(errno);
printf("bind返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
return -1;
}
result = listen(listenfd, 32);
if(result == -1)
{
char *perrorinfo = strerror(errno);
printf("listen返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
return -1;
}
int connfd;
const char *pcontent = "I sent sth to client!\n";
for(;;)
{
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
write(connfd,pcontent,strlen(pcontent));
printf("本服务器给客户端发送了一串字符~~~~~~~~~~~!\n");
close(connfd);
}
close(listenfd);
return 0;
}
使用以上程序作为服务端,然后用telnet作为客户端,发起连接,这个时候,根据程序说明,会马上关闭这个连接,然后,我们用netstat命令观察可以看到除了有一个端口处于监听状态意外,还有的就是出现了TIME_WAIT这个状态了。 三:TIME_WAIT状态出现了有什么影响 感觉第二点出现的情况,如果我们马上关闭这个服务端程序,然后又重新启动,这个时候你会发现这个程序启动失败失败的原因是bind error了,提示地址已复用,也就是同一个地址和端口只能同时绑定一次。我们试想,如果在服务端处理大量连接的时候,某个时候,服务端挂掉了,这个时候由于TIME_WAIT状态的存在,导致程序运行不起来,那不坏菜了,服务端还怎么提供服务?? 四:为什么要有TIME_WAIT状态 我们再来谈谈为什么需要这个状态?什么,居然要有这个状态??第三点不是说有这个状态会有不好的情况吗?当然,先别急,既然这个状态的出现就有他出现的道理,对于第三点出现时会有一些影响,当然,我们也是可以避免的,我们在第五点去谈这个问题。
为什么要有这个状态,参考了unix网络编程当中谈到的两点。 一::可靠地实现TCP全双工的终止。
这句话怎么理解呢?我们知道tcp是全双工的,
我们看上面的图,在左边,
如果没有TIME_WAIT这个状态,
那么服务端便会从FIN_WAIT2这个状态->CLOSED状态,
这两个状态的转变有什么影响吗?我们看上图,
如果服务端最后一次发送的ack包,
由于某种原因丢了,客户端没有收到,
那么根据tcp的特性,客户端会再次发送FIN包,
如果此时服务端处于TIME_WAIT状态,
就会重发这个ack包,如果此时服务端处于CLOSED状态,
无论客户端有没有收到ack包,
因为这个连接已经关闭了,所以服务端也就不会重发最
后一个ack包了,这种断开就是不太友好的,
如果有了TIME_WAIT状态,那么便有助于可靠
的实现TCP全双工连接的终止。
二:允许老的重复的TCP数据包在网络中消逝(丢弃)。
怎么理解这句话呢?这个要结合TIME_WAIT
为什么要存在的时间是1-4min有关了,
根据资料说明,一个数据包在网络上存
活的时间大概是1-4min,我们还是从反
面来想这个问题,如果没有这个状态,
那么在服务端发送最后一次ack包以后,
服务端会马上处于CLOSE状态,然后,
假设这个时候有一个新的客户端连进来,
假设这个新的客户端的IP,PORT与之前
的一个客户端是一样的,没有TIME_WAIT状态,
最后一个ack的包可能就会发到这个新的连接
上来。。。。。。如果有TIME_WAIT状态,
即使有人伪造一个相同IP和PORT的连接,
也不会让这个包发到刚刚建立起来的连接上。
五:如何避免TIME_WAIT状态 使用套接字选项可以避免这个状态的产生,从而可以让服务端程序在出现异常时可以快速的重启,恢复运行。 setsockopt,这个函数一般位于socket与bind之间,如果想探讨为什么一般位于这两个函数之间的,可以网上搜索。
#include <stdio.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SERV_PORT 9000
int main(int argc, char *const *argv)
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int reuseaddr=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, (const void *) &reuseaddr,sizeof(reuseaddr)) == -1)
{
char *perrorinfo = strerror(errno);
printf("setsockopt(SO_REUSEADDR)返回值为%d,错误码为:%d,错误信息为:%s;\n",-1,errno,perrorinfo);
}
int result;
result = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if(result == -1)
{
char *perrorinfo = strerror(errno);
printf("bind返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
return -1;
}
result = listen(listenfd, 32);
if(result == -1)
{
char *perrorinfo = strerror(errno);
printf("listen返回的值为%d,错误码为:%d,错误信息为:%s;\n",result,errno,perrorinfo);
return -1;
}
int connfd;
const char *pcontent = "I sent sth to client!\n";
for(;;)
{
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
write(connfd,pcontent,strlen(pcontent));
printf("本服务器给客户端发送了一串字符~~~~~~~~~~~!\n");
close(connfd);
}
close(listenfd);
return 0;
}
可以使用以上程序配合telnet进行测试。
|