TCP

在 TCP 通讯中主要有连接的建立数据的传输连接的关闭三个过程!每个过程完成不同的工作,而且序列号和确认号在每个过程中的变化都是不同的。

TCP 建立连接

TCP 建立连接,也就是我们常说的三次握手,它需要三步完成。在 TCP 的三次握手中,发送第一个 SYN 的一端执行的是主动打开。而接收这个 SYN 并发回下一个 SYN 的另一端执行的是被动打开。

SYN:同步序列编号(Synchronize Sequence Numbers)。SYN用于建立TCP/IP时的握手连接,由客户端想服务端发送SYN,服务端向客户端发送SYN+ACK响应报文,客户端向服务端发送一个ACK响应报文,然后建立一个完整的连接,即三次握手。

这里以客户端向服务器发起连接来说明。
1.客户端向服务器发送一个同步数据包请求建立连接,该数据包中,初始序列号(ISN)是客户端随机产生的一个值,确认号是 0;

初始化序号(Initial Sequence Number),每次建立连接前重新初始化一个序列号,为了通信双方能够根据序号将不属于本连接的报文段丢弃。

确认号(acknowledgement number),指下一次期望收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。

2.服务器收到这个同步请求数据包后,会对客户端进行一个同步确认。这个数据包中,序列号(ISN)是服务器随机产生的一个值,确认号是客户端的初始序列号+1

3.客户端收到这个同步确认数据包后,再对服务器进行一个确认。该数据包中,序列号是上一个同步请求数据包中的确认号值,确认号是服务器的初始序列号+1。

初始序列号(ISN)随时间而变化的,而且不同的操作系统也会有不同的实现方式,所以每个连接的初始序列号是不同的。 TCP 连接两端会在建立连接时,交互一些信息,如窗口大小、 MSS 等,以便为接着的数据传输做准备。
TCPConnect

窗口字段

用于接收端通知发送端:接收端当前能够接收的字节数(即当前允许发送端发送的字节数)。接收端通告的窗口大小变成0,发送端会发一个1字节的段(就是下一字节的数据,没新的数据段发送的时候发一个ack)(TCP零窗口探测),强制接收端重新宣告下一个期望的字节和窗口大小。如果接收方回复窗口大小仍然为零,则发送方的探测定时器加倍。没有收到ACK时,发送探测包的最大次数之后连接超时。

TCP 传输数据

在 TCP 建立连接后,就可以开始传输数据了。 TCP 工作在全双工模式,它可以同时进行双向数据传输。这里为了简化,我们只谈服务器向客户端发送数据的情况,而客户端向服务器发送数据的原理和它是类似的,这里便不重复说明。
服务器向客户端发送一个数据包后,客户端收到这个数据包后,会向服务器发送一个确认数据包
1.发送数据:服务器向客户端发送一个带有数据的数据包,该数据包中的序列号和确认号与建立连接第三步的数据包中的序列号和确认号相同;
2.确认收到:客户端收到该数据包,向服务器发送一个确认数据包,该数据包中,序列号是为上一个数据包中的确认号值,而确认号为服务器发送的上一个数据包中的序列号+所该数据包中所带数据的大小。
数据分段中的序列号可以保证所有传输的数据按照正常的次序进行重组,而且通过确认保证数据传输的完整性
115A462I-0

TCP 关闭连接

前面我们提到,建立一个连接需要 3 个步骤,但是关闭一个连接需要经过 4 个步骤。因为TCP 连接是全双工的工作模式,所以每个方向上需要单独关闭。在 TCP 关闭连接时,首先关闭的一方(即发送第一个终止数据包的)将执行主动关闭,而另一方(收到这个终止数据包的)再执行被动关闭。
关闭连接的 4 个步骤如下:
1.服务器完成它的数据发送任务后,会主动向客户端发送一个终止数据包,以关闭在这个方向上的 TCP 连接。该数据包中,序列号为客户端发送的上一个数据包中的确认号值,而确认号为服务器发送的上一个数据包中的序列号+该数据包所带的数据的大小;
2.客户端收到服务器发送的终止数据包后,将对服务器发送确认信息,以关闭该方向上的 TCP 连接。这时的数据包中,序列号为第 1 步中的确认号值,而确认号为第 1 步的数据包中的序列号+1
3.同理,客户端完成它的数据发送任务后,就也会向服务器发送一个终止数据包,以关闭在这个方向上的 TCP 连接,该数据包中,序列号为服务器发送的上一个数据包中的确认号值,而确认号为客户端发送的上一个数据包中的序列号+该数据包所带数据的大小;
4.服务器收到客户端发送的终止数据包后,将对客户端发送确认信息,以关闭该方向上的 TCP 连接。这时在数据包中,序列号为第 3 步中的确认号值,而确认号为第 3 步数据包中的序列号+1;
281600391879667

因为 FIN 和 SYN 一样,也要占一个序号。理论上服务器在 TCP 连接关闭时发送的终止数据包中,只有终止位是置 1,然后客户端进行确认。但是在实际的 TCP 实现中,在终止数据包中,确认位和终止位是同时置为 1 的,确认位置为 1 表示对最后一次传输的数据进行确认,终止位置为 1 表示关闭该方向的 TCP 连接

为什么需要TIME_WAIT?

TIME_WAIT是TCP协议用以保证被重新分配的socket不会受到之前残留的延迟重发报文影响的机制,是必要的逻辑保证。
描述过程:
Client调用close()函数,给Server发送FIN,请求关闭连接;Server收到FIN之后给Client返回确认ACK,同时关闭读通道(不清楚就去看一下shutdown和close的差别),也就是说现在不能再从这个连接上读取东西,现在read返回0。此时Server的TCP状态转化为CLOSE_WAIT状态。
Client收到对自己的FIN确认后,关闭 写通道,不再向连接中写入任何数据。
接下来Server调用close()来关闭连接,给Client发送FIN,Client收到后给Server回复ACK确认,同时Client关闭读通道,进入TIME_WAIT状态。
Server接收到Client对自己的FIN的确认ACK,关闭写通道,TCP连接转化为CLOSED,也就是关闭连接。
Client在TIME_WAIT状态下要等待最大数据段生存期的两倍,然后才进入CLOSED状态,TCP协议关闭连接过程彻底结束。

以上就是TCP协议关闭连接的过程,现在说一下TIME_WAIT状态。
从上面可以看到,主动发起关闭连接的操作的一方将达到TIME_WAIT状态,而且这个状态要保持Maximum Segment Lifetime的两倍时间。

原因有二:
一、保证TCP协议的全双工连接能够可靠关闭
二、保证这次连接的重复数据段从网络中消失

先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。

再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

实现