转载请注明原文链接哦
做网络开发,经常会遇到以下”投诉“——
”你的软件网络怎么做得那么不稳定!“
”怎么了?“
”经常有设备无故下线了。“
”啊。。。“
”赶紧解决啊,现场急。“
”能给个日志来么。。。顺便(用wireshark)抓个包呗。。。“
。。。。。
反正遇到这个问题,我首先会检查soket是网络不稳定断开的,还是正常断开,正常断开的话是谁主动断开的。我们就来讨论一下怎么是正常的关闭,又怎样进行判断是哪边提出断开的。
TCP正常断开需要进行四次分手(哎呀嘛呀~怎么和人一样墨迹啊~)。翠花上图:
所谓四次分组就是客户端和服务端相互了四次分组交换:
“我们分手吧!” —— 啪!一个(FIN)打过来
“哎呦!不要啊~还记得大明湖畔%^&*(#$……” —— 回了个ACT,
“好吧,竟然无法挽留,我答应你” —— 把以前的礼物的还了(也发了个FIN)
“BYE-BYE!” —— 也回了个ACT
嗯嗯,其实TCP四次分手其实也是和人一样,既然分手了就分得干净点,免得有一天有了新的对象(新的连接),还因为和前任没断干净而出现混乱。
为了进一步验证,我们用代码做做实验吧:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int service(int listenfd)
{
int connfd = -1;
while(1)
{
printf("accept befor....\n");
connfd = accept(listenfd, NULL, NULL);
printf("accept new socket\n");
getchar();
printf("close connfd\n");
close(connfd);
connfd = -1;
}
return 0;
}
int main(int argv, char *argc[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
char ip[64] = {0};
int client = 0, port;
int ret;
if(argv < 4 || sockfd < 0)
{
perror("socket fail");
close(sockfd);
return -1;
}
if(0 == strcmp(argc[1], "client"))
{
printf("start -- client\n");
client = 1;
}
else
{
printf("start -- %s\n", argc[1]);
}
strcpy(ip, argc[2]);
printf("ip:%s\n", ip);
port = atoi(argc[3]);
printf("port:%d\n", port);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if(client)
{
ret = inet_pton(AF_INET, ip, &addr.sin_addr);
if(ret != 1)
{
perror("inet_pton fail");
close(sockfd);
return -1;
}
ret = connect(sockfd, (struct sockaddr *)&addr, (socklen_t)sizeof(addr));
if(ret != 0)
{
perror("connect fail");
close(sockfd);
return -1;
}
printf("connect %s:%d success\n", ip, port);
getchar();
#if 0
printf("abort without close socker\n");
abort();
#else
printf("close socker\n");
close(sockfd);
#endif
return 0;
}
else
{
addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(sockfd, (struct sockaddr *)&addr,sizeof(addr));
if(ret < 0)
{
perror("bind fail");
close(sockfd);
return -1;
}
if(listen(sockfd,5) < 0)
{
perror("listen Faild");
close(sockfd);
return -1;
}
service(sockfd);
}
return 0;
}
为了方便,上面同时实现了客户端和服务端,通过运行选项来区别是运行客户端的代码还是服务端的代码。
建立TCP连接
编译成功后,先运行服务端
看到服务端口已启用
运行客户端
连接成功,查看TCP状态
当前状态都为“ESTABLISHED”
客户端先主动断开,断开后查看TCP状态
可以看到:
- 主动断开的一方状态变为FIN_WAIT2
- 被动断开的一方状态为CLOSE_WAIT
服务端也断开,断开后查看TCP状态
可以看到:
- 主动断开的一方已经没有状态描述了
- 被动断开的一方还存在状态为TIME_WAIT
过一段时间后,TIME_WAIT也消失了
下面是TCP四次分手分组交换图
咦?!怎么刚才没看
FIN_WAIT_1的状态呢!!那是因为发出去的FIN和收到的ACK之前的时间很快,所以通常FIN_WAIT_1很难出现。但也不是不可能,只要主动断开之前对方把网线拔了,你的分手信(FIN)就无法送给对方了:
“我们分手吧!”
“喂~你说什么我听不见!大声点!喂!。。。嘟嘟嘟。。。”
“( 0_0||| )"
(完)