TCP

573 阅读22分钟

简介

TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。在因特网协议族中,TCP层是位于IP层之上,应用层之下的中间层。应用层向 TCP 层发送数据,然后 TCP 把数据流分割成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元(MTU)的限制)。

TCP数据格式

TCP报文格式.png 源/目的端口

计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通信。源端口、目标端口是各占2个字节,可推算计算机的端口个数为65535个。客户端的源端口是临时开启的随机端口。

序号

占4个字节,表示本报文段所发送数据的第一个字节的编号。

确认号

占4个字节,期望收到的数据的开始序列号。也即已经收到的数据的字节长度加1。

数据偏移

占4位,它指出TCP报文的首部长度(计算出的数据段开始地址的偏移值)。最大值:(2^4-1)*4=60

保留

占6位,保留今后使用,但目前应都为0

紧急指针URG

占一位,当URG=1时,表示高优先级数据包,紧急指针字段有效。

确认ACK

占一位,当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1

推送PSH

占一位,当PSH=1时,表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。

复位RST

占一位,当RST=1,表示出现严重差错。可能需要重新创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。

同步SYN

占一位,当SYN=1,ACK=0,表示这是连接请求,用于创建连接和使顺序号同步,若同意连接,则响应报文中应该使SYN=1,ACK=1

终止FIN

占一位,当FIN=1,表示发送方没有数据要传输了,要求释放连接。

窗口

占2字节,用以告知对方下一次允许发送的数据大小(字节为单位)。该字段可以用于 TCP 的流量控制。

检验和

占2字节,对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段,用来检验数据是否有错误,在发送和接收时都要计算校验和。

紧急指针

占2字节,本报文段中的紧急数据的最后一个字节的序号

选项

选项字段—最多40字节。每个选项的开始是1字节的kind字段说明选项的类型,1字节的Length字段,说明选项的类型占的字节数。

Kind=0:选项表结束

Kind=1:无操作用于选项字段之间的字边界对齐。

Kind=2:最大报文段长度(Length = 4字节,Maximum Segment Size,MSS)通常在创建连接而设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据报的长度就不会超过MTU(MTU最大长度为1518字节,最短为64字节),从而避免本机发生IP分片。只能出现在同步报文段中,否则将被忽略。

Kind=3:窗口扩大因子(Length = 3字节,wscale),取值0-14。用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。

Kind=4:sackOK(Length = 2字节)发送端支持并同意使用SACK选项。

Kind=5:SACK(Length = 可变)选择性确认选项。

Left Edge:占4字节,左边界
Right Edge:占4字节,右边界
一对边界信息需要占用8字节,由于TCP首部的选项部分最多40字节,
所以,SACK选项最多携带4组边界信息
ACK选项的最大占用字节数 = 4 * 8 + 2 = 34

Kind=8:时间戳(10字节,TCP Timestamps Option,TSopt)

发送端的时间戳(Timestamp Value field,TSval,4字节)
时间戳回显应答(Timestamp Echo Reply field,TSecr,4字节)

3次握手建立连接

TCP07_建立连接.png

  1. 客户端A向服务器B(LISTEN状态)发出连接请求(SYN)报文,报文首部中的同部位SYN=1,同时携带客户端为该连接而设定的随机初始序列号x(即:seq=x),此时,客户端A进程进入了 SYN-SENT(同步已发送)状态。TCP规定,任何SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗一个序号。

  2. 服务器B收到请求报文后,如果同意连接,则发出确认(SYN+ACK)报文。确认报文中应该SYN=1,ACK=1,确认号是ack=x+1,同时也要携带一个随机产生的序号y(即:seq=y),此时,服务器B进程进入了SYN-RCVD(同步收到)状态。该报文也不能携带数据,但是同样要消耗一个序号。

  3. 客户端A进程收到确认(SYN+ACK包)后,还要向服务器B给出确认(ACK包)。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端A 进入ESTABLISHED(已建立连接)状态。当服务器B收到客户端A的确认后也进入ESTABLISHED(已建立连接)状态。

“三次握手”的目的是在不可靠的信道上创建可信通信连接,需要双方通知对方自己的分组的序列号的开始数值。

一旦三向握手完成,应用程序数据就可以开始在客户端和服务器之间流动。客户端可以在 ACK 包之后立即发送数据包,服务器必须等待 ACK 才能发送任何数据。此启动过程适用于每个 TCP 连接,并对所有使用 TCP 的网络应用程序的性能产生重要影响:在传输任何应用程序数据之前,每个新连接都将有完整的往返延迟。

