计算机网络
计算机网络基础复习
TCP 协议
三次握手
为什么三次握手可以建立"可靠"传输?
- 第一次握手:如果成功,Server 则确认 Client的发送信道可靠,Server 也确认 自己的接收信道可靠。
- 第二次握手:如果成功,Client 则确认 Server的发送信道和接收信道都可靠,Client 也确认 自己的发送信道和接收信道都可靠。
- 第三次握手:如果成功,Server 则确认 Client的接收信道可靠,Server 也确认 自己的发送信道可靠。
此时,Server和Client双方都确认 自己和对方的发送和接收通道已经可以正常运行。
为什么不进行两次握手?
Server 无法确认 Client 的接收信道和自己的发送信道 是否可靠。即 Server 无法知道客户端是否收到了 SYN-ACK 响应
为什么不需要超过三次握手?
四次或更多次握手将带来不必要的开销,且不会带来额外的可靠性优势,三次足以。
四次挥手
为什么需要四次挥手?
服务器收到客户端的 FIN 报文时,内核会马上回一个 ACK 应答报文,但是服务端应用程序可能还有数据要发送,所以不能马上发送 FIN 报文,而是将发送 FIN 报文的控制权交给服务端应用程序:
- 如果服务端应用程序有数据要发送的话,就发完数据后,才调用关闭连接的函数
- 如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数
什么时候变成三次挥手?
当被动关闭方(上图的服务端)在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
可靠传输
TCP 是通过序列号、确认应答、校验和、重发控制、连接管理以及窗口控制等机制实现可靠性传输的。
重传机制
常见的重传机制
- 超时重传
- 快速重传
- SACK
- D-SACK
为什么需要重传机制?
因为会多种网络问题会导致丢包(网络拥塞、信号干扰导致数据包损坏、路由器故障或链路中断、接收方处理能力不足,来不及处理…)
为了应对各种情况,TCP 需要能够检测到丢包,并重发丢失的数据包。那么 TCP 发送方如何判断丢包了呢?答案是确认应答机制(ACK)TCP 通过确认应答(ACK)和超时重传这两种基本机制来实现可靠传输。
确认应答
发送方发送数据时,为每个数据段分配一个序列号(Sequence Number)
接收方收到数据后,返回一个确认号(Acknowledgement Number),表示期望收到的下一个字节序号
发送方通过接收到的确认号,可以判断数据是否已经成功到达接收方
如上图所示,
- 发送方先发送第一个数据包:
- 序列号 SEQ = 100,长度50字节,也就是发送了100 到 149 这段数据
- 接收方收到后回复:
- ACK = 150,意思是"我收到了100-149 的数据,请发送150 开始的数据"
- 发送方继续发送第二个数据包:
- 序列号 SEQ = 150,长度50字节,也就是发送了150 到 199 这段数据
- 接收方再次回复:
- ACK = 200,意思是"我收到了150-199 的数据,请发送 200 开始的数据"
当然,上图简化为一方发送,另一方接收数据,实际上 TCP 是全双工协议,双方可以同时发送/接收,同时发送时,ACK 就携带在数据包中。
超时重传
上面的例子展示的是理想情况,但网络是不可靠的,数据包在传输过程中可能会出现两种问题:
- 数据包丢失
- ACK 确认包丢失
如何解决这个问题呢?对,就是超时重传:在发送数据包之后启动一个定时器,如果在指定重传超时时间(RTO, Retransmission Timeout)没有收到 ACK,就认为数据包丢失了,需要重新发送
看到这,我们很自然的想到,超时重传机制有一个关键的参数:RTO(Retransmission Timeout,超时重传时间),RTO 的选择是个非常难的问题,过长或者过短都有问题:
- RTO 太短:可能导致不必要的重传,浪费带宽
- RTO 太长:丢包后等待时间过长,影响传输效率
理论上来说,RTO 应该要略微大于**网络往返时间(RTT)**,既要避免过早重传,也要保证丢包时能及时恢复
滑动窗口
TCP 利用滑动窗口实现流量控制和拥塞控制
发送窗口图示,可以分为四个部分:
- 已经发送并且确认的 TCP 段(已经发送并确认)
- 已经发送但是没有确认的 TCP 段(已经发送未确认)
- 未发送但是接收方准备接收的 TCP 段(可以发送)
- 未发送并且接收方也并未准备接受的 TCP 段(不可发送)
接收窗口图示,可以分为三个部分:
- 已经接收并且已经确认的 TCP 段(已经接收并确认)
- 等待接收且允许发送方发送 TCP 段(可以接收未确认)
- 不可接收且不允许发送方发送 TCP 段(不可接收)
流量控制
流量控制是为了控制发送方发送速率,保证接收方来得及接收
在上面图片中这个案例,发送方初始时拥有一个400字节的可用窗口,这个窗口代表了在不等待对方确认的情况下,发送方可以连续发送的数据量。就像是一个在传送带上滑动的窗口,窗口内的数据可以被发送,随着 ACK 的到来,窗口会向前滑动
整个过程的数据传输流程如下:
第一阶段,正常传输
发送方首先发送1-100字节的数据,接着发送101-200字节,然后发送201-300字节,最后发送301-400字节。
发送方每次发送完数据后,窗口就会相应地向前移动,直到全部可用窗口用完,不能再继续发送数据,必须等待接收方确认。
第二阶段,丢包处理
在发送301-400字节的数据包时发生了丢包。这时系统的处理过程如下:
接收方发现收到了乱序数据,发送ACK=301,表示期望收到301字节开始的数据,接收方同时将窗口调整为200字节,这是进行流量控制。
发送方收到ACK=301后,知道前300字节已经被确认,发送窗口向右移动 300 字节,此时还有 301-400 未被确认,并且窗口被调整为 200 字节,所以还可以继续发送 401-500 字节的数据。
第三阶段,超时重传
当发送方重传计时器超时后发现301-400字节的数据包可能丢失:
- 触发超时重传,重新发送301-400字节的数据
- 接收方成功收到重传的数据和之前的401-500字节数据,发送ACK=501,并将接收窗口调整为100字节,进行流量控制
- 发送方收到 ACK 501 后,知道前面 500 字节数据已经被确认,发送窗口向右移动 200 字节,但由于此时窗口大小已经被调整为 100 字节,所以只能继续发送501-600字节的数据。
第四阶段,暂停发送
最后接收方收到 501-600 的数据,回复一个 ACK 601,同时将接收方窗口调整为 0,此时:
- 接收方通过ACK=601确认所有数据
- 窗口调整为 0,迫使发送方暂停发送,直到接收方重新分配窗口空间
整个过程为了方便理解,我们只画了 Client 到 Server 的数据发送和 Server 对 Client 进行的流量控制,但实际上 TCP 是全双工协议,数据发送是双向的,Client 也会对 Server 进行流量控制,道理都是一样的。
这个例子中,接收方可能由于缓冲区满,应用层来不及接收,在不断的减缓接收方窗口,以此降低发送方发送速率,并在最后将接收窗口调整为 0,这将暂停发送方的数据发送。
窗口减小到 0,我们称之为窗口关闭
拥塞控制
真实的网络是有传输瓶颈的,因为网络传输过程中会经过网线、路由器等设备,网线具有带宽限制,路由器等中转设备有缓存限制,一旦网络中传输的数据超过其承载能力,就会导致:路由器缓冲区溢出,丢弃数据包传输延迟增加网络吞吐量下降重传次数增加,进一步加重网络负担最后会导致大家都没法传输数据,所以我们需要拥塞控制。没有合适的拥塞控制机制,就容易出现网络拥塞的情况,就像在没有红绿灯和限速的高速公路上,车辆可能会堵塞一样。
拥塞控制与流量控制的区别?
- 流量控制的目的是为了让发送方速率和接收方匹配,而拥塞控制是从整个网络全局出发,检测拥塞是否发生,如果发生则自发调整发送速度,以恢复网络。
- 流量控制确保接收方不会过载,拥塞控制避免整个网络过载。
- 都是通过控制发送方窗口来实现的。
拥塞控制核心概念
TCP 拥塞控制主要通过以下两个关键参数来实现:
- 拥塞窗口(Congestion Window,cwnd):发送方维护的一个状态变量,用于限制可以发送但未收到确认的数据量
- 慢开始门限(Slow Start Threshold,ssthresh):用于决定是使用慢开始算法还是拥塞避免算法
- 当 cwnd <= ssthresh 使用慢启动算法
- 当 cwnd > ssthresh 使用拥塞避免算法
实际发送窗口 = min(拥塞窗口cwnd, 接收窗口rwnd)
为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
TCP 的拥塞控制采用了四种算法,即 慢开始、 拥塞避免、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
慢开始
慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
拥塞避免
拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送方的 cwnd 加 1.
这种拥塞避免的线性增长什么时候会结束呢?当出现数据包丢失,也就是 RTO 超时的时候,TCP 认为网络可能出现了拥塞,于是重传超时的数据包,同时:
更新 ssthresh 为当前 cwnd 一半,即 sshthresh = cwnd/2
同时将 cwnd 更新为 1
进入慢启动过程
可以看到,当 TCP 发生了 RTO 超时重传的时候,cwnd 就被打回了原型,重新从慢启动开始探测。显然这个拥塞处理太粗暴了,毕竟偶尔丢个包也不一定是网络拥塞了,完全犯不着 会导致网络传输速度剧烈抖动!
快重传
丢包事件也能由三个冗余 ACK 事件触发,TCP 认为这种“丢包事件”,相比于 RTO 超时指示的丢包,反应应该不那么剧烈。
这种情况下就是触发快速重传,发送方的行为:
- 收到3个连续重复ACK后立即重传对应报文段
- 不必等到RTO超时,可以更快响应丢包情况
接收方行为:
- 收到数据立即发送ACK确认,不能等待发送数据进行捎带确认
- 对失序数据也要发送重复ACK,而不是等待数据恢复
而后面 TCP Reno(rfc5681) 的处理则会温和很多:
- cwnd 更新为原来一半,cwnd = cwnd /2
- ssthresh 更新为最新的 cwnd ,ssthresh = cwnd
- 然后进入快速恢复阶段
快恢复
快速恢复算法(Fast Recovery)是 TCP Reno 中用来处理丢包时的一种机制,它通过减少数据包丢失后对窗口大小的调整,避免了像传统的超时重传(RTO)那样的剧烈退避。
引入了快速恢复算法,是为了在恢复丢失数据包期间,还能发送新的数据包,尽量减少丢包期间的网络带宽的浪费,这就是快速恢复名称的由来。
在执行快速恢复算法前,cwnd 和 ssthresh(慢启动阈值)会进行更新(上面已经说了):
- cwnd = cwnd / 2
这表示将当前的拥塞窗口大小减半,反映出网络负载的减少。
- ssthresh = cwnd
设置新的慢启动阈值为当前的窗口大小。
UDP 协议
协议特点
无连接:与 TCP 不同,UDP 在发送数据之前,不需要与目标主机建立连接,也不需要经过“三次握手”这些复杂的步骤。它就像是投掷一封信到远方,根本不管信是否能够成功送达。
无状态:UDP 不维护任何的连接状态,也不记录数据的发送情况。每次发送的数据包都是独立的,UDP不关心数据包的顺序和丢失。每个数据包(Datagram)独立存在,和其他的数据包没有任何关系。
尽力而为:UDP 会尽力传输数据,但并不保证数据一定能够成功到达目标主机,也不保证数据的顺序。它没有流量控制、拥塞控制,也不提供重传机制。如果一个数据包丢失了,发送方也不会自动重新发送。它的目标就是尽可能快速地把数据送到目标。
较小的开销:由于没有TCP那种复杂的控制机制,UDP报文头的大小也较小(只有8字节),这使得它在带宽受限或要求低延迟的场景反而表现得更好。
从不做什么角度,可能更能清晰的记住 UDP 提供的服务:
- UDP 在发送数据之前不建立连接,它只是将其打包然后发送。
- UDP 不提供确认来表明数据已收到。
- UDP 不保证其消息一定会到达。
- UDP 不会检测丢失的消息并重新传输它们。
- UDP 不确保数据的接收顺序与发送顺序相同。
- UDP 不提供任何机制来管理设备之间的数据流或处理拥塞。
协议报文格式

