相关概念这部分,描述了tcp协议中涉及的几处概念,相当于名词解释。
建议阅读时先扫一遍这部分。后续阅读中,如果涉及相关概念,在回来差字典。便于理解。(可以开两个网页来看。)
tcp:传输控制协议 ,以上是tcp的报文结构,其各部分表示的含义如下:
- 16位的源端口与目标端口分别表示了,数据从哪个进程来,到哪个进程去。
- 32位序号(Sequence Number简写seq),用以解决网络包乱序问题。
- 32位确认序号(Acknowledgement Number简写ack),用以解决丢包问题。
- 4位首部长度:表示tcp报头有多少个4字节。
- 6位标识位置(TCP Flag ),包的类型,主要用于操控tcp的状态机。(详细内容请看下节)
- 16位窗口大小(Window), 或者叫Sliding Window滑动窗口大小,对应 滑动窗口机制。
- 16位校验和(checksum)
- 16位紧急指针 (Urgent Pointer)
这里需要注意的是:
- tcp协议包里,是没有IP的。IP在下一层的IP协议里。tcp关注的是端口号,是两个进程间的事。
- 一个tcp链接,需要四元祖标识(src_ip, src_port, dst_ip, dst_port)。只有四个属性完全相等时,才能表示为同一链接。
- URG: 标识紧急指针是否有效
- ACK: 标识确认序号是否有效
- PSH: 用来提示接收端应用程序立刻将数据从tcp缓冲区读走
- RST: 要求重新建立连接. 我们把含有RST标识的报文称为复位报文段
- SYN: 请求建立连接. 我们把含有SYN标识的报文称为同步报文段
- FIN: 通知对端, 本端即将关闭. 我们把含有FIN标识的报文称为结束报文段
网络上的传输是没有连接的,包括TCP也是一样的。而TCP所谓的“连接”,其实只不过是在通讯的双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP的状态变换是非常重要的。
TCP的状态机是一个具有11种状态的有限状态机。
上图中涉及的11种状态,对应的描述,如下表所示。
状态 | 描述 |
---|---|
CLOSED | 关闭状态,没有连接活动或正在进行 |
LISTEN | 监听状态,服务器正在等待连接进入 |
SYN RCVD | 收到一个连接请求,尚未确认 |
SYN SENT | 已经发出连接请求,等待确认 |
ESTABLISHED | 连接建立,正常数据传输状态 |
FIN WAIT 1 | (主动关闭)已经发送关闭请求,等待确认 |
FIN WAIT 2 | (主动关闭)收到对方关闭确认,等待对方关闭请求 |
TIME WAIT | 完成双向关闭,等待所有分组死掉 |
CLOSING | 双方同时尝试关闭,等待对方确认 |
CLOSE WAIT | (被动关闭)收到对方关闭请求,已经确认 |
LAST ACK | (被动关闭)等待最后一个关闭确认,并等待所有分组死掉 |
介绍完了tcp相关的基本概念,我们继续介绍tcp的运行机制。我将分成两部分来描述,即保障可靠性的相关机制和提高性能的相关机制。
tcp协议会将数据的每个字节都标记上编号(序列号)。每次数据传送,都会在报头的seq位,标注上当前传送到了哪一部分(序号表示)。而与之对应的ACK报文都会在ack位上返回上次的seq位上的序号,以表示这部分数据,我收到了。
在确认应答机制基础上,我们知道,如果主机A向主机B发送请求(seq=1000),主机B应该返回确认(ack=1001)。但如果因为网络问题,主机A迟迟没有收到主机B的确认。tcp应该如何处理此问题?这就涉及到了超时重传机制。
我们继续上面的问题,如果A没有收到B的回信,可能的原因应该有两种:
- A->B的信丢了
- B->A的信丢了
针对第一种情况,如果A未收到B的回信,一段时间后,A会进行重传。(每次发送出数据后,A就会维护一个计时器,收到确认后重置。)
但是,如果A未收到回信,是因为B->A丢失了,而A仍然重传,那么B端就会收到多个A的重复数据。 怎么办呢?其实利用每次传输的序列号,很容易去重。
- tcp服务端会建立TCB(传输控制块)进入Listen状态。
- tcp客户端也会创建TCB,发送SYN报文(tcp客户端首先会初始化一个自身的序列号x,令seq=x,同时SYN=1。),自身进入SYN-SENT(已经发出连接请求,等待确认)。
- tcp服务器收到请求链接后,如果同意建立链接,就返回ACK报文确认。(tcp服务器会令ACK=1表示同意,同时根据应答响应机制,会返回一个ack=x+1,同时初始化一个自己的序列号y,seq=y。最后,当前链接仍然在同步状态,所以SYN=1。)自身进入SYN-RCVD状态。
- tcp客户端收到服务器的确认后,要再次返回一个确认。此时ACK=1,seq=x+1,ack=y+1。信息发出后,自身进入ESTABLISHED状态(链接建立)
- tcp服务器收到客户端的确认后,进入ESTABLISHED状态(链接建立)。此时,双方就可以传递数据了。
建立链接为什么需要三次握手,两次握手不行么?
主要是为了防止失效的报文,突然传递到服务器。导致建立链接,浪费资源。
对于tcp链接,链接的双方都可以释放链接。这里假设双方处于ESTABLISHED状态,客户端发起释放链接。
-
首先,客户端向服务器发起链接释放报文段,自身进入FIN WAIT 1状态。此时FN=1,seq=u
-
服务端收到FIN=1的报文段,返回确认(ACK=1,seq=v,ack=u+1),同时自身进入CLOSE-WAIT。
此时,TCP要通知上层应用,客户端到服务器方向的链接中断了。此时TCP处于半关闭状态。
如果此时服务端还有一些未传完的数据,服务端可以继续传递。 -
客户端收到服务端确认后,进入FIN WAIT 2状态。此时,客户端仍然会接收服务端发送的数据。直到服务端发完数据,发起释放请求。
-
服务端发送完数据后,就可以发送FIN报文段释放链接了(FIN=1,ACK=1,seq=w,ack=u+1)。自身就进入了LAST ACK状态,等待客户端的最后确认。
-
客户端收到服务端的关闭请求后,返回确认(ACK=1,seq=u+1,ack=w+1)。收到确认后,服务端会进入CLOSE状态。
-
客户端返回确认后,会进入TIME-WAIT状态。注意此时链接并未释放,客户端需要等待2*MSL(最长报文段寿命)后,彻底释放链接。进入CLOSE状态。
为什么最后客户端还要等待 2*MSL的时间?
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
-
保证客户端发送的最后一个ACK报文能够到达服务器,如果ACK报文丢失,服务端就可以通过上文提到的超时重传机制,重新获取ACK报文。
-
防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文
阅读顺序,请先阅读3.1、3.2
在滑动窗口机制的前提下,可以大大加快数据传输的速度。但是接收端的数据处理能力是有限度的。如果在不知道接收端的情况下冒然发送大量数据。很可能会出问题(接收端缓冲区填满,引起丢包,重传...)。
tcp引入了慢启动机制,简单的说就是,先发少量数据“探探路”,摸清情况后再决定用什么速度发送数据。
这里引入拥塞窗口的概念:
- 初始状态下拥塞窗口=1;
- 每收到一次ACK,拥塞窗口+1
- 每次发送数据时,拥塞窗口 = min(拥塞窗口,接收端主机反馈的窗口)
目前为止拥塞窗口是按照指数增长的。为了控制拥塞窗口的增长速度。tcp协议中又引入了慢启动的阈值(ssthresh)。如下图所示,当拥塞窗口超过阈值时,开始线性增长。
当发生网路拥塞时,阈值=当前拥塞窗口大小/2 ,拥塞窗口=1。
注意:
关于网络拥塞
少量的丢包,仅会触发超时重传。
只有大量的丢包,才会被判定为网络拥塞。
接收端的数据处理能力是有限度的。如果发送数据太快,将接收端缓冲区填满。就会产生丢包,进而引发超时重传等一系列问题。针对这个问题tcp协议中有流量控制机制。
- 接收端会将自己可以接收的缓冲区大小,通过ACK中的window设置窗口大小,告知发送端。
- 接收端缓冲区快满时,会将一个更小的值,发送给发送端,发送端就会据此减慢发送的速度。
- 如果接收端缓冲区已经满了,会将window大小设置为0。发送端接收到ACK后,会停止发送,同时每隔一段时间发一个探测的包,获取最新的接收端window。
在前面章节中,我们提到了确认应答机制(ACK机制),对于每一个发送的数据段,都要返回一个ACK确认应答。收到ACK以后再发送下一个数据段。
这里有一个比较大的缺点,就是性能较差。那可不可以批量发送多个数据段呢?(如下图所示)
这里提出一个概念:滑动窗口
窗口大小:指无需等待确认应答,既可以直接发送的数据的最大值。上图直接发送了四段数据,窗口大小即为4000字节。 窗口大小标识在tcp报头的window中(在第一章中有图)。
滑动窗口:前n段数据(依据窗口大小)无需等待,之后,每收到一个ACK应答,就继续发送第n+1,n+2,n+3...段数据。这个窗口会不断向后滑动,故称之为“滑动窗口”。
操作系统为了支持滑动窗口,专门开辟了一段缓冲区记录发送的数据段。只有收到ACK的数据段才可以删除。未收到ACK的数据段会一直保存,以备丢包后超时重传。
在滑动窗口的基础上,如果丢包了怎么办呢?
这里我们跟描述超时重传一样,分两种情况:
- 主机A -> 主机B,某个数据段丢失。
- 主机B -> 主机A,某个ACK丢失。
对于第一种情况,ACK丢失的问题不大,因为我们可以通过后续的ACK确认到主机B收到了哪些数据段,无需重传。
对于第二种情况,即发出的数据段丢失。如下图所示,如果1001-2000数据段丢失,主机B就会一直发送,下一段需要1001的应答。当主机A连续收到三次同样的1001,主机A就会将1001-2000数据段重传。主机B收到1001后,会直接应答7001。这种机制就是快重传。
阅读顺序,请先阅读2.4、2.5
接收端主机收到数据后如果立即返回ACK,此时的窗口大小很可能会比较小。 通常情况下,接收端处理数据的速度非常快。我们只要稍等一会,数据也就处理完了,当前的窗口就很可能会变大。
窗口越大, 网络吞吐量就越大, 传输效率就越高。
TCP的目标是在保证网络不拥堵的情况下尽量提高传输效率;
同时,也不是所有数据包,都延迟应答。有两种特殊情况。
- 数量限制:每隔N个包,就会直接应答,没有延迟。(通常情况下N=2)
- 时间限制:超过最大延迟时间,也会直接应答,没有延迟。(通常情况下,最大延迟时间=200ms)
这就是tcp的延迟应答机制。
在延迟应答的基础上, 我们发现, 很多情况下
客户端和服务器在应用层也是 “一发一收” 的
意味着客户端给服务器说了 “How are you”
服务器也会给客户端回一个 “Fine, thank you”
那么这个时候ACK就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起发送给客户端