活久见!TCP两次挥手,你见过吗?那四次握手呢?
我们都知道,TCP是个面向连接的、可靠的、基于字节流的传输层通信协议。

那这里面提到的"面向连接",意味着需要 建立连接,使用连接,释放连接。
建立连接是指我们熟知的TCP三次握手。
而使用连接,则是通过一发送、一确认的形式,进行数据传输。
还有就是释放连接,也就是我们常见的TCP四次挥手。
TCP四次挥手大家应该比较了解了,但大家见过三次挥手吗?还有两次挥手呢?
都见过?那四次握手呢?
今天这个话题,不想只是猎奇,也不想搞冷知识。
我们从四次挥手开始说起,搞点实用的知识点。
TCP四次挥手
简单回顾下TCP四次挥手。

正常情况下。只要数据传输完了,不管是客户端还是服务端,都可以主动发起四次挥手,释放连接。
就跟上图画的一样,假设,这次四次挥手是由客户端主动发起的,那它就是主动方。服务器是被动接收客户端的挥手请求的,叫被动方。
客户端和服务器,一开始,都是处于ESTABLISHED状态。
第一次挥手:一般情况下,主动方执行close()或 shutdown()方法,会发个FIN报文出来,表示"我不再发送数据了"。
第二次挥手:在收到主动方的FIN报文后,被动方立马回应一个ACK,意思是"我收到你的FIN了,也知道你不再发数据了"。
上面提到的是主动方不再发送数据了。但如果这时候,被动方还有数据要发,那就继续发。注意,虽然第二次和第三次挥手之间,被动方是能发数据到主动方的,但主动方能不能正常收就不一定了,这个待会说。
第三次挥手:在被动方在感知到第二次挥手之后,会做了一系列的收尾工作,最后也调用一个 close(), 这时候就会发出第三次挥手的 FIN-ACK。
第四次挥手:主动方回一个ACK,意思是收到了。
其中第一次挥手和第三次挥手,都是我们在应用程序中主动触发的(比如调用close()方法),也就是我们平时写代码需要关注的地方。
第二和第四次挥手,都是内核协议栈自动帮我们完成的,我们写代码的时候碰不到这地方,因此也不需要太关心。
另外不管是主动还是被动,每方发出了一个 FIN 和一个ACK 。也收到了一个 FIN 和一个ACK 。这一点大家关注下,待会还会提到。
FIN一定要程序执行close()或shutdown()才能发出吗?
不一定。一般情况下,通过对socket执行 close() 或 shutdown() 方法会发出FIN。但实际上,只要应用程序退出,不管是主动退出,还是被动退出(因为一些莫名其妙的原因被kill了), 都会发出 FIN。
FIN 是指"我不再发送数据",因此
shutdown()关闭读不会给对方发FIN, 关闭写才会发FIN。
如果机器上FIN-WAIT-2状态特别多,是为什么
根据上面的四次挥手图,可以看出,FIN-WAIT-2是主动方那边的状态。
处于这个状态的程序,一直在等第三次挥手的FIN。而第三次挥手需要由被动方在代码里执行close() 发出。
因此当机器上FIN-WAIT-2状态特别多,那一般来说,另外一台机器上会有大量的 CLOSE_WAIT。需要检查有大量的 CLOSE_WAIT的那台机器,为什么迟迟不愿调用close()关闭连接。
所以,如果机器上FIN-WAIT-2状态特别多,一般是因为对端一直不执行close()方法发出第三次挥手。

主动方在close之后收到的数据,会怎么处理
之前写的一篇文章《代码执行send成功后,数据就发出去了吗?》中,从源码的角度提到了,一般情况下,程序主动执行close()的时候;
如果当前连接对应的
socket的接收缓冲区有数据,会发RST。如果发送缓冲区有数据,那会等待发送完,再发第一次挥手的
FIN。
大家知道,TCP是全双工通信,意思是发送数据的同时,还可以接收数据。
Close()的含义是,此时要同时关闭发送和接收消息的功能。
也就是说,虽然理论上,第二次和第三次挥手之间,被动方是可以传数据给主动方的。
但如果 主动方的四次挥手是通过 close() 触发的,那主动方是不会去收这个消息的。而且还会回一个 RST。直接结束掉这次连接。

第二第三次挥手之间,不能传输数据吗?
也不是。前面提到Close()的含义是,要同时关闭发送和接收消息的功能。
那如果能做到只关闭发送消息,不关闭接收消息的功能,那就能继续收消息了。这种 half-close 的功能,通过调用shutdown() 方法就能做到。
int shutdown(int sock, int howto);
其中 howto 为断开方式。有以下取值:
SHUT_RD:关闭读。这时应用层不应该再尝试接收数据,内核协议栈中就算接收缓冲区收到数据也会被丢弃。
SHUT_WR:关闭写。如果发送缓冲区中还有数据没发,会将将数据传递到目标主机。
SHUT_RDWR:关闭读和写。相当于
close()了。

