002-【飞天闪客】你管这破玩意儿叫TCP?
原视频:你管这破玩意儿叫TCP?
传输层的引入

在前文关于网络的叙述中,我们了解到了网络的工作原理,现在,我们可以将数据包从一台电脑传递到另一台电脑。但是,电脑中其实是有很多程序的,数据包是由哪个程序(进程)发出、由哪个程序(进程)接收是没有说明的。为了对进程进行标识,操作系统引入了端口号的概念。
所谓端口号,就是给进程分配的一个数字,用于标识进程。比如,我们打开浏览器,浏览器会分配一个端口号,当浏览器接收到数据包时,操作系统会根据端口号将数据包交给对应的进程。

与此同时,数据包也需要添加传输层协议头,规定源端口与目标端口。于是,原先的设备与设备之间的通信,就升级为进程与进程之间的通信。这就是UDP协议的工作原理。
注:UDP协议头中不光有源端口和目标端口,还有数据包长度和校验值,这里为了简化,没有展示。
TCP协议
由于UDP协议是无连接的,数据包在传输过程中可能会丢失、重复、乱序,因此,TCP协议诞生了。TCP协议是面向连接的,在传输数据之前,需要先建立连接,然后才能传输数据,传输完毕后,需要断开连接。
需要解决的问题
由于网络环境不可靠,数据包可能在半路丢失,而A和B会无法察觉。

要解决这个问题,需要回答两个问题:
- A怎么知道包丢了?答案:让B告诉A。
- 丢了的包怎么办?答案:重传。
针对以上问题,设计以下传输模式:A每传输一个数据包,就要求B确认收到(返回ACK包),如果A在一定时间内没有收到B的确认,就认为数据包丢失了,于是A会重传数据包。把上述协议称为停止等待协议(可靠交付)(stop-and-wait protocal)。

只要按照上述协议,虽然A无法保证B一定能收到数据包,但A能确认B是否收到了数据包,如果没有就重发,尽最大努力让通信过程变得可靠。
但是新的问题又来了,这种方式虽然可靠,但是传输的效率过于低下,于是,我们让A一次发多个包,而B一次发多个ACK包,实现流水线传输。

但是好景不长,A发出的包可能经过不同的路由到达B,这就导致B接收到的包的顺序与发送的顺序可能不一样:

于是,我们在发送的数据包中增加一个序号,B在相应的确认包上也增加一个确认号,通过编号的方式,不仅解决了停止等待协议的效率问题,也解决了顺序问题。

注:实际上ACK的号是收到的最后一个数据包的序号seq+1,也就是告诉对方下一个该发的序号是多少。但图中为了便于理解,ACK就表示收到了那个序号,不必纠结。
而ACK包中的确认号不仅表示收到了当前序号的包,还表示当前序号之前的包均收到了,这种方式叫做累计确认或累计应答。
然而又好景不长,有时候A发送的速度过快,B来不及接收,而B却没有将这一情况告诉A:

解决方案:B告诉A自己的接收能力,A根据B的接收能力调整发送速度。如何告知?在包中增加一个窗口大小字段,表示各自的接收能力:

那么在得知了对方的接收能力后,A如何调整发送速度呢?A会将数据分为以下4类:
- 已发送,已收到ACK
- 已发送,未收到ACK
- 未发送,但可发送
- 未发送,不可发送

A不断发送数据包时,每发送一个数据包,已发送的最后一个序号就往右移动,知道碰到了窗口的上边界,此时A就无法继续发送数据包,达到了流量控制。以下演示A发送数据但是未接收ACK包的过程:

但是当A不断发包的同时,A也会收到来自B的确认包,此时整个窗口就会往后移动,因此上边界也会往后移动,A就能发更多的数据包了:

以上都是在窗口大小不变的情况下进行的传输,如果确认包返回的窗口大小与当前窗口大小不一致,那么窗口的边界会发生对应的变化:
- 新的窗口大小大于当前窗口大小:先移动下边界到已确认数据包的位置,同时按当前窗口大小调整上边界,然后根据新的窗口大小调整上边界的位置;

新的窗口大小大于当前窗口大小 - 新的窗口大小小于当前窗口大小:直接调整下边界的位置,然后根据需要调整上边界的位置。

新的窗口大小小于当前窗口大小
以上机制被称为滑动窗口(sliding window)。
按理说,有了以上机制,数据的传输应该十分顺畅,但是还有一个重要的因素,就是网络环境的不确定性,比如网络拥塞。

解决网络拥塞的方法仍然是控制窗口大小,但是控制窗口大小的方式又不一样了,我们引入一个拥塞窗口(congestion window)来控制窗口大小,拥塞窗口的大小由以下4个因素决定:
以下内容来自ai:
- 慢开始门限:当拥塞窗口大小小于慢开始门限时,使用慢开始算法,即指数增长;
- 慢开始门限:当拥塞窗口大小等于慢开始门限时,使用拥塞避免算法,即线性增长;
- 拥塞避免门限:当拥塞窗口大小大于慢开始门限且小于拥塞避免门限时,使用拥塞避免算法,即线性增长;
- 重传门限:当拥塞窗口大小大于拥塞避免门限时,使用快速重传算法,即指数增长;
- 重传门限:当发生重传时,将拥塞窗口大小设置为重传门限的一半,并将慢开始门限设置为重传门限的一半。
- 重传门限:当收到3个重复的ACK时,将拥塞窗口大小设置为重传门限的一半,并将慢开始门限设置为重传门限的一半。
双方的收发能力决定了滑动窗口的大小,网络环境决定了拥塞窗口的大小,最终决定了数据传输的效率。
通过以上机制,我们大体解决了数据传输的问题,但是后续实践中还是会发现新的问题:有的时候,B还没有准备好或是挂掉了,A就开始发送数据包,因此导致了一定程度的浪费。这个问题的根源在于,A在还没有确认B是否已经准备好后就发送数据包,导致B根本没有接收到数据包。这时候就需要引入一个机制,让A在发送数据包之前先确认B是否准备好,这个机制就是三次握手。

有了上述机制,我们便在一个不可靠的网络环境下实现了可靠的数据传输。这就是TCP,一种==面向连接的、可靠的、基于字节流的传输层通信协议==。