为什么要三次握手?

为了防止A的已经失效的连接请求报文段在连接释放后又被传送到了B,在没有第三次握手的情况下,B接收到了A的请求报文段,并且向A发送确认报文后直接建立连接导致资源浪费,所以需要第三次A的主动握手来确认第一次握手是有效的. 如果A并没有发送,则对于B的第二次握手(确认同步报文段)并不会给予理睬,也不会对B的确认发出确认.B由于收不到确认,就知道A并没有要求建立连接.

如果服务器端接到了客户端发的SYN后,回了SYN/ACK后客户端突然出现故障了怎么办?

如果服务器端接到了客户端发的SYN后回了SYN/ACK后客户端掉线了,服务器端没有收到客户端回来的ACK,那么,这个连接处于一个中间状态,既没成功,也没失败。于是,服务器端如果在一定时间内没有收到客户端回来的ACK,会重发SYN/ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s才知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才会断开这个连接。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还有一个保活计时器,能够确保在客户端出故障的时候,能够在一定时间内释放连接,节省资源。服务器每收到一次客户的数据,保活计时器就刷新一次,时间的设置通常是2个小时。若两个小时内没有收到客户的数据,服务器就发送一个探测报文段,以后每隔75秒发送一次。如果一连10次探测报文段发送后无客户的响应,服务器就认为这个客户端出现了故障,接着就关闭这个连接。

4次挥手释放连接

TCP08_释放连接.png

  1. 客户端A进程发出连接释放(FIN)报文,并且停止发送数据。释放数据报文首部,FIN=1,ACK=1,其序列号为seq=u(等于前面已经传送过去的数据的最后一个字节的序号加1),此时,客户端A 进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

  2. 服务器B收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端B就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器B 通知高层的应用进程,客户端A 向服务器B 的方向就释放了,这时候处于半关闭状态,即客户端A 已经没有数据要发送了,但是服务器B 若发送数据,客户端A依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

  3. 客户端A收到服务器B的确认请求后,此时,客户端A就进入FIN-WAIT-2(终止等待2)状态,等待服务器B发送连接释放报文。由于在半关闭状态,服务器B很可能又发送了一些数据,假定此时的序列号为seq=w,服务器B将最后的数据发送完毕后,就向客户端A发送连接释放报文,FIN=1,ACK=1,ack=u+1,此时,服务器B就进入了LAST-ACK(最后确认)状态,等待客户端A的确认。

  4. 客户端A 收到服务器B 的连接释放报文后,必须发出确认报文,ACK=1,ack=w+1,而自己的序列号是seq=u+1,服务器B只要收到了客户端A 发出的确认报文,立即进入CLOSED状态,此时,客户端A进入了TIME-WAIT(时间等待)状态。注意,客户端A必须经过2MSL(最长报文段寿命)的时间后,才进入CLOSED状态。

为什么关闭连接是四次挥手?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKED,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

为什么客户端A最后还要等待2MSL?

防止本次连接中产生的数据包误传到下一次连接中,本次连接中的数据包都会在2MSL时间内消失。第一个MSL:保证客户端 A 发送的最后一个ACK报文能够到达服务器 B,第二个MSL:如果服务器B没有收到,则会重传的FIN+ACK报文段,而客户端A 就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。(防止新的进程刚好分配到该端口号,新的进程收到服务器B 发送的连接释放报文。本来新的进程可能是想跟服务器B建立连接的。)

可靠传输

序号确认号

通过使用序号和确认号,TCP层可以把收到的报文段中的字节按正确的顺序交付给应用层。序号是32位的无符号数,在它增大到2^32-1时,便会回绕到0。对于初始化序列号的选择是TCP中关键的一个操作,在发送第一个包时(SYN包),选择一个随机数作为序号的初值,以克制TCP序号预测攻击丢包,确保强壮性和安全性。

发送确认包(ACK包)携带了接收到的对方发来的字节流的编号,称为确认号,以告诉对方已经成功接收的数据流的字节位置。Ack并不意味着数据已经交付了上层应用程序。

可靠性通过发送方检测到丢失的传输数据并重传这些数据。包括超时重传(RTO)与重复累计确认(DupAcks)。

超时重传(RTO)

发送方使用一个保守估计的时间作为收到数据包的确认的超时上限。如果超过这个上限仍未收到确认包,发送方将重传这个数据包。每当发送方收到确认包后,会重置这个重传定时器。 

