页面加载中,请稍候
来源:一口Linux发布时间:2022-10-09556浏览
询问 AI
而且在用 wireshark 工具抓包的时候,我们也会常看到 TCP 挥手过程是三次,而不是四次,如下图:
先来回答为什么 RFC 文档里定义 TCP 挥手过程是要四次?再来回答什么情况下,什么情况会出现三次挥手?
具体过程:FIN 报文一定得调用关闭连接的函数,才会发送吗?不一定。如果进程退出了,不管是不是正常退出,还是异常退出(如进程崩溃),内核都会发送 FIN 报文,与对方完成四次挥手。
当服务端收到 RST 后,内核就会释放连接,当服务端应用程序再次发起读操作或者写操作时,就能感知到连接已经被释放了:
但是注意,shutdown 函数也可以指定「只关闭读取方向,而不关闭发送方向」,但是这时候内核是不会发送 FIN 报文的,因为发送 FIN 报文是意味着我方将不再发送任何数据,而shutdown 如果指定「不关闭发送方向」,就意味着 socket 还有发送数据的能力,所以内核就不会发送 FIN。
然后因为 TCP 延迟确认机制是默认开启的,所以导致我们抓包时,看见三次挥手的次数比四次挥手还多。什么是 TCP 延迟确认机制?当发送没有携带数据的 ACK,它的网络效率也是很低的,因为它也有 40 个字节的 IP 头 和 TCP 头,但却没有携带数据报文。为了解决 ACK 传输效率低问题,所以就衍生出了TCP 延迟确认。TCP 延迟确认的策略:
延迟等待的时间是在 Linux 内核中定义的,如下图:
知道了 HZ 的大小,那么就可以算出:怎么关闭 TCP 延迟确认机制?如果要关闭 TCP 延迟确认机制,可以在 Socket 设置里启用 TCP_QUICKACK,启用TCP_QUICKACK,就相当于关闭 TCP 延迟确认机制。
//1表示开启TCP_QUICKACK,即关闭TCP延迟确认机制
intvalue=1;
setsockopt(socketfd,IPPROTO_TCP,TCP_QUICKACK,(char*)value,sizeof(int));
当被动关闭方(上图的服务端)在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。服务端的代码如下,做的事情很简单,就读取数据,然后当 read 返回 0 的时候,就马上调用 close 关闭连接。因为 TCP 延迟确认机制是默认开启的,所以不需要特殊设置。
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<netinet/tcp.h>
#defineMAXLINE1024
intmain(intargc,char*argv[])
{
//1.创建一个监听socket
intlistenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd<0)
{
fprintf(stderr,"socketerror:%s\n",strerror(errno));
return-1;
}
//2.初始化服务器地址和端口
structsockaddr_inserver_addr;
bzero(server_addr,sizeof(structsockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(8888);
//3.绑定地址+端口
if(bind(listenfd,(structsockaddr*)(server_addr),sizeof(structsockaddr))<0)
{
fprintf(stderr,"binderror:%s\n",strerror(errno));
return-1;
}
printf("beginlisten....\n");
//4.开始监听
if(listen(listenfd,128))
{
fprintf(stderr,"listenerror:%s\n\a",strerror(errno));
exit(1);
}
//5.获取已连接的socket
structsockaddr_inclient_addr;
socklen_tclient_addrlen=sizeof(client_addr);
intclientfd=accept(listenfd,(structsockaddr*)client_addr,client_addrlen);
if(clientfd<0){
fprintf(stderr,"accepterror:%s\n\a",strerror(errno));
exit(1);
}
printf("acceptsuccess\n");
charmessage[MAXLINE]={0};
while(1){
//6.读取客户端发送的数据
intn=read(clientfd,message,MAXLINE);
if(n<0){//读取错误
fprintf(stderr,"readerror:%s\n\a",strerror(errno));
break;
}elseif(n==0){//返回0,代表读到FIN报文
fprintf(stderr,"clientclosed\n");
close(clientfd);//没有数据要发送,立马关闭连接
break;
}
message[n]=0;
printf("received%dbytes:%s\n",n,message);
}
close(listenfd);
return0;
}
#include<stdlib.h>
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<netdb.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
intmain(intargc,char*argv[])
{
//1.创建一个监听socket
intconnectfd=socket(AF_INET,SOCK_STREAM,0);
if(connectfd<0)
{
fprintf(stderr,"socketerror:%s\n",strerror(errno));
return-1;
}
//2.初始化服务器地址和端口
structsockaddr_inserver_addr;
bzero(server_addr,sizeof(structsockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
server_addr.sin_port=htons(8888);
//3.连接服务器
if(connect(connectfd,(structsockaddr*)(server_addr),sizeof(server_addr))<0)
{
fprintf(stderr,"connecterror:%s\n",strerror(errno));
return-1;
}
printf("connectsuccess\n");
charsendline[64]="hello,iamxiaolin";
//4.发送数据
intret=send(connectfd,sendline,strlen(sendline),0);
if(ret!=strlen(sendline)){
fprintf(stderr,"senddataerror:%s\n",strerror(errno));
return-1;
}
printf("alreadysend%dbytes\n",ret);
sleep(1);
//5.关闭连接
close(connectfd);
return0;
}
先启用服务端:
然后用 tcpdump 工具开始抓包,命令如下:tcpdump-ilotcpandport8888-s0-w/home/tcp_close.pcap
此时,服务端的输出:
接下来,我们来看看抓包的结果。
可以看到,TCP 挥手次数是 3 次。所以,下面这个结论是没问题的。结论:当被动关闭方(上图的服务端)在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制(默认会开启)」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。实验二我们再做一次实验,来看看关闭 TCP 延迟确认机制,会出现四次挥手吗?客户端代码保持不变,服务端代码需要增加一点东西。在上面服务端代码中,增加了打开了 TCP_QUICKACK (快速应答)机制的代码,如下:
编译好服务端代码后,就开始运行服务端和客户端的代码,同时用 tcpdump 进行抓包。抓包的结果如下,可以看到是四次挥手。
设置 TCP_QUICKACK 的代码,为什么要放在 read 返回 0 之后?我也是多次实验才发现,在 bind 之前设置 TCP_QUICKACK 是不生效的,只有在 read 返回 0 的时候,设置 TCP_QUICKACK 才会出现四次挥手。网上查了下资料说,设置 TCP_QUICKACK 并不是永久的,所以每次读取数据的时候,如果想要立刻回 ACK,那就得在每次读取数据之后,重新设置 TCP_QUICKACK。而我这里的实验,目的是为了当收到客户端的 FIN 报文(第一次挥手)后,立马回 ACK 报文,所以就在 read 返回 0 的时候,设置 TCP_QUICKACK。当然,实际应用中,没人会在我这个位置设置 TCP_QUICKACK,因为操作系统都通过 TCP 延迟确认机制帮我们把四次挥手优化成了三次挥手了,这本来就是一件好事呀。
新闻来源:一口Linux,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
暂无评论哦,快来评论一下吧!
2026-06-26

2026-07-06