|
马上注册登陆,结交更多好友,享用更多功能,让你轻松玩转社区
您需要 登录 才可以下载或查看,没有账号?用户注册
x
当被动关闭方在TCP挥手过程中,如果「没有数据要发送」,同时「没有开启TCP_QUICKACK(默认情况就是没有开启,没有开启TCP_QUICKACK,等于就是在使用TCP延迟确认机制)」,那么第二和第次挥手就会合并传输,这样就出现了次挥手。所以,出现次挥手现象,是因为TCP延迟确认机制导致的。大家好,我是小林。这篇文章之前发过,但是当时忘记标注原创了。有读者反馈,我文章被别人转载,但是没有注明原作者信息,所以就重新发一遍,标注个原创。----------正文---------- ip域名查询网的相关知识也可以到网站具体了解一下,有专业的客服人员为您全面解读,相信会有一个好的合作!
上周有位读者面美团时,被问到:TCP四次挥手中,能不能把第二次的ACK报文,放到第次FIN报文一起发送
虽然我们在学习TCP挥手时,学到的是需要四次来完成TCP挥手,但是在一些情况下,TCP四次挥手是可以变成TCP次挥手的。
而且在用工具抓包的时候,我们也会常看到TCP挥手过程是次,而不是四次,如下图:
先来回答为什么RFC文档里定义TCP挥手过程是要四次再来回答什么情况下,什么情况会出现次挥手
为什么TCP挥手需要四次TCP四次挥手的过程如下:
具体过程:
客户端主动调用关闭连接的函数,于是就会发送FIN报文,这个FIN报文代表客户端不会再发送数据了,进入FIN_WAIT_1状态;服务端收到了FIN报文,然后马上回复一个ACK确认报文,此时服务端进入CLOSE_WAIT状态。在收到FIN报文的时候,TCP协议栈会为FIN包插入一个文件结束符EOF到接收缓冲区中,服务端应用程序可以通过调用来感知这个FIN包,这个EOF会被放在已排队等候的其他已接收的数据之后,所以必须要得继续接收缓冲区已接收的数据;接着,当服务端在数据的时候,比较后自然就会读到EOF,接着()就会返回0,这时服务端应用程序如果有数据要发送的话,就发完数据后才调用关闭连接的函数,如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,这时服务端就会发一个FIN包,这个FIN报文代表服务端不会再发送数据了,之后处于LAST_ACK状态;客户端接收到服务端的FIN包,并发送ACK确认包给服务端,此时客户端将进入TIME_WAIT状态;服务端收到ACK确认包后,就进入了比较后的CLOSE状态;客户端经过2MSL时间之后,也进入CLOSE状态;你可以看到,每个方向都需要一个FIN和一个ACK,因此通常被称为四次挥手。
为什么TCP挥手需要四次呢服务器收到客户端的FIN报文时,内核会马上回一个ACK应答报文,但是服务端应用程序可能还有数据要发送,所以并不能马上发送FIN报文,而是将发送FIN报文的控制权交给服务端应用程序:
如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数;如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,从上面过程可知,是否要发送第次挥手的控制权不在内核,而是在被动关闭方(上图的服务端)的应用程序,因为应用程序可能还有数据要发送,由应用程序决定什么时候调用关闭连接的函数,当调用了关闭连接的函数,内核就会发送FIN报文了,所以服务端的ACK和FIN一般都会分开发送。
FIN报文一定得调用关闭连接的函数,才会发送吗
不一定。如果进程退出了,不管是不是正常退出,还是异常退出(如进程崩溃),内核都会发送FIN报文,与对方完成四次挥手。
粗暴关闭雅关闭前面介绍TCP四次挥手的时候,并没有详细介绍关闭连接的函数,其关闭的连接的函数有两种函数:
函数,同时关闭发送方向和读取方向,也就是不再有发送和接收数据的能力。如果有多进程多线程共享同一个,如果有一个进程调用了关闭只是让引用计数-1,并不会导致不可用,同时也不会发出FIN报文,其他进程还是可以正常读写该,直到引用计数变为0,才会发出FIN报文。函数,可以指定只关闭发送方向而不关闭读取方向,也就是不再有发送数据的能力,但是还是具有接收数据的能力。如果有多进程多线程共享同一个,则不管引用计数,直接使得该不可用,然后发出FIN报文,如果有别的进程企图使用该,将会受到影响。如果客户端是用函数来关闭连接,那么在TCP四次挥手过程中,如果收到了服务端发送的数据,由于客户端已经不再具有发送和接收数据的能力,所以客户端的内核会回RST报文给服务端,然后内核会释放连接,这时就不会经历完成的TCP四次挥手,所以我们常说,调用是粗暴的关闭。
当服务端收到RST后,内核就会释放连接,当服务端应用程序再次发起读操作或者写操作时,就能感知到连接已经被释放了:
如果是读操作,则会返回RST的报错,也就是我们常见的C。如果是写操作,那么程序会产生SIGPIPE信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。相对的,函数因为可以指定只关闭发送方向而不关闭读取方向,所以即使在TCP四次挥手过程中,如果收到了服务端发送的数据,客户端也是可以正常读取到该数据的,然后就会经历完整的TCP四次挥手,所以我们常说,调用是雅的关闭。
但是注意,函数也可以指定「只关闭读取方向,而不关闭发送方向」,但是这时候内核是不会发送FIN报文的,因为发送FIN报文是意味着我方将不再发送任何数据,而如果指定「不关闭发送方向」,就意味着还有发送数据的能力,所以内核就不会发送FIN。
什么情况会出现次挥手当被动关闭方(上图的服务端)在TCP挥手过程中,「没有数据要发送」并且「开启了TCP延迟确认机制」,那么第二和第次挥手就会合并传输,这样就出现了次挥手。
然后因为TCP延迟确认机制是默认开启的,所以导致我们抓包时,看见次挥手的次数比四次挥手还多。
什么是TCP延迟确认机制
当发送没有携带数据的ACK,它的络效率也是很低的,因为它也有40个字节的IP头和TCP头,但却没有携带数据报文。为了解决ACK传输效率低问题,所以就衍生出了TCP延迟确认。TCP延迟确认的策略:
当有响应数据要发送时,ACK会随着响应数据一起立刻发送给对方当没有响应数据要发送时,ACK将会延迟一段时间,以等待是否有响应数据可以一起发送如果在延迟等待发送ACK期间,对方的第二个数据报文又到达了,这时就会立刻发送ACK
延迟等待的时间是在L内核中定义的,如下图:
关键就需要HZ这个数值大小,HZ是跟系统的时钟频率有关,每个操作系统都不一样,在我的L系统中HZ大小是1000,如下图:
知道了HZ的大小,那么就可以算出:
比较大延迟确认时间是200(10005)比较短延迟确认时间是40(100025)怎么关闭TCP延迟确认机制
如果要关闭TCP延迟确认机制,可以在S设置里启用TCP_QUICKACK,启用TCP_QUICKACK,就相当于关闭TCP延迟确认机制。
1表示开启TCP_QUICKACK,即关闭TCP延迟确认机制=1;(,IPPROTO_TCP,TCP_QUICKACK,(*),());验验证验一
接下来,来给大家做个验,验证这个结论:
当被动关闭方(上图的服务端)在TCP挥手过程中,「没有数据要发送」并且「开启了TCP延迟确认机制」,那么第二和第次挥手就会合并传输,这样就出现了次挥手。
服务端的代码如下,做的事情很简单,就读取数据,然后当返回0的时候,就马上调用关闭连接。因为TCP延迟确认机制是默认开启的,所以不需要特殊设置。
##########MAXLINE1024(,*[]){1创建一个监听=(AF_INET,SOCK_STREAM,0);(0){(,":%\",());-1;}2初始化服务器地址和端口__;(_,(_));__=AF_INET;___=(INADDR_ANY);__=(8888);3绑定地址+端口((,(*)(_),())0){(,":%\",());-1;}("\");4开始监听((,128)){(,":%\\",());(1);}5获取已连接的__;__=(_);=(,(*)_,_);(0){(,":%\\",());(1);}("\");[MAXLINE]={0};(1){6读取客户端发送的数据=(,,MAXLINE);(0){读取错误(,":%\\",());;}(==0){返回0,代表读到FIN报文(,"\");();没有数据要发送,立马关闭连接;}[]=0;("%:%\",,);}();0;}客户端代码如下,做的事情也很简单,与服务端连接成功后,就发送数据给服务端,然后睡眠一秒后,就调用关闭连接,所以客户端是主动关闭方:
########(,*[]){1创建一个监听=(AF_INET,SOCK_STREAM,0);(0){(,":%\",());-1;}2初始化服务器地址和端口__;(_,(_));__=AF_INET;___=_("127001");__=(8888);3连接服务器((,(*)(_),(_))0){(,":%\",());-1;}("\");[64]=",";4发送数据=(,,(),0);(!=()){(,":%\",());-1;}("%\",);(1);5关闭连接();0;}编译服务端和客户端的代码:
先启用服务端:
然后用工具开始抓包,命令如下:
-8888-0-_然后启用客户端,可以看到,与服务端连接成功后,发完数据就退出了。
此时,服务端的输出:
接下来,我们来看看抓包的结果。
可以看到,TCP挥手次数是3次。所以,下面这个结论是没问题的。
结论:当被动关闭方(上图的服务端)在TCP挥手过程中,「没有数据要发送」并且「开启了TCP延迟确认机制(默认会开启)」,那么第二和第次挥手就会合并传输,这样就出现了次挥手。
验二
我们再做一次验,来看看关闭TCP延迟确认机制,会出现四次挥手吗客户端代码保持不变,服务端代码需要增加一点东西。在上面服务端代码中,增加了打开了TCP_QUICKACK(速应答)机制的代码,如下:
编译好服务端代码后,就开始运行服务端和客户端的代码,同时用进行抓包。抓包的结果如下,可以看到是四次挥手。
所以,当被动关闭方(上图的服务端)在TCP挥手过程中,「没有数据要发送」,同时「关闭了TCP延迟确认机制」,那么就会是四次挥手。
设置TCP_QUICKACK的代码,为什么要放在返回0之后
我也是多次验才发现,在之前设置TCP_QUICKACK是不生效的,只有在返回0的时候,设置TCP_QUICKACK才会出现四次挥手。上查了下资料说,设置TCP_QUICKACK并不是长时间的,所以每次读取数据的时候,如果想要立刻回ACK,那就得在每次读取数据之后,重新设置TCP_QUICKACK。而我这里的验,目的是为了当收到客户端的FIN报文(首次挥手)后,立马回ACK报文,所以就在返回0的时候,设置TCP_QUICKACK。当然,际应用中,没人会在我这个位置设置TCP_QUICKACK,因为操作系统都通过TCP延迟确认机制帮我们把四次挥手化成了次挥手了,这本来就是一件好事呀。
总结当被动关闭方在TCP挥手过程中,如果「没有数据要发送」,同时「没有开启TCP_QUICKACK(默认情况就是没有开启,没有开启TCP_QUICKACK,等于就是在使用TCP延迟确认机制)」,那么第二和第次挥手就会合并传输,这样就出现了次挥手。所以,出现次挥手现象,是因为TCP延迟确认机制导致的。 |
|