流量控制

流量控制:用来避免主机分组发送得过快而使接收方来不及完全收下。

如果接收方的缓存区满了,发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,大量的丢包会极大着浪费网络资源,所以要进行流量控制。

停止等待协议

停止等待就是每发送完一个分组就停止发送,等待对方的确认。在收到确认后再发送下一个分组。 772759-20170729195334191-559570820.png 连续 ARQ 协议

由于停止等待协议信道利用率太低,所以需要使用连续 ARQ 协议来进行改善。这个协议会连续发送一组数据包,然后再等待这些数据包的 ACK。接收方一般都是采用积累确认的方式。这就是说,接收方不必对收到的分组逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认,这就表示:到这个分组为止的所有分组都已正确收到了。

发送窗口

发送窗口内的分组可以连续发送出去而不需要等待对方的确认。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。 image-b9ed2644a8034974bfbfcf7ee75fb7a1.png 滑动窗口协议

滑动窗口协议在发送方和接收方之间各自维持一个滑动窗口,发送发是发送窗口,接收方是接收窗口,而且这个窗口是随着时间变化可以向前滑动的。它允许发送方发送多个分组而不需等待确认。TCP的滑动窗口是以字节为单位的。

发送窗口中:已发送并收到确认的数据:不在发送窗口和发送缓冲区之内;已发送但未收到确认的数据:位于发送窗口之内;允许发送但尚未发送的数据:位于发送窗口之内;发送窗口之外的缓冲区内暂时不允许发送的数据。

接收窗口中:已发送确认并交付主机的数据:不在接收窗口和接收缓冲区之内;未按序收到的数据:位于接收窗口之内;允许的数据:位于接收窗口之内;不允许接收的数据:位于发送窗口之内。

凡是已经发送过的数据,在未收到确认之前,都必须暂时保留,以便在超时重传时使用。只有当发送方收到了接收方的确认报文段时,发送方窗口才可以向前滑动几个序号。当发送方发送的数据经过一段时间没有收到确认(由超时计时器控制),就要使用回退N步协议,回到最后接收到确认号的地方,重新发送这部分数据(SACK只需要发送缺失的分组)。

SACK(选择性确认)

最初采取累计确认的TCP协议在丢包时效率很低。例如,假设通过10个分组发出了1万个字节的数据。如果第一个分组丢失,在纯粹的累计确认协议下,接收方不能说它成功收到了1,000到9,999字节,但未收到包含0到999字节的第一个分组。因而,发送方可能必须重传所有1万个字节。

为此,TCP采取了“选择确认”(selective acknowledgment,SACK)选项。RFC 2018 对此定义为允许接收方确认它成功收到的分组的不连续的块,以及基础TCP确认的成功收到最后连续字节序号。这种确认可以指出SACK block,包含了已经成功收到的连续范围的开始与结束字节序号。从而解决了,TCP发送方会把乱序收包当作丢包,因此会重传乱序收到的包,导致连接的性能下降的问题。在上述例子中,接收方可以发出SACK指出序号1000到9999,发送方因此知道只需重发第一个分组(字节 0 到 999)。

SACK选项并不是强制的。仅当双端都支持时才会被使用。TCP连接创建时会在TCP头中协商SACK细节。

SACK信息会放在TCP首部的选项部分

Kind:占1字节。值为5代表这是SACK选项

Length:占1字节。表明SACK选项一共占用多少字节

Left Edge:占4字节,左边界

Right Edge:占4字节,右边界

一对边界信息需要占用8字节,由于TCP首部的选项部分最多40字节,

所以,SACK选项最多携带4组边界信息

ACK选项的最大占用字节数 = 4 * 8 + 2 = 34

流量控制实现

TCP使用滑动窗口协议实现流量控制。通过确认报文中窗口字段来控制发送方的发送速率。发送方的发送窗口大小不能超过接收方给出窗口大小。

当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,开始了“保持定时器”(persist timer),以避免因随后的修改接收窗口的数据包丢失使连接的双侧进入死锁,发送方无法发出数据直至收到接收方修改窗口的指示。当“保持定时器”到期时,TCP发送方尝试恢复发送一个小的ZWP包(Zero Window Probe),期待接收方回复一个带着新的接收窗口大小的确认包。一般ZWP包会设置成3次,如果3次过后还是0的话,有的TCP实现就会发RST把链接断了。

拥塞控制

