① 32位序号:该条TCP数据所携带的起始序号。
② 32位确认序号:期望对方发送数据从哪一个序号开始发送。
③ 4位首部长度:最大是0xF(15),指的是TCP头部的长度。
首部长度 = 4位首部长度(DEC) * 4 ,单位为Byte。
注:DEC:表示十进制的数字。
④ 6个标志位:(6个比特位)
URG
:紧急标志位(直接越过发送缓冲区等待的数据优先传输到网络层,一般配合底下的16位紧急指针使用)ACK
:确认标志位PSH
:发送数据标志位RST
:重置连接标志位(当无法识别对方发来的连接请求时,就会使用RST标志位),换句话说,当连接双方X、Y要断开连接时,X方认为连接已经断开了,Y方却认为连接还没有断开,这个时候,当Y给X发送数据包时,X就会回复带有RST标志位的数据包给YSYN
:发起连接标志位FIN
:断开连接标志位(连接双方有一方想要断开这种连接关系,称为断开)
⑤ 16位窗口大小:告知消息发送方,自己对消息的接收能力时多少,这个值是动态变化的。
⑥ 16位检验和:校验数据在传输过程中是否失真。
⑦ 16位紧急指针:配合URG标志位发送带外数据。(紧急的数据)
⑧ MSS:最大报文段长度(MAX Segmet Size)
- 在三次握手过程中,双方协商MSS的大小,取两者的最小值。
- 为什么要协商最大报文段长度?
防止报文过大,在网络当中传输的时候,数据丢失导致重传。(为了不让其频繁的重传)
- MSS的大小会受到数据链路层MTU的影响
MTU:最大传输单元,是网卡在传输数据帧的时候的一个限制值,这个限制值是取决于网络传输设备的电气特性。(我们可以使用
ifconfig
命令中看到MTU的大小,一般在网络中传输的基本都是1500字节)
- MSS + tcpHeader + ipHeader <= MTU,这是一个特性。
本小节出现的相关的图片均来自《图解TCPIP》第五版。
TCP是一个“聪明的”协议,为了保证它的可靠传输,我们要从两个方面来看:
- 保证数据可靠有序的到达对端
- 提高传输效率
TCP通过肯定的确认应答(ACK)实现可靠的数据传输。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。
在一定时间内没有等到确认应答,发送端就可以认为数据已经丢失,并进行重发。由
此,即使产生了丢包,仍然能够保证数据能够到达对端,实现可靠传输。如下图:
未收到确认应答并不意味着数据一定丢失。也有可能是数据对方已经收到,只是返回的确认应答在途中丢失。这种情况也会导致发送端因没有收到确认应答,而认为数据没有到达目的地,从而进行重新发送。如下图:
重发超时(RTO)是指在重发数据之前,等待确认应答到来的那个特定时间间隔。如果超过了这个时间仍未收到确认应答,发送端将进行数据重发。那么这个重发超时的具体时间长度又是如何确定的呢?
TCP要求不论处在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,它在每次发包时都会计算往返时间(Round Trip Time也叫RTT。是指报文段的往返时间。) 及其偏差(RTT时间波动的值、方差。有时也叫抖动。) 。将这个往返时间和偏差相加重发超时的时间,就是比这个总和要稍大一点的值。
再具体一点的计算就是:
RTO = RTT(上次) * i + RTT(上上次) * (1-i)
一定要注意的是超时重传时间不是固定的,而是动态变化的。
- 在BSD的Unix以及Windows系统中,超时都以0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍(偏差的最小值也是0.5秒。因此最小的重发时间至少是1秒) 。不过,由于最初的数据包还不知道往返时间,所以其重发超时一般设置为6秒左右。
- 数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
- 此外,数据也不会被无限、反复地重发。达到一定重发次数之后,如果仍没有任何确认 应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
保证可靠性的连接管理机制其实就是TCP的三次握手和四次挥手。
更为具体的解释,请看我之前写的TCP协议详解(图解三次握手、四次挥手)(一)。
TCP以1个段为单位,每发一个段进行一次确认应答的处理。这样的传输方式有一个缺点。那就是,包的往返时间越长通信性能就越低。
为解决这个问题,TCP引入了窗口这个概念。即使在往返时间较长的情况下,它也能控制网络性能的下降。如图所示,确认应答不再是以每个分段,而是以更大的单位进行确认时,转发时间将会被大幅度的缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。
- 窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。图中,窗口大小为4个段,也就是4000个字节。
- 发送前四个段的时候, 不需要等待任何ACK, 直接发送
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
- 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
- 窗口越大, 则网络的吞吐率就越高;
这种机制就被称为滑动窗口机制。
但是这种滑动窗口的机制也可能存在对应的问题。
在使用窗口控制中,如果出现段丢失该怎么办?或者是出现了丢包的情况该怎么办?
这里存在着两种情况:
① 数据包已经传输给对方,但是对方返回的ACK数据包丢失了。
② 传输的数据包直接丢失,或者说是某个报文段丢失的情况。
- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001开始的数据"一样;
- 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
当某一报文段丢失后,发送端会一直收到序号为1001的确认应答,这个确认应答好像在提醒发送端“我想接收的是从1001开始的数据”。因此,在窗口比较大,又出现报文段丢失的情况下,同一个序号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答(之所以连续收到3次而不是两次的理由是因为,即使数据段的序号被替换两次也不会触发重发机制) ,就会将其所对应的数据进行重发。
这种机制被称为 “高速重发控制”(也叫 “快重传”)
发送端根据自己的实际情况发送数据。但是,接收端可能收到的是一个毫无关系的数据包又可能会在处理其他问题上花费一些时间。因此在为这个数据包做其他处理时会耗费一些时间,甚至在高负荷的情况下无法接收任何数据。如此一来,如果接收端将本应该接收的数据丢弃的话,就又会触发重发机制,从而导致网络流量的无端浪费。
为了防止这种现象的发生,TCP提供一种机制可以让发送端根据接收端的实际接收能力控制发送的数据量。这就是所谓的流量控制。它的具体操作是,接收端主机向发送端主机通知自己可以接收数据的大小,于是发送端会发送不超过这个限度的数据。该大小限度就被称作窗口大小。在前面所介绍的窗口大小的值就是由接收端主机决定的。
TCP首部中,专门有一个字段用来通知窗口大小(16位窗口大小)。接收主机将自己可以接收的缓冲区大小放入这个字段中通知给发送端。这个字段的值越大,说明网络的吞吐量越高。
不过,接收端的这个缓冲区一旦面临数据溢出时,窗口大小的值也会随之被设置为一个更小的值通知给发送端,从而控制数据发送量。也就是说,发送端主机会根据接收端主机的指示,对发送数据的量进行控制。这也就形成了一个完整的TCP流控制(流量控制)
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的.
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;如图:
- 此处引入一个概念程为拥塞窗口,当发送开始的时候, 定义拥塞窗口大小为1个数据段(1MSS)
- 每次收到一个ACK应答, 拥塞窗口加1;
- 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
扩展:连接建立以后即刻从1MSS开始进行慢启动的话,通过卫星通信等手段提高通信吞吐量所耗的时间会比较长。为此,有时也会将慢启动的初始值设置大于1MSS的值。具体来说,MSS的值小于1095字节时最大为4MSS,小于2190字节时最大为4390字节,超过2190字节时最大值大于2MSS。以太网的标准MSS值为1460字节,因此慢启动的初始值从4380字节(3MSS)开始就可以)。
像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快.
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍,此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长。
当TCP开始启动的时候, 慢启动阈值等于窗口最大值,在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;
少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞,当TCP通信开始后, 网络吞吐量会逐渐上升,随着网络发生拥堵, 吞吐量会立刻下降。
但是这种拥塞窗口的大小一旦到达网络拥塞的情况时,拥塞窗口的大小会直接一下子变为慢启动时的阈值,即1个MSS,但是这种情况其实是不科学的,因为我一次丢包有可能是正常的,这种正常的情况的发生有可能是网络闪断了,当网络闪断的时候,拥塞串口的大小就会被置为1,然后再慢启动,再进行拥塞控制,针对这种情况,我们就需要一种快恢复的机制
总结一下,TCP为了实现拥塞控制,总共采用了慢启动机制、拥塞避免机制、快重传机制和快恢复机制。
快重传机制在流量控制小节已经讲过了。
接收数据的主机如果每次都立刻回复确认应答的话,可能会返回一个较小的窗口。那是因为刚接收完数据,缓冲区已满。
当某个接收端收到这个小窗口的通知以后,会以它为上限发送数据,从而又降低了网络的利用率(这其实是窗口控制特有的问题,专门术语叫做糊涂窗口综合征)。为此,引入了一个方法,那就是收到数据以后并不立即返回确认应答,而是延迟一段时间的机制。
换句话来讲:
- 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率.
那么所有的包都可以延迟应答么? 肯定也不是;
- 数量限制: 每隔N个包就应答一次;
- 时间限制: 超过最大延迟时间就应答一次;
具体的数量和超时时间, 依操作系统不同也有差异,一般N取2,超时时间取200ms。
事实上,大可不必为每一个数据段都进行一次确认应答。TCP采用滑动窗口的控制机制,因此通常确认应答少一些也无妨。TCP文件传输中,绝大多数是每两个数据段返回一次确认应答。
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的. 意味着客户端给服务器说了 “How are you”, 服务器也会给客户端回一个 “Fine, thank you”; 那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端。
接收数据以后如果立刻返回确认应答,就无法实现捎带应答。而是将所接收的数据传给应用处理生成返回数据以后进再进行发送请求为止,必须一直等待确认应答的发送。也就是说,如果没有启用延迟确认应答就无法实现捎带应答。延迟确认应答是能够提高网络利用率从而降低计算机处理负荷的一种较优的处理机制。
举个例子来说:
假设当前存在客户端C和服务端S,C和S已经建立了连接, 但是两者都不进行数据的收发,就是互相都不向对方发送数据, 这个时候服务端S就很纳闷,因为没有接收到来自客户端C的数据,它不知道C当前的状态是什么,不能随便的断开连接(因为C有可能还在酝酿数据), 然后在这个过程里面就有一个保活计时器来进行计时, 当连接上之后客户端没有发送数据,就开始计时,每当过一段时间,如果还没发送数据,则服务端S就会给对应客户端C发送一个探测数据包,探测客户端目前是否正常。如果客户端正常,则会对该探测包进行应答,如果不正常(如果当客户端突然崩溃了,就不会调用close接口进行断开连接,服务端认为连接还在,而客户端已经挂掉了,这个时候就是一种不正常的情况),则不会进行应答。
需要注意的是,这个保活定时器并不是只发一次探测包,而是每隔75s发一次,总共发10次。如果在这10次中,客户端都没有进行应答,则服务端会主动的将连接断开。
保活定时器发出的探测包也被称为TCP的心跳包,或者说是保活数据包。
TCP可靠性的保证:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
TCP提高性能的保障:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
- 拥塞控制