TCP数据格式
源端口/目的端口
计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通信。源端口、目标端口是各占2个字节,可推算计算机的端口个数为65535个。客户端的源端口是临时开启的随机端口。
序号
占4个字节,表示本报文段所发送数据的第一个字节的编号。在TCP连接中所传送的字节流的每一个字节都会按顺序编号。由于序列号由32位表示,所以每2^32-1个字节,就会出现序列号回绕,再次从 0 开始
确认号
占4个字节,期望收到对方下一个报文的第一个数据字节的序号。
数据偏移
占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字节,表示从确认号开始,本报文的发送方可以接收的字节数,即接收窗口大小。用于流量控制。
检验和
占2字节,对整个的TCP报文段,包括TCP头部和TCP数据,以16位字进行计算所得。这是一个强制性的字段。
紧急指针
占2字节,本报文段中的紧急数据的最后一个字节的序号
选项
选项字段—最多40字节。每个选项的开始是1字节的kind字段说明选项的类型,1字节的Length字段,说明选项的类型占的字节数。
Kind=0:选项表结束(1字节)
Kind=1:无操作(1字节)用于选项字段之间的字边界对齐。
Kind=2:最大报文段长度(4字节,Maximum Segment Size,MSS)通常在创建连接而设置SYN标志的数据包中指明这个选项,指明本端所能接收的最大长度的报文段。通常将MSS设置为(MTU-40)字节,携带TCP报文段的IP数据报的长度就不会超过MTU(MTU最大长度为1518字节,最短为64字节),从而避免本机发生IP分片。只能出现在同步报文段中,否则将被忽略。
Kind=3:窗口扩大因子(3字节,wscale),取值0-14。用来把TCP的窗口的值左移的位数,使窗口值乘倍。只能出现在同步报文段中,否则将被忽略。这是因为现在的TCP接收数据缓冲区(接收窗口)的长度通常大于65535字节。
Kind=4:sackOK—发送端支持并同意使用SACK选项。
Kind=5:SACK选择性确认选项。
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字节)
复制代码
可靠传输
-
首先,采用三次握手来建立TCP连接,四次挥手来释放TCP连接,从而保证建立的传输信道是可靠的。
-
其次,TCP采用了连续ARQ协议来保证数据传输的正确性,使用滑动窗口协议来保证接方能够及时处理所接收到的数据,进行流量控制。
-
最后,TCP使用慢开始、拥塞避免、快重传和快恢复来进行拥塞控制,避免网络拥塞。
3次握手建立连接TCP连接
假设:客户A主动打开连接,而服务器B被动打开连接。
- TCP服务器B 进程先创建传输控制块TCB,时刻准备接受客户A 进程的连接请求,此时 服务器B 就进入了LISTEN(收听)状态;
- 客户端A进程也是先创建传输控制块TCB,然后向服务器B 发出连接请求(SYN)报文,报文首部中的同部位SYN=1,同时携带客户端为这个连接请求而设定的随机初始序列号x(即:seq=x),此时,客户端A进程进入了 SYN-SENT(同步已发送)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗一个序号。
- 服务器B收到请求报文后,如果同意连接,则发出确认(SYN/ACK)报文。确认报文中应该SYN=1,ACK=1,确认号是ack=x+1,同时也要携带一个随机产生的序号y(即:seq=y),此时,服务器B进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
- 客户端A进程收到确认(SYN/ACK包)后,还要向服务器B 给出确认(ACK包)。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端A 进入ESTABLISHED(已建立连接)状态。
- 当服务器B 收到客户端A 的确认后也进入ESTABLISHED(已建立连接)状态。
“三次握手”的目的是在不可靠的信道上创建可信通信连接,需要双方通知对方自己的分组的序列号的开始数值。
4次挥手释放TCP连接
数据传输完毕后,双方都可释放连接。最开始的时候,客户端A 和服务器B 都是处于ESTABLISHED(建立)状态,然后客户端A 主动关闭,服务器B 被动关闭
- 客户端A 进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,ACK=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端A 进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- 服务器B 收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端B 就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器B 通知高层的应用进程,客户端A 向服务器B 的方向就释放了,这时候处于半关闭状态,即客户端A 已经没有数据要发送了,但是服务器B 若发送数据,客户端A 依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 客户端A 收到服务器B 的确认请求后,此时,客户端A 就进入FIN-WAIT-2(终止等待2)状态,等待服务器B 发送连接释放报文(在这之前还需要接受服务器B 发送的最后的数据)。
- 服务器B 将最后的数据发送完毕后,就向客户端A 发送连接释放报文,FIN=1,ACK=1,ack=u+1,由于在半关闭状态,服务器B 很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器B 就进入了LAST-ACK(最后确认)状态,等待客户端A 的确认。
- 客户端A 收到服务器B 的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端A 就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端A 撤销相应的TCB后,才进入CLOSED状态。
- 服务器B 只要收到了客户端A 发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器B 结束TCP连接的时间要比客户端A 早一些。
停止等待协议
假设:A为发送方,B为接收方
停止等待是指每发送完一个分组就停止发送,等待对方的确认.在收到确认后再发送下一个分组.
无差错情况
每发送一个分组,收到一个分组的确认,再发送下一个分组
出现差错
出现差错的两种情况:
- B收到分组M,检测时出现了差错
- 分组M在传输过程中丢失了
对于第一种情况,B检测到M错误后,就丢弃M,其他什么也不做(也不发送数据通知A收到有差错的分组); 第二种情况,B没有收到1,一直等待,不发送数据.
确认丢失
B收到M后,发送的对M的确认丢失了。A在设定的超时重传的时间内没有收到确认,重传M。 B这时候又收到了M,采取如下两步行动
- 丢弃这个重复分区
- 向A发送确认
确认迟到
B对M的确认迟到了。此时A因为超时重传,重新传给B一份M,B收到后丢弃同样的分组M,并重传确认。
连续ARQ协议
由于停止等待ARQ协议信道利用率太低,所以需要使用连续ARQ协议来进行改善。这个协议会连续发送一组数据包,然后再等待这些数据包的ACK。 连续ARQ规定,发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置。
发送窗口:
发送窗口内的分组可以连续发送出去而不需要等待对方的确认。凡是已经发送过的数据,在未收到确认之前都必须暂时保留,以便在超时重传时使用。
累积确认:
接收方不必对收到的分组逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认。
Go-back-N(回退N) :
表示需要退回来重传已经发送过的N个分组。 如果发送方发送了5个分组,而第3个分组丢失了,这时候接收方只能对前2个分组发出确认。发送方在一定时间没有收到后面3个分组的确认信息后,只能把后面3个分组重传一次(SACK只需要发送缺失的分组)。
SACK(选择性确认)
最初采取累计确认的TCP协议在丢包时效率很低。例如,假设通过10个分组发出了1万个字节的数据。如果第一个分组丢失,在纯粹的累计确认协议下,接收方不能说它成功收到了1,000到9,999字节,但未收到包含0到999字节的第一个分组。因而,发送方可能必须重传所有1万个字节。
为此,TCP采取了“选择确认”(selective acknowledgment,SACK)选项。RFC 2018 对此定义为允许接收方确认它成功收到的分组的不连续的块。在上述例子中,接收方可以发出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
滑动窗口
假设:A为发送方,B为接收方
滑动窗口协议在在发送方和接收方之间各自维持一个滑动窗口,发送发是发送窗口,接收方是接收窗口,而且这个窗口是随着时间变化可以向前滑动的。它允许发送方发送多个分组而不需等待确认。TCP的滑动窗口是以字节为单位的。
发送窗口中:已发送并收到确认的数据:不在发送窗口和发送缓冲区之内;已发送但未收到确认的数据:位于发送窗口之内;允许发送但尚未发送的数据:位于发送窗口之内;发送窗口之外的缓冲区内暂时不允许发送的数据。
接收窗口中:已发送确认并交付主机的数据:不在接收窗口和接收缓冲区之内;未按序收到的数据:位于接收窗口之内;允许的数据:位于接收窗口之内;不允许接收的数据:位于发送窗口之内。
- 凡是已经发送过的数据,在未收到确认之前,都必须暂时保留,以便在超时重传时使用。
- 只有当发送方A收到了接收方的确认报文段时,发送方窗口才可以向前滑动几个序号。
- 当发送方A发送的数据经过一段时间没有收到确认(由超时计时器控制),就要使用回退N步协议,回到最后接收到确认号的地方,重新发送这部分数据(SACK只需要发送缺失的分组)。
流量控制
流量控制:用来避免主机分组发送得过快而使接收方来不及完全收下,一般由接收方通告给发送方进行调控。
如果接收方的缓存区满了,发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,大量的丢包会极大着浪费网络资源,所以要进行流量控制。
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(阈值)(减小后的值),然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。
问题
为什么要三次握手?
为了防止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才会断开这个连接。
为什么客户端A最后还要等待2MSL?
- 保证客户端A 发送的最后一个ACK报文能够到达服务器B,如果服务器B没有收到,则会重传自己的FIN+ACK报文段,而客户端A 就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
- 防止 “三次握手”中出现的 “已经失效的连接请求报文段”。在A发送完最后一个ACK报文段后,在经过时间2MSL就可以让本连接持续的时间内所产生的所有报文段都从网络中消失,确保下一个新的连接不会出现旧连接的报文段。
- 防止新的进程刚好分配到该端口号,新的进程收到服务器B 发送的连接释放报文。本来新的进程可能是想跟服务器B建立连接的。
为什么关闭连接是四次挥手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,”你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还有一个保活计时器,能够确保在客户端出故障的时候,能够在一定时间内释放连接,节省资源。服务器每收到一次客户的数据,保活计时器就刷新一次,时间的设置通常是2个小时。若两个小时内没有收到客户的数据,服务器就发送一个探测报文段,以后每隔75秒发送一次。如果一连10次探测报文段发送后无客户的响应,服务器就认为这个客户端出现了故障,接着就关闭这个连接。
为什么选择在传输层就将数据分成多个段,而不是等到网络层再分片传递给数据链路层?
因为可靠传输是在传输层进行控制的,如果在传输层不分段,一旦出现数据丢失,整个传输层的数据都得重传,如果在传输层分了段,一旦出现数据丢失,只需要重传丢失的那些段即可 ,可以提高重传的性能。