TCP三次握手四次挥手及time_wait状态解析

TCP的建立——三次握手

TCP三次握手四次挥手及time_wait状态解析
1.服务器必须准备好接受外来的连接。通常通过调用socket,bind,listen这三个函数来完成,我们称之为被动打开(passive open)。
2. 客户端通过调用connect函数发起主动的打开(active open)。这导致客户TCP发送一个SYN(同步)分节,它告诉服务器客户将在(待建立的)连接中发送的数据的初始***。通常SYN分节不携带任何数据,其所在IP数据报只含有一个IP首部,一个TCP首部以及可能有的TCP选项。
3. 服务器必须确认(ACK)客户的SYN,同时自己也发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始***。服务器单个分节中发送SYN和客户SYN的ACK(确认)。(客户端的connect函数返回)
4. 客户必须确认服务器发来的SYN,发送一个ACK,然后服务器的accept函数返回。

这种交换至少需要三个分组,因此称之为TCP的三次握手。上图展示了所交换的三个分节。

SYN、ACK的***

上图给出的客户初始***为J,服务器的初始***为K。ACK中的确认号发送这个ACK一端所期待的下一个***。因为SYN占据一个字节的***空间,所以每一个SYN的ACK确认号就是SYN的初始***加1。类似的下文中断开连接的FIN的ACK也是FIN的***加1。


TCP断开——四次挥手

TCP三次握手四次挥手及time_wait状态解析

TCP终止一个连接需要四个分节。
1.某个应用进程首先调用close,我们称之为该端执行主动关闭(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
2.接收到这个FIN的对端执行被动关闭(passive close)。这个FIN由TCP确认(发送ACK进行确认)。它的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程(放在已排队等候应用进程接收的任何其它数据之后),因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
3.一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
4.接收这个最终FIN的原发送端TCP(即主动关闭的那一端)确认这个FIN(发送ACK进行确认)。

既然每个方向都需要一个FIN和一个ACK,因此通常需要四个分节,所以我们称他为四次挥手。

在步骤2与步骤3之间,执行被动关闭的一端到执行主动关闭一端数据流动是可能的。这称为半关闭,等我随后学习再来讲解

当套接字关闭时,其所在端TCP各自发送了一个FIN。我们在图中指出,这是应用进程调用close而发生的,不过需要认识到,当一个unix进程无论自愿的(调用exit或从main函数返回)还是非自愿的(收到一个终止信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。

还有需要注意的是,上图展示了客户端主动关闭的情况,需要指出的是,无论服务器还是客户端都可以主动关闭。但通常情况都是客户端执行主动关闭,但是某些协议(如HTTP/1.0)是由服务器主动关闭


time_wait

1. 首先什么是time_wait

TCP三次握手四次挥手及time_wait状态解析
如上图,在服务器端发送一个FIN时,客户端会处于time_wait状态。当处于time_wait状态时,我们无法创建新的连接,因为端口被占用。

2. time_wait有什么作用

(1)可靠的终止TCP连接。
若处于time_wait的客户端发送给服务器确认报文段丢失的话,服务器将在此重新发送FIN报文段,那么客户端必须处于一个可接收的状态就是time_wait状态而不是close状态。
(2)保证让迟来的TCP报文段有足够的时间识别并丢弃
linux中一个TCP端口不能被打开两次或两次以上,当客户端处于time_wait状态时我们将无法使用此端口建立新连接,如果不存在time_wait状态,新连接可能会收到就连接的数据。

为什么在time_wait中就可以保证旧数据完全被销毁?

因为网络中数据存在时间最大为MSL(maxinum segment lifetime),而time_wait 持续时间为2MSL所以保证网络中的数据可以丢弃。

3一定是客户端才有time_wait状态吗?

不一定。
 当客户端进行主动关闭时,time_wait存在于客户端,但是当服务器执行主动关闭或者发生异常时,会产生在服务器,所以当服务器异常断开时,你可能需要等待一会才能重启服务器,如果你立即重启服务器,终端会提醒你端口被占用。

TCP三次握手四次挥手及time_wait状态解析
上图是我们启动服务器正常运转,当我的服务器发生异常时,time_wait就存在于服务器,然后我立即重启服务器,则bind函数会返回,如下图所示,端口被占用。
TCP三次握手四次挥手及time_wait状态解析

如何避免time_wait状态占用资源

如果是客户端,我们一般不需要担心,因为客户端一般选择的都是临时端口,再次创建新的连接会分配未被占用的端口。除非客户端指定使用某一个端口,但是不需要这么做。
如果在服务器主动关闭后异常终止,因为服务器使用的是指定的服务器端口,所以time_wait状态将导致它不能重启,需要等待一段时间,但在大型服务器中,会造成巨额的损失。
如何避免这种情况的发生呢? 
我们可以使用socket的选项SO_REUSEADDR来强制进程立即使用time_wait状态的连接占用端口。通过setsockopt设置后,即使服务器处于time_wait状态,与之绑定的socket地址也可以立即被重启使用。

setsockopt的用法

1.closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想重用该socket。

BOOL BReuseaddr=True

setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&BReuseaddr, sizeof(BOOL));

TCP三次握手四次挥手及time_wait状态解析
当我在我的服务器中设置好之后,我异常关闭服务器再立即重启就不会显示端口被占用,而是立即可以重启。