什么是TCP协议
TCP协议即Transmission Control Protocol传输控制协议,主要用于提供可靠的通信传输,它是面向连接的、可靠的流协议。它的特点是具有顺序控制或重发控制机制、具备流量控制、拥塞控制、提高网络利用率。
TCP协议的特点及实现
TCP如何提高可靠性
TCP通信中通过返回确认应答来提高可靠性。当发送端的数据到达接收主机时,接收主机需要返回确认的ACK(Positive Acknowledgement)来确认。
如果接收主机没有返回ACK或者等待时间过长,说明数据很可能丢失。此时发送端会认为数据丢失并进行重发,因此即使存在丢包,仍然会保证数据继续发送直至抵达目的主机。
不过由于没有ACK确认也可能是因为接收主机在返回ACK的途中丢失,此时发送端会重复发送同样内容,导致接收主机反复接收相同的数据。
此外,也有一些原因导致ACK延迟抵达,在发送端开始重试之后才收到确认应答。每次通信中会包含序列号,序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号,接收端根据TCP首部中的序列号和数据的长度,确认下一次通信应该接收的序号作为确认应答返回。通过序列号和确认应答号,TCP可以实现可靠传输。
超时重发
TCP的超时重发不是固定的等待时间间隔的。由于网络环境的影响,需要确认一个合适的等待时间进行重发。因此,TCP在每次通信中都会记录往返花费的时间(RTT,Round Trip Time)以及其偏差(波动值、方差,也称抖动)。通过往返时间和偏差,重发的超时时间就是比这个的总和稍大的数值。
在BSD的Unix以及Windows中,超时都以0.5秒为单位进行控制。由于最初的数据包不知往返时间,一般设置为6秒左右。
在重发后仍收不到ACK,则等待时间会以2倍、4倍的指数函数延长。达到一定次数以后会判定异常关闭连接。
连接管理
TCP通过三次握手建立连接,通过四次挥手断开连接。
三次握手
- 客户端发送SYN包请求建立连接
- 服务端返回ACK确认与客户端建立连接,同时发送SYN包请求建立连接
- 客户端发送ACK包确认与服务端建立连接
三次握手实例
协议3-4部分详细说明了建立连接所要进行的三次握手细节。
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED
Basic 3-Way Handshake for Connection Synchronization
基础的三次握手:
2. A发送序列号为100的SYN包给B
3. B发送对序列号100的确认应答ACK,期望收到A序列号101的包,同时发送SYN包
4. A发送对序列号300的ACK包(301),此处包含的是空segment
5. A发送数据包,此时SEQ与4一样,因为4没有占用SEQ的空间
TCP A TCP B
1. CLOSED CLOSED
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. SYN-RECEIVED <-- <SEQ=300><CTL=SYN> <-- SYN-SENT
4. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
5. SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...
6. ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
7. ... <SEQ=101><ACK=301><CTL=ACK> --> ESTABLISHED
Simultaneous Connection Synchronization
双方同时发起的握手请求:
按照上面的回话过程,会产生混淆,两端建立的TCP连接实际并不同步,而是各自对应序列号100、300的连接。
为了解决这个问题,当收到的ACK与发送出去的SYC不对应时,一个特殊的控制信号就会被发出:reset。
当TCP处于非同步状态(如SYN-SENT、SYN-RECEIVED)收到reset时,它会转为LISTEN状态。如果TCP处于同步状态(如ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT),它会终止连接并通知用户。
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> <SEQ=100><CTL=SYN> ...
3. (duplicate) ... <SEQ=90><CTL=SYN> --> SYN-RECEIVED
4. SYN-SENT <-- <SEQ=300><ACK=91><CTL=SYN,ACK> <-- SYN-RECEIVED
5. SYN-SENT --> <SEQ=91><CTL=RST> --> LISTEN
6. ... <SEQ=100><CTL=SYN> --> SYN-RECEIVED
7. SYN-SENT <-- <SEQ=400><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
8. ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK> --> ESTABLISHED
Recovery from Old Duplicate SYN
从重复SYN信息中恢复正常连接:
3.A发送了2个SYN包给B,序列号依次为90,100。此时B接收到了90,处于SYN-RECEIVED状态
4.B ACK了序列号为90的SYN请求,此时A收到的ACK与发送的SYN(100)不对应,因此发送RST,要求reset
5.B收到RST后处于LISTEN状态
6.此时B收到了A序列号为100的SYN请求
7.B对序列号100的SYN请求进行ACK
8.A收到正确的ACK,建立连接,并发送ACK给B,B建立连接
当TCP连接处于半开状态(half-open)时,也就是任意一方终断连接而另一方不知道,或者两方处于不同步状态时,如果尝试通信,将会导致RST的发送。
当一方的TCP连接Crash后,如A此时打算重新开始,或者从上一次的某点继续发送数据,A就会重新建立连接,或者向它认为还没关闭的(上一次的)连接发送数据。后一种情况中A会从自己的TCP连接中收到“connection not open”的信息。前一种情况中A发送建立连接的SYN,此时B认为之前的连接依然有效:
TCP A TCP B
1. (CRASH) (send 300,receive 100)
2. CLOSED ESTABLISHED
3. SYN-SENT --> <SEQ=400><CTL=SYN> --> (??)
4. (!!) <-- <SEQ=300><ACK=100><CTL=ACK> <-- ESTABLISHED
5. SYN-SENT --> <SEQ=100><CTL=RST> --> (Abort!!)
6. SYN-SENT CLOSED
7. SYN-SENT --> <SEQ=400><CTL=SYN> -->
Half-Open Connection Discovery
4.B返回ACK=100,与A发送的序列号400的SYN不对应
5.因此A会发送RST,B收到RST后会关闭原连接(从同步态转入CLOSED,而非LISTEN)
TCP A TCP B
1. (CRASH) (send 300,receive 100)
2. (??) <-- <SEQ=300><ACK=100><DATA=10><CTL=ACK> <-- ESTABLISHED
3. --> <SEQ=100><CTL=RST> --> (ABORT!!)
Active Side Causes Half-Open Connection Discovery
在A崩溃恢复后,此时A已经不再持有原连接,B仍以为连接生效,尝试向A发送数据。此时A时不会接收数据的因为连接并不存在,因此向B发送RST终止连接。
四次挥手
- 客户端发送FIN包请求断开连接
- 服务端发送ACK包应答,确认断开连接
- 服务端发送FIN包请求断开连接
- 客户端发送ACK包应答,确认断开连接
四次挥手实例
四次挥手可以简单分为几种情况:
- 发送端提出FIN
发送方发出FIN报文后进入FIN-WAIT-1状态,不再发送数据,等待对方确认,此时仍然可以接收数据。收到ACK后进入FIN-WAIT-2状态,此时等待对方将剩余数据发送完毕后发送FIN报文。当收到对方FIN报文后,进入TIME-WAIT状态,发送ACK确认对方报文,等待2MSL时间后关闭连接。 - 接收端收到FIN
接收方接到FIN报文后,发送ACK进行确认,同时将buffer区域的剩余数据发送完毕后发出FIN报文请求关闭连接。当接收到对方的ACK报文后,关闭连接。如果ACK一直没有到达,在超时后会告知用户关闭连接。 - 双方同时提出FIN
此时双方交换FIN报文,并且都互相ACK对方报文,当收到对方ACK后,进入TIME-WAIT状态,在2MSL后关闭连接。
TCP A TCP B
1. ESTABLISHED ESTABLISHED
2. (Close)
FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> --> CLOSE-WAIT
3. FIN-WAIT-2 <-- <SEQ=300><ACK=101><CTL=ACK> <-- CLOSE-WAIT
4. (Close)
TIME-WAIT <-- <SEQ=300><ACK=101><CTL=FIN,ACK> <-- LAST-ACK
5. TIME-WAIT --> <SEQ=101><ACK=301><CTL=ACK> --> CLOSED
6. (2 MSL)
CLOSED
Normal Close Sequence
TCP A TCP B
1. ESTABLISHED ESTABLISHED
2. (Close) (Close)
FIN-WAIT-1 --> <SEQ=100><ACK=300><CTL=FIN,ACK> ... FIN-WAIT-1
<-- <SEQ=300><ACK=100><CTL=FIN,ACK> <--
... <SEQ=100><ACK=300><CTL=FIN,ACK> -->
3. CLOSING --> <SEQ=101><ACK=301><CTL=ACK> ... CLOSING
<-- <SEQ=301><ACK=101><CTL=ACK> <--
... <SEQ=101><ACK=301><CTL=ACK> -->
4. TIME-WAIT TIME-WAIT
(2 MSL) (2 MSL)
CLOSED CLOSED
Simultaneous Close Sequence
TCP分段发送数据
TCP中的包的长度最理想的情况(最大消息长度:MSS,Maximum Segment Size)应该是正好在IP中部倍分片处理的最大数据长度。TCP在传送数据时会以MSS为单位进行分割,在重发中也是以MSS为单位。
MSS的确定是通过在三次握手过程中,客户端与服务端都会在TCP首部中写入MSS选项,取两者中较小值作为实际使用的MSS值。此时握手过程中的TCP包长度+4,以包含MSS信息。
利用窗口控制提高速度
为了解决对每次通信的包均进行确认应答带来的性能影响,TCP引入窗口概念,确认应答由每段MSS改为对数段进行应答。
发送端无需等待接收端的ACK即可继续发送下一段报文,如:
假设MSS=1000,发送端发送1-1000、1001-2000、2001-3000、3001-4000给接收端,当接收端确认这4端通信后再继续发送后续数据。
此时窗口大小为4段(MSS)。
在整给窗口的确认应答没有到达之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。
在收到确认应答后,将窗口滑动至确认应答中的序列号位置,这种机制也被称为滑动窗口控制
滑动窗口与重发控制
在滑动窗口中不可避免也会有丢包问题,以上面例子为例,分为以下几种情况:
- 接收端接收成功但其中一段ACK报文丢失
此时如果中间的确认应答丢失,由于对于接收端的视角来说,全部报文都正常接收,因此会正常相应给发送端,因此发送确认报文:下一个是1001,下一个是2001(丢失),下一个是3001,下一个是4001。对于发送端而言,无需理会中途是否丢失ACK报文,只要收到后续的ACK,即可认为前面的报文已经成功传达。
- 其中一段报文没有抵达接收端
此时假定丢失了第二段报文,那么接收端由于报文缺失,会在ACK中告知发送端:下一个是1001,下一个是1001,下一个是1001。如果发送端连续3次收到同一个ACK应答,就会对此段数据进行重发。当丢失的报文接收成功时,对于接收端而言,目前1-4000的报文均已接收到,因此返回的ACK将会是:下一个是4001。
为什么是3次才进行重发,而不是2次或者4次?
由于发送端在窗口限度内发送了多段报文,当接收端返回ACK 1001时,发送端并不知道接收端请求1001是因为1001-2000段报文丢失,还是说1001-2000段报文因为一些原因在接收端接收顺序异常(例如最后才接收到此段报文,那么前面接收的报文都会返回ACK 1001)。当第一次接收到重复的 ACK 1001时,发送端会假设这是因为报文顺序被打乱造成的,因此会选择继续等待看是否会再有重复报文发来,如果是因为乱序,那么下面几次后应该会接收到新的ACK,因为缺失的报文已经被补全。但若接收到连续3段或更多的重复ACK报文,则很有可能该段ACK请求的报文在途中丢失,此时TCP会选择重发此段报文。这种机制被称为高速重发控制(Fast Retransmission)
http://www.faqs.org/rfcs/rfc2001.html
3. Fast Retransmit
Modifications to the congestion avoidance algorithm were proposed in 1990 [3]. Before describing the change, realize that TCP may generate an immediate acknowledgment (a duplicate ACK) when an out- of-order segment is received (Section 4.2.2.21 of [1], with a note that one reason for doing so was for the experimental fast- retransmit algorithm). This duplicate ACK should not be delayed. The purpose of this duplicate ACK is to let the other end know that a segment was received out of order, and to tell it what sequence number is expected.
Since TCP does not know whether a duplicate ACK is caused by a lost segment or just a reordering of segments, it waits for a small number of duplicate ACKs to be received. It is assumed that if there is just a reordering of the segments, there will be only one or two duplicate ACKs before the reordered segment is processed, which will then generate a new ACK. If three or more duplicate ACKs are received in a row, it is a strong indication that a segment has been lost. TCP then performs a retransmission of what appears to be the missing segment, without waiting for a retransmission timer to expire.
流控制
接收端通过告知发送端自己可以接收的数据大小,发送端会发送不超过这个限度的数据。这个大小限度称为窗口大小。
TCP首部中有对应字段存放窗口大小。
当缓冲区面临数据溢出时,窗口大小会被设置成一个更小的值通知发送端。发送端根据指示对发送数据量进行控制。这也就形成了一个完整的TCP流控制(流量控制)。
当窗口大小为0时,即缓冲区满,下一次发送端在等待重发超时的时间以后(因为即使上一个包窗口为0,仍有可能处理完毕后在下一个包返回窗口大小的更新),会发送一个窗口探测的包(仅含一个字节),如发送4001-4001作为窗口探测。此时假设缓冲区满,继续返回0。发送端通过不断重试探测包,当接收端返回窗口大小不为0时,就会继续按照窗口大小正常发送数据包。
拥堵控制
为了避免网络阻塞以及启动时大量发送数据,TCP中定义了一个拥塞窗口的概念。
慢启动时,拥塞窗口大小设置为1个MSS大小,每收到一次ACK,拥塞窗口大小翻倍。
发送数据包时,数据包长度为拥塞窗口大小及接收端通知的窗口大小中的较小值。
当发生超时重发时,拥塞窗口重置为1,进行慢启动修正,减少了连续发包导致的网络拥堵。
由于随着确认应答,拥塞窗口大小指数增长,因此需要引入慢启动阈值,当窗口值超过阈值时,后续ACK只允许窗口以下面比例放大:MSS * MSS / 窗口大小
。
当窗口大至一定程度时,每次放大的比例甚至会小于一个MSS长度。
在初始化TCP通信时,阈值没有设定,只有当发生超时重发时,阈值才会被设定为拥塞窗口的一半大小。而又因为高速重发控制,在发生高速重发时会将窗口大小设置为当前窗口一半,并将阈值设置为窗口大小+3MSS。