文章目录
一、如何做到可靠传输
-
ACK机制(确认机制)
对方收到数据后发“收到”确认,没确认就可能丢了。 -
重传机制:重传策略
没收到确认就重新发送,什么时候重发、重发几次由策略决定。 -
序号机制
每个数据块编号,避免顺序错乱、重复或遗漏。 -
重排机制
如果乱序收到,就按编号排回正确顺序。 -
窗口机制
一次只发部分数据,等收到确认再继续发,避免发送太快。-
流量控制
根据对方接收能力调整发送速度,防止对方“吃不消”。 -
带宽有限
网络通道有限,发太快可能导致拥堵和丢包。
-
二、TCP 与 UDP 协议
2.1 TCP 与 UDP 对比
报文传输:
sendto(hello) ----> recvfrom 返回 hello
sendto(GuLu) ----> recvfrom 返回 GuLu
报文传输的时候,要有足够的空间读取整个报文,否则超出的部分就会丢失
字节流传输:
send(hello)
send(GuLu) ----> recv 可能返回 helloGuLu
2.1.1 报文传输(UDP)
-
每次发送的是一个完整的报文(数据包)
👉 类似“一次寄一个快递包裹” -
用
sendto()
发送,用recvfrom()
接收
👉 每次recvfrom()
收到的就是一个完整的报文 -
报文之间不会混在一起,系统帮你自动分包
-
⚠ 如果接收方准备的缓冲区(空间)太小,超过部分会直接丢失,不会等下次再接
例子:
sendto("hello") // 发送 5 个字节 recvfrom() -> "hell" // 缓冲区只有 4 个字节,多的 "o" 被丢了
2.1.2 字节流传输(TCP)
-
数据像水流一样传输,没有边界划分
-
用
send()
发送,用recv()
接收
👉 发送多少次不重要,接收方可能一次性收到多个,或者只收到一部分 -
接收到的数据需要程序自己分辨边界,比如靠分隔符、长度字段等
-
⚠ 不会丢数据,但会出现“粘包”“拆包”问题
例子:
send("hello") send("GuLu") recv() -> "helloGuLu" // 可能粘在一起 recv() -> "hel" + "loGuLu" // 也可能拆开了
2.1.3 总结对比
特点 | 报文传输(UDP) | 字节流传输(TCP) |
---|---|---|
是否分包 | 系统自动分包 | 无分包,程序自己判断 |
接收顺序 | 一次收一个报文 | 一次收若干字节 |
数据边界 | 天然有边界 | 没有边界(粘包/拆包) |
丢数据情况 | 缓冲区小会丢超出部分 | 不会丢,但可能不完整接收 |
典型函数 | sendto() / recvfrom() | send() / recv() |
2.2 TCP 和 UDP 格式对比
2.3 ARQ协议(Automatic Repeat-reQuest)
ARQ协议(Automatic Repeat-reQuest),即自动重传请求,是传输层的错误纠正协议之一,它通过使用确认和超时两个机制,在不可靠的网络上实现可靠的信息传输。
它的核心机制有两个:
- 确认(ACK):收到数据后,告诉发送方”我收到了”
- 超时重传:发送方在规定时间内没收到ACK,就重新发送一次
ARQ的三种常见模式:
-
停等式 ARQ(Stop-and-Wait)
- 每次只发一帧数据,等对方确认(ACK)后才能发下一帧
- 简单但效率低,容易浪费带宽
应用:
- 通常不用于高速网络
- 类似于 ZeroMQ 的 Request-Respond 模式,一问一答
-
回退N帧 ARQ(Go-Back-N)
- 连续发送多个数据帧,不等ACK,但最多发N个(滑动窗口大小)
- 如果某一帧出错或丢了,就从那一帧开始全部重传
特点:
- 实现简单,效率比停等式高
- 但会浪费带宽(好的帧也要重发)
应用:
- TCP 协议使用了类似机制,但结合了更多优化
-
选择性重传 ARQ(Selective Repeat)
- 连续发送多个帧
- 如果某一帧丢了,只重传那一帧,不影响其它帧
特点:
- 更高效,节省带宽
- 但实现复杂,需要接收端缓存和编号管理
2.3.1 ARQ协议——停等式(stop-and-wait)
停等协议的工作原理如下:
- 发送方对接收方发送数据包,然后等待接收方回复ACK并且开始计时。
- 在等待过程中,发送方停止发送新的数据包。
- 当数据包没有成功被接收方接收,接收方不会发送ACK。这样发送方在等待一定时间后,重新发送数据包。
- 反复以上步骤直到收到从接收方发送的ACK
缺点:较长的等待时间导致低的数据传输速度。
2.3.2 ARQ协议——回退n帧(go-back-n)
为了克服停等协议长时间等待ACK的缺陷,连续ARQ协议会连续发送一组数据包,然后再等待这些数据包的ACK。
什么是滑动窗口:
- 发送方和接收方都会维护一个数据帧的序列,这个序列被称作窗口。
- 发送方的窗口大小由接收方确定,目的在于控制发送速度,以免接收方的缓存不够大,而导致溢出,同时控制流量也可以避免网络拥塞。
- 协议中规定,对于窗口内未经确认的分组需要重传。
回退N步(Go-Back-N,GBN):
- 回退N步协议允许发送方在等待超时的间歇,可以继续发送分组。所有发送的分组,都带有序号。
在GBN协议中,发送方需响应以下三种事件:
- 上层的调用。上层调用相应send()时,发送方首先要检查发送窗口是否已满。
- 接收ACK。在该协议中,对序号为n的分组的确认采取累积确认的方式,表明接收方已正确接收到序号n以前(包括n)的所有分组。
- 超时。若出现超时,发送方将重传所有已发出但还未被确认的分组
假如上面 2 这个包丢失了, 3,4,5,6,7,8 都正常发送给对方了,但是如果 2~8 这些包刚好组成一帧数据,那这个时候丢失了 2 这个包,整个数据帧都要进行重传
总结:GBN采用的技术包括序号、累积确认、检验和以及计时/重传。
2.3.3 ARQ协议——选择重传(Selective-repeat)
一、基础概念
- SR协议(选择性重传):只重发丢的那一份,不是全都重发
- 接收窗口:相当于“我这边正在等哪些包”,假设当前等的是编号 [4, 5, 6, 7]
- 分组编号:每个数据包都有一个编号,像快递编号一样
- ACK:收到包之后,回个消息告诉对方“我收到了”
二、接收方需响应的三种事件
假设接收窗口的基序号为4,分组长度也为4,那么接收方处理分组的三种情况(窗口基序号是4)
情况一:收到了当前窗口里正在等的包(编号在 4~7)
就像我在等快递编号 4~7 的包,现在收到了编号是 5 的
- 我会立刻发一个 ACK(5) 告诉发送方“我收到了包5”
- 如果这个包我之前还没收到,那我就把它先存起来
- 如果我现在收到的是编号4,而且之前的 5、6、7 都也到了,那我就可以把这几个包一次性交给上层处理
- 然后接收窗口就会整体向前滑动,变成等 [8, 9, 10, 11] 的包了
情况二:收到了窗口之前的包(编号是 0~3)
这包我以前收过,现在又来了,但我还是得“礼貌地”回个 ACK
- 虽然包0~3已经处理过了,但发送方可能没收到我当时的ACK,所以又发了一次
- 我虽然不处理这个包,但还是要再发一次 ACK
- 如果我不发ACK,发送方就会以为我没收到,它的窗口就卡在那里不动了
情况三:收到不属于窗口的包(比如编号8、9,但我窗口还在等4~7)
你太着急了,我还没准备好接这个快递,直接无视你。
- 这些包超前太多了,我现在还没等他们
- 所以我会直接忽略这个包,不处理也不ACK
三、总结一下行为模式
收到的分组编号 | 处理方式 | 是否ACK |
---|---|---|
在窗口里(4~7) | 缓存、发送ACK,必要时上交数据 | ✅ 是 |
在窗口外但曾收过(0~3) | 不处理数据,但仍发送ACK | ✅ 是 |
超出窗口太多(>7) | 忽略,不处理也不发ACK | ❌ 否 |
假如同样丢失 2 这个包, 3,4,5,6,7,8 都正常发送给对方了,那么我们就只重传 2 这个包即可
2.3.4 总结:三种ARQ对比总结
模式 | 发几帧? | 出错后怎么办? | 效率 | 复杂度 |
---|---|---|---|---|
停等式 ARQ | 一次发1帧 | 超时后重发这一帧 | 低 | 简单 |
回退N帧 ARQ | 连续发N帧 | 出错后,从错帧开始全都重发 | 中 | 中 |
选择性重传 ARQ | 连续发N帧 | 只重发出错的那几帧 | 高 | 较复杂 |
2.4 RTT 和 RTO
RTO(Retransmission Timeout)重传超时时间
- 定义:发送方等待ACK的最长时间,如果超时还没收到确认,就会重传数据包。
- 作用:防止数据丢失或确认丢失导致的“等不到回复”。
RTT(Round-Trip Time)往返时延
-
定义:从发送数据到收到确认的总耗时
- 发送端发出数据
- 接收端收到后立即发出ACK
- 发送端收到ACK → 整个过程就是一个 RTT
-
RTT 越大,网络越“慢”;RTT 波动大,表示网络“拥堵”不稳定。
RTT由三部分组成
-
传播延迟(Propagation Delay)
- 信号在电缆/光纤中传输的时间(相对固定)
-
处理延迟(Processing Delay)
- 发送/接收主机处理报文的时间(也比较固定)
-
排队延迟(Queuing Delay)
- 路由器中等待处理的时间(受网络拥塞影响,波动较大)
RTO 的设置依赖 RTT 的估计,太短会导致误重传,太长会浪费时间。RTT 是评估网络状态的重要指标,能反映网络是否拥堵。
2.5 流量控制
双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来,这时候接收方只能把处理不过来的数据存在缓存区里(失序的数据包也会被存放在缓存区里) 接收缓存。
如果缓存区满了发送方还在疯狂着发送数据,接收方只能把收到的数据包丢掉,大量的丢包会极大着浪费网络资源,因此,我们需要控制发送方的发送速率, 让接收方与发送方处于一种动态平衡才好。
对发送方发送速率的控制,称之为流量控制。
另外,UDP 要自己控制发送速率,tcp 不用管
一、流量控制——如何控制?
接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量win来表示接收窗口的大小。
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生。
当发送方停止发送数据后,该怎样才能知道自己可以继续发送数据?
- 当接收方处理好数据,接受窗口 win > 0 时,接收方发个通知报文去通知发送方,告诉他可以继续发送数据了。当发送方收到窗口大于0的报文时,就继续发送数据。
- 当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器
二、流量控制小结
(1) 滑动窗口的基本概念
-
通信双方都维护两个滑动窗口:
- 接收窗口:用于接收数据,反映接收方缓存区的可用空间。
- 发送窗口(拥塞窗口):用于控制发送速率,表示发送方最多能连续发送但未确认的数据量。
-
接收窗口大小的通知称为“窗口通告”。
(2) 接收窗口大小是否固定?
- 不是固定的,是根据算法和网络情况动态调整的。丢包多的时候需要减小接收窗口
(3) 接收窗口越大越好吗?
- 不一定。
- 当接收窗口超过某个值时,继续增大不能有效减少丢包,反而加剧内存消耗。
- 所以接收窗口大小应根据网络状况和发送方的拥塞窗口来动态设定。
(4) 接收窗口和发送窗口是否相等?
- 一般接收窗口 ≥ 发送窗口。
- 接收方在确认报文中会告诉发送方当前的接收窗口大小。
- 发送方根据这个值来设置自己的发送窗口大小。
- 但二者并不总是相等,因为接收方发送ACK那一刻,部分数据可能还在处理中。
2.6 拥塞控制
拥塞控制和流量控制虽然采取的动作很相似,但拥塞控制与网络的拥堵情况相关联,而流量控制与接收方的缓存状态相关联
2.7 UDP并发编程
Epoll 如果要同时监听多个端口, 可以考虑使用 epoll方式管理
recvfrom( );
- 获取到 udp 数据
- 获取到 udp 发送者的 ip + port
三、UDP如何可靠,KCP协议在哪些方面有优势
以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。
3.1 UDP 如何实现可靠传输?
UDP 是一种无连接、无状态的传输协议:
- 不保证数据按顺序到达;
- 不保证数据一定送达;
- 不提供重传、流控、拥塞控制。
➤ 所以,为了在 UDP 上实现“可靠”传输,必须在应用层设计一套完整的传输控制机制,包括:
机制 | 描述 |
---|---|
序号编号 | 给每个数据包加上序号,接收端可检测出乱序或丢失 |
ACK(确认机制) | 接收方对收到的包发确认回执 |
超时重传(RTO) | 若某包发送后未在规定时间收到ACK,则重发 |
滑动窗口 | 控制已发送但未确认的包的数量,实现流控 |
拥塞控制 | 避免网络过载,比如通过调节发送速率应对丢包 |
快速重传 | 通过连续的重复ACK判断某个包丢失,提前重传 |
选择性重传 | 只重传丢失的数据包,而不是后续所有数据 |
KCP 就是这样一个 基于 UDP 实现可靠传输的协议,其核心机制包括以上全部,并且做了许多优化。
3.2 KCP 协议的核心机制与优化点
KCP 是专为高实时性应用(如游戏、音视频直播)设计的高性能 ARQ(自动重传请求)协议,采用的是 Selective Repeat(选择性重传)方式。其设计目标是:
以一定带宽代价(10%-20%)换取更低延迟和更快传输响应。
下面列出 KCP 的主要机制及它相比 TCP 的创新和优化:
1. RTO策略优化(重传超时算法)
-
TCP 的问题:RTO(重传超时时间)在连续丢包时呈指数增长,即 RTO × 2,一旦网络波动严重,会导致超时变得很长(如 100ms → 200ms → 400ms → 800ms)。
-
KCP 的改进:
- 启动“快速模式”后,超时增长为 RTO × 1.5;
- 更快检测超时,更快进行重传;
- 实验发现 1.5 是一个相对平衡的系数,避免过快过慢。
2. 选择性重传(Selective Retransmission)
-
TCP 的行为:丢了一个包,后面即使收到也不能确认,会一起重传(Go-Back-N 模型的一种变体),浪费带宽。
-
KCP 的行为:
- 只重传确实丢失的包;
- 接收端告诉发送端哪些包丢了(ACK + UNA 机制);
- 极大减少不必要的重传,特别适合丢包较多的环境(如无线网络)。
3. 快速重传机制(Fast Retransmit)
- 当发送方发出 1,2,3,4,5:
- 如果收到 ACK1、ACK3、ACK4,说明 ACK2 被跳过了。
- 如果连续被跳过两次(
fastresend = 2
),认为 2 已丢失,立即重传; - 不需要等待 RTO 超时,提高了响应速度。
✅ 这对实时性要求高的场景非常关键。
4. ACK机制(非延迟ACK)
-
TCP 默认采用“延迟ACK”:收数据后不会立即回复ACK,而是等一段时间一起发,提高带宽利用。
- 问题:超时计算会算出较大 RTT时间,延长了丢包时的判断过程
-
KCP 可调节是否延迟ACK:
- 默认情况下非延迟;
- 一收到包就发ACK,加快对丢包的反应速度。
5. ACK + UNA 机制(增强的确认系统)
ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一, 而 KCP协议中,除去单独的 ACK 包外,所有包都有UNA信息。
-
TCP 仅发送 UNA(Unacknowledged Number Acknowledged),表明“之前的包都收到了”。
- 缺点:如果只知道某个编号前全部收到,丢了一包就可能触发大范围重传。
-
KCP:ACK + UNA 混合使用
- 所有数据包中都携带 UNA;
- 还会显式发送 ACK 指出哪些包已收到;
- 更加精细化地反馈接收情况,利于实现选择性重传。
6. 流量控制(非退让流控)
KCP正常模式同TCP一样使用公平退让法则,即发送窗口大小由:发送缓存大小、接收端剩余接收缓存大小、丢包退让及慢启动这四要素决定。但传送及时性要求很高的小数据时,可选择通过配置跳过后两步,仅用前两项来控制发送频率。以牺牲部分公平性及带宽利用率之代价,换取了开着BT都能流畅传输的效果。
-
TCP 使用“公平退让”:慢启动 + 拥塞控制,避免网络拥塞。
-
KCP 在默认模式下也模仿 TCP,但:
- 可配置为“非退让模式”:
- 不慢启动、不退让;
- 仅受发送缓存和接收缓存限制;
- 在网络被其他任务抢占时也能保证 KCP 流畅(如:BT 下载时直播不卡)。
- 可配置为“非退让模式”:
✅ 实时性优先级高的应用,使用非退让可大幅改善体验。
3.3 KCP 相比 TCP 的优势与劣势
项目 | TCP | KCP |
---|---|---|
协议层级 | 内核协议栈,系统级别 | 应用层,可嵌入程序自由定制 |
可靠性机制 | 强,但机制较老 | 同样可靠,机制灵活且优化 |
流控和拥塞控制 | 慢启动,指数回退 | 可配置跳过慢启动,适用于高实时性 |
丢包处理 | 整体重传效率低 | 快速+选择性重传,效率高 |
响应时延 | ACK/重传反应较慢 | 响应更快(RTO更低、ACK非延迟) |
带宽利用 | 高,适合大文件、稳定网络 | 带宽略低,适合高抖动网络 |
部署难度 | 操作系统内置,无需部署 | 需集成到应用层,自行处理逻辑 |
3.4 KCP 适合哪些应用场景?
场景 | 说明 |
---|---|
实时游戏 | 网络要求延迟低,KCP表现优越 |
音视频直播 | 一边上传,一边播放,要求传输稳定迅速 |
移动端应用 | 网络质量不稳定,KCP比TCP表现更好 |
实验性网络协议开发 | 自定义可靠传输策略,KCP高度灵活 |