怎么知道对端socket执行了close还是shutdown
不管主动关闭方调用的是close()还是shutdown(),对于被动方来说,收到的就只有一个FIN。
被动关闭方就懵了,"我怎么知道对方让不让我继续发数据?"

其实,大可不必纠结,该发就发。
第二次挥手和第三次挥手之间,如果被动关闭方想发数据,那么在代码层面上,就是执行了 send() 方法。
int send( SOCKET s,const char* buf,int len,int flags);
send() 会把数据拷贝到本机的发送缓冲区。如果发送缓冲区没出问题,都能拷贝进去,所以正常情况下,send()一般都会返回成功。

然后被动方内核协议栈会把数据发给主动关闭方。
如果上一次主动关闭方调用的是
shutdown(socket_fd, SHUT_WR)。那此时,主动关闭方不再发送消息,但能接收被动方的消息,一切如常,皆大欢喜。如果上一次主动关闭方调用的是
close()。那主动方在收到被动方的数据后会直接丢弃,然后回一个RST。
针对第二种情况。
被动方内核协议栈收到了RST,会把连接关闭。但内核连接关闭了,应用层也不知道(除非被通知)。
此时被动方应用层接下来的操作,无非就是读或写。
如果是读,则会返回
RST的报错,也就是我们常见的Connection reset by peer。如果是写,那么程序会产生
SIGPIPE信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。
总结一下,当被动关闭方 recv() 返回EOF时,说明主动方通过 close()或 shutdown(fd, SHUT_WR) 发起了第一次挥手。
如果此时被动方执行两次 send()。
第一次
send(), 一般会成功返回。第二次
send()时。如果主动方是通过shutdown(fd, SHUT_WR)发起的第一次挥手,那此时send()还是会成功。如果主动方通过close()发起的第一次挥手,那此时会产生SIGPIPE信号,进程默认会终止,异常退出。不想异常退出的话,记得捕获处理这个信号。
如果被动方一直不发第三次挥手,会怎么样
第三次挥手,是由被动方主动触发的,比如调用close()。
如果由于代码错误或者其他一些原因,被动方就是不执行第三次挥手。
这时候,主动方会根据自身第一次挥手的时候用的是 close() 还是 shutdown(fd, SHUT_WR) ,有不同的行为表现。
如果是
shutdown(fd, SHUT_WR),说明主动方其实只关闭了写,但还可以读,此时会一直处于FIN-WAIT-2, 死等被动方的第三次挥手。如果是
close(), 说明主动方读写都关闭了,这时候会处于FIN-WAIT-2一段时间,这个时间由net.ipv4.tcp_fin_timeout控制,一般是60s,这个值正好跟2MSL一样 。超过这段时间之后,状态不会变成 `TIME-WAIT`,而是直接变成`CLOSED`。
# cat /proc/sys/net/ipv4/tcp_fin_timeout 60

TCP三次挥手
四次挥手聊完了,那有没有可能出现三次挥手?
是可能的。
我们知道,TCP四次挥手里,第二次和第三次挥手之间,是有可能有数据传输的。第三次挥手的目的是为了告诉主动方,"被动方没有数据要发了"。
所以,在第一次挥手之后,如果被动方没有数据要发给主动方。第二和第三次挥手是有可能合并传输的。这样就出现了三次挥手。

如果有数据要发,就不能是三次挥手了吗
上面提到的是没有数据要发的情况,如果第二、第三次挥手之间有数据要发,就不可能变成三次挥手了吗?
并不是。TCP中还有个特性叫延迟确认。可以简单理解为:接收方收到数据以后不需要立刻马上回复ACK确认包。
在此基础上,不是每一次发送数据包都能对应收到一个 ACK 确认包,因为接收方可以合并确认。
而这个合并确认,放在四次挥手里,可以把第二次挥手、第三次挥手,以及他们之间的数据传输都合并在一起发送。因此也就出现了三次挥手。

TCP两次挥手
前面在四次挥手中提到,关闭的时候双方都发出了一个FIN和收到了一个ACK。
正常情况下TCP连接的两端,是不同IP+端口的进程。
但如果TCP连接的两端,IP+端口是一样的情况下,那么在关闭连接的时候,也同样做到了一端发出了一个FIN,也收到了一个 ACK,只不过正好这两端其实是同一个socket 。

而这种两端