拥塞控制:发送方与接收方根据确认包或者包丢失的情况,以及定时器,估计网络拥塞情况,从而修改数据流的行为。

防止过多的数据注入到网络中,避免网络中的路由器或或者链路过载,这是一个全局性的过程 ,涉及到所有的主机、路由器 ,以及与降低网络传输性能有关的所有因素。相比而言,流量控制是点对点通信的控制。

发送方让自己的发送窗口等于拥塞窗口 cwnd(congestion window)。发送方控制拥塞窗口的原则是: 只要网络没有出现拥塞,拥塞窗口就可以再大一些,根据网络的承载情况控制分组的发送量,以获取高性能又能避免拥塞崩溃。 一旦出现了拥塞(确认报文没有按时收到)或者有可能出现拥塞,就必须把拥塞窗口缩小.

TCP的现代实现包含四种相互影响的拥塞控制算法:慢开始、拥塞避免、快速重传、快速恢复。

慢开始

cwnd (拥塞窗口)的初始值比较小,然后随着数据包被接收方确认(收到一个ACK),cwnd 就加倍。cwnd 达到ssthresh(阈值)后,以线性方式增加。

拥塞避免

拥塞避免(加法增大):拥塞避免阶段,拥塞窗口缓慢增大,以防止网络过早出现拥塞。

快重传

当收到了失序的报文段时,立即发出对已收到报文段的重复确认,发送方只要一连收到3个重复确认,就知道对方没有收到报文段,因而立即进行重传.

快恢复

当发送方连续收到三个重复确认,说明网络出现拥塞,就执行“乘法减小”算法,把ssthresh减为拥塞峰值的一半,与慢开始不同之处是现在不执行慢开始算法,即cwnd现在不恢复到初始值,而是把cwnd值设置为新的ssthresh(阈值)(减小后的值),然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。

拥塞控制.png

总结

为什么选择在传输层就将数据分成多个段,而不是等到网络层再分片传递给数据链路层?

因为可靠传输是在传输层进行控制的,如果在传输层不分段,一旦出现数据丢失,整个传输层的数据都得重传,如果在传输层分了段,一旦出现数据丢失,只需要重传丢失的那些段即可 ,可以提高重传的性能。

在TCP的数据传送状态,很多重要的机制保证了TCP的可靠性和强壮性。它们包括:

  1. 使用序号,对收到的报文段进行排序以及检测重复;

  2. 使用校验和,检验数据是否有错误,在发送和接收时都要计算校验和;

  3. 使用确认和计时器,检测和纠正丢包或延时;

  4. 超时重传。

  5. 流量控制,使用连续ARQ协议来保证数据传输的正确性,使用滑动窗口协议来保证接方能够及时处理所接收到的数据,进行流量控制。避免数据发送得过快而使接收方来不及完全收下,大量的丢包会极大着浪费网络资源;

  6. 拥塞控制,使用慢开始、拥塞避免、快重传和快恢复来进行拥塞控制,避免网络拥塞。防止过多的数据注入到网络中,避免网络中的路由器或或者链路过载,出现拥塞; TCP具有下列特点: 

  7. 面向连接;

  8. 可靠传输;

  9. 基于字节流;

  10. 在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销;

  11. 通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点。 为满足TCP协议的这些特点,TCP协议做了如下的规定: 

  12. 数据分片:在发送端对用户数据进行分片并使用序号,在接收端进行重组;

  13. 到达确认:接收端接收到分片时,根据分片序号向发送端发送一个确认;

  14. 超时重传:发送方在发送分片时启动定时器,若超时还未收到相应的确认,则重传;

  15. 流量控制:避免数据发送得过快而使接收方来不及完全收下,大量的丢包会极大着浪费网络资源;

  16. 拥塞控制:防止过多的数据注入到网络中,避免网络中的路由器或或者链路过载,出现拥塞;

  17. 失序处理:分片到达时可能会失序,TCP将对收到的数据进行重新排序,并以正确的顺序交给应用层;

  18. 重复处理:分片会发生重复,接收端必须丢弃重复的数据;

  19. 数据校验:TCP将保持它首部和数据的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片。 TCP 与 UDP 区别

  20. TCP 是面向连接的传输控制协议,而UDP 提供了无连接的数据报服务;

  21. TCP 具有高可靠性,确保传输数据的正确性,不出现丢失或乱序;

  22. UDP 具有较好的实时性,工作效率较 TCP 协议高;

  23. UDP 段结构比 TCP 的段结构简单,因此网络开销也小。