当前位置: 首页 > news >正文

10个TCP可靠性与拥塞控制题目整理

TCP可靠性与拥塞控制:核心知识点整理

这篇文章整理了TCP可靠传输和拥塞控制相关的核心问题,包括滑动窗口、超时重传、拥塞控制算法,以及在项目中处理send()返回值的经验。
在这里插入图片描述

目录

  • 前言
  • Q1: TCP如何保证可靠性?
  • Q2: ACK确认号的含义
  • Q3: 超时重传是如何工作的?
  • Q4: 什么是滑动窗口?
  • Q5: 接收窗口为0怎么办?
  • Q6: 什么是快重传?
  • Q7: SACK解决了什么问题?
  • Q8: 拥塞控制和流量控制的区别
  • Q9: 拥塞控制的四个算法
  • Q10: 项目中如何处理send()返回值?
  • 学习总结

前言

学完TCP连接管理后,接着学习TCP的可靠传输机制。这部分内容比较复杂,涉及到序列号、ACK、滑动窗口、拥塞控制等概念。

最开始看王道视频时,感觉概念特别多,理解起来比较吃力。后来通过画图、写代码、实际调试,慢慢理解了这些机制的作用。

这篇文章记录了我对这些机制的理解,以及在Reactor项目中遇到的实际问题。


Q1: TCP如何保证可靠性?

我的理解

TCP通过以下几个机制来保证可靠传输:

核心机制:

  1. 序列号和ACK确认

    • 每个字节都有序列号
    • 接收方发送ACK确认收到的数据
    • 发送方根据ACK知道哪些数据已经送达
  2. 超时重传

    • 发送方设置定时器
    • 超时未收到ACK,重传数据
    • 确保数据不会因为丢包而丢失
  3. 滑动窗口

    • 接收方告诉发送方可以接收多少数据(rwnd)
    • 发送方根据rwnd调整发送速度
    • 防止接收方来不及处理

其他机制:

  1. 快重传:收到3个重复ACK,立刻重传,不等超时
  2. 选择确认(SACK):明确告诉发送方哪些数据收到了
  3. 校验和:检测数据是否损坏
  4. 顺序控制:保证数据按顺序交付

我原来的疑问

Q:这么多机制,哪些是最重要的?

学习过程中发现,前三个机制(序列号+ACK、超时重传、滑动窗口)是最核心的,后面的机制都是在这基础上的优化。


Q2: ACK确认号的含义

我的理解

ACK号表示:“这个序号之前的所有数据我都已经收到了,下次请从这个序号开始发送。”

例子:

发送方发送:seq=100,数据长度200字节数据范围:100-299(共200字节)接收方回复:ACK=300ACK=300的含义:- "我已经收到了100-299的所有字节"- "下一次请从序号300开始发送"计算公式:ACK号 = 收到的最后一个字节的序号 + 1ACK = 299 + 1 = 300

累积确认:

如果接收方发送ACK=500,表示1-499的所有字节都已收到,不仅仅是某一段。

我原来的疑问

Q:为什么是+1,而不是直接用最后一个字节的序号?

一开始我也搞不清楚,后来明白了:ACK表示"期望收到的下一个字节",所以要+1。


Q3: 超时重传是如何工作的?

我的理解

工作原理:

  1. 发送方发送数据后,启动一个定时器(RTO时间)
  2. 等待接收方的ACK
  3. 如果在RTO时间内收到ACK → 停止定时器
  4. 如果超时未收到ACK → 重传数据

两种丢包情况:

情况1:数据丢失

发送方 → 接收方:数据包丢失 X
发送方:等待RTO超时
发送方:重传数据
接收方:收到数据,发送ACK

情况2:ACK丢失

发送方 → 接收方:数据包到达 ✓
接收方 → 发送方:ACK丢失 X
发送方:等待RTO超时
发送方:重传数据
接收方:检测到重复数据,丢弃,重发ACK

RTO的计算:

RTO(Retransmission Timeout)不是固定值,是动态计算的:

RTO = SRTT + 4 × RTTVAR其中:SRTT:平滑的RTT(Smoothed RTT)RTTVAR:RTT的偏差(Variation)Linux典型值:- 最小RTO:200ms- 初始RTO:1秒- 最大RTO:120秒

指数退避:

如果重传后还是超时:

第1次超时:RTO = 1秒,重传
第2次超时:RTO = 2秒,重传
第3次超时:RTO = 4秒,重传
第4次超时:RTO = 8秒,重传
...最多重传15次(Linux默认),然后放弃连接

我原来的疑问

Q:RTO为什么要动态计算,不能固定成1秒吗?

因为网络状况不同:

  • 局域网RTT可能只有几毫秒,固定1秒太慢
  • 跨国网络RTT可能几百毫秒,固定1秒可能太快

动态计算可以适应不同的网络环境。


Q4: 什么是滑动窗口?

我的理解

滑动窗口是TCP流量控制的核心机制。接收方告诉发送方自己能接收多少数据(rwnd),发送方根据rwnd调整发送速度。

为什么需要滑动窗口?

没有滑动窗口的问题(停止等待):

发送1个数据包 → 等待ACK → 发送1个 → 等待ACK问题:- 效率低:大量时间浪费在等待- 网络利用率低例子:RTT = 100ms数据段大小 = 1KB吞吐量 = 1KB / 100ms = 10KB/s如果网络带宽是100MB/s,利用率只有0.01%!

有滑动窗口后:

连续发送多个数据包,不需要每次都等待ACK例子:窗口大小 = 10个数据段(10KB)吞吐量 = 10KB / 100ms = 100KB/s利用率提高了10倍!

工作原理:

发送方维护一个发送窗口,窗口大小由接收方的rwnd决定:

发送方缓冲区:
┌──────────────────────────────────────────┐
│ 已发送已确认 │ 已发送未确认 │ 可发送 │ 不可发送 │
└───────────┴─────────────┴──────┴────────┘└──────────────────┘发送窗口接收方缓冲区:
┌──────────────────────────────────────────┐
│ 已接收已确认 │ 可接收(接收窗口)│ 不可接收 │
└───────────┴─────────────────┴──────────┘└───────────────┘rwnd发送窗口大小 = 接收方通知的rwnd

项目中的体现

在C++程序中,我们不需要手动实现滑动窗口,这是TCP协议栈自动处理的。但需要注意:

  • send()可能只发送部分数据(受发送窗口限制)
  • 需要循环发送,直到全部发送完成

Q5: 接收窗口为0怎么办?

我的理解

如果接收方缓冲区满了,会通知发送方rwnd=0,发送方就不能再发送数据了。

问题: 接收方缓冲区有空间后,如何通知发送方?

解决方法:零窗口探测(Zero Window Probe)

发送方的处理:

1. 收到rwnd=0后,停止发送数据
2. 定期发送1字节的探测包(Zero Window Probe)
3. 接收方回复ACK,携带最新的rwnd
4. 如果rwnd > 0,发送方恢复发送数据探测间隔:- 从1秒开始,指数退避- 第1次:1秒后- 第2次:2秒后- 第3次:4秒后- 最多探测15次

流程:

发送方                          接收方|                               ||--- 数据 ---------------------->| 缓冲区满|<-- ACK, rwnd=0 ---------------||                               || 停止发送,设置探测定时器       ||                               ||--- Zero Window Probe (1字节)-->| 缓冲区仍满|<-- ACK, rwnd=0 ---------------||                               || (1秒后)                      ||--- Zero Window Probe --------->| 缓冲区有空间|<-- ACK, rwnd=4096 ------------||                               ||--- 继续发送数据 -------------->|

Q6: 什么是快重传?

我的理解

快重传(Fast Retransmit):收到3个重复ACK后,立刻重传数据,不需要等待超时。

触发条件: 收到3个重复ACK(同一ACK号共4次)

详细解释:

- 第1个ACK=101:正常确认(不算重复)
- 第2个ACK=101:重复ACK #1
- 第3个ACK=101:重复ACK #2
- 第4个ACK=101:重复ACK #3  ← 触发快重传!

完整流程:

发送方                          接收方|------ seq=1 ------------------>| ✓ 收到|<----- ACK=101 ------------------|  正常ACK|                               ||------ seq=101 ---------------->| X(丢失)|                               ||------ seq=201 ---------------->| ✓(失序)|<----- ACK=101 ------------------|  重复ACK #1|                               ||------ seq=301 ---------------->| ✓(失序)|<----- ACK=101 ------------------|  重复ACK #2|                               ||------ seq=401 ---------------->| ✓(失序)|<----- ACK=101 ------------------|  重复ACK #3|                               || 收到3个重复ACK!               || 立刻重传seq=101               ||------ seq=101 ---------------->| ✓ 收到|                               ||<----- ACK=501 ------------------|  确认所有数据

为什么是3个重复?

这是个经验值:

  • 太少(1-2个):可能误判(正常的乱序)
  • 太多(4-5个):反应太慢
  • 3个:平衡了灵敏度和误判率

优势:

相比超时重传更快:

超时重传:等待RTO(可能1秒)
快重传:等待3个ACK(可能只需10-30ms)
延迟降低97%!

Q7: SACK解决了什么问题?

我的理解

SACK(Selective Acknowledgment,选择确认):在ACK报文中,明确告诉发送方哪些数据收到了。

累积确认的问题:

场景:多个数据包丢失发送方发送:1-100, 101-200, 201-300, 301-400接收方收到:1-100 ✓101-200 X(丢失)201-300 ✓301-400 ✓接收方只能回复:ACK=101表示:"101之前的都收到了"发送方的困惑:- 不知道201-300是否收到- 不知道301-400是否收到- 只能重传101-200, 201-300, 301-400(全部)- 浪费带宽

SACK的解决方案:

接收方回复:ACK=101, SACK=(201-400)含义:- ACK=101:101之前的都收到了- SACK=(201-400):201-400也收到了发送方的理解:- 101-200丢失- 201-400已收到- 只需要重传101-200 ✓

完整流程:

发送方                          接收方|------ 1-100 ------------------->| ✓|------ 101-200 ----------------->| X(丢失)|------ 201-300 ----------------->| ✓|------ 301-400 ----------------->| ✓|                               ||<----- ACK=101, SACK=(201-400)--||                               || 只重传101-200 ✓               ||------ 101-200 ----------------->| ✓|                               ||<----- ACK=401 -----------------|

我原来的疑问

Q:SACK是默认开启的吗?

不一定,需要双方都支持。Linux默认支持,可以通过以下命令查看:

cat /proc/sys/net/ipv4/tcp_sack
# 输出:1(开启)

Q8: 拥塞控制和流量控制的区别

我的理解

这两个容易混淆,但作用完全不同:

流量控制(Flow Control):

目的:防止发送方发送过快,接收方来不及处理
控制对象:接收方的接收能力
实现机制:滑动窗口(rwnd)
决定者:接收方通知发送方例子:接收方缓冲区:4096字节已接收未读取:1096字节剩余空间:3000字节接收方通知:rwnd=3000发送方:最多发送3000字节

拥塞控制(Congestion Control):

目的:防止网络拥塞
控制对象:网络的传输能力
实现机制:拥塞窗口(cwnd)
决定者:发送方自己判断例子:网络拥塞时:丢包率高、RTT增大发送方判断:网络拥塞了发送方:减小cwnd,降低发送速度

实际发送窗口:

发送窗口 = min(rwnd, cwnd)- rwnd:接收方的接收能力(流量控制)- cwnd:网络的传输能力(拥塞控制)- 取两者最小值例子:rwnd = 10000字节(接收方能接收)cwnd = 5000字节(网络能承受)发送窗口 = min(10000, 5000) = 5000字节

Q9: 拥塞控制的四个算法

我的理解

拥塞控制有四个核心算法,通过调整cwnd(拥塞窗口)来控制发送速度。

算法1:慢启动(Slow Start)

目的:连接建立时,探测网络容量原理:- cwnd初始值:1个MSS- 每收到1个ACK,cwnd += 1- cwnd指数增长:1 → 2 → 4 → 8 → 16 → 32...终止条件:- cwnd >= ssthresh(慢启动阈值)- 进入拥塞避免阶段例子:初始:cwnd=1, ssthresh=16第1个RTT:发送1个,收到1个ACK,cwnd=2第2个RTT:发送2个,收到2个ACK,cwnd=4第3个RTT:发送4个,收到4个ACK,cwnd=8第4个RTT:发送8个,收到8个ACK,cwnd=16cwnd达到ssthresh,进入拥塞避免

算法2:拥塞避免(Congestion Avoidance)

目的:避免网络拥塞,缓慢增加cwnd原理:- 每收到1个ACK,cwnd += 1/cwnd- cwnd线性增长:16 → 17 → 18 → 19...- 增长速度慢(每个RTT增加1个MSS)终止条件:- 发生超时重传(网络拥塞)- 进入慢启动(ssthresh = cwnd / 2,cwnd = 1)或- 收到3个重复ACK- 进入快恢复

算法3:快重传(Fast Retransmit)

目的:快速检测丢包并重传原理:- 收到3个重复ACK- 立刻重传,不等超时- 进入快恢复例子:cwnd=20, ssthresh=16收到3个重复ACK重传丢失的数据包进入快恢复

算法4:快恢复(Fast Recovery)

目的:快速恢复发送速度原理:- ssthresh = cwnd / 2- cwnd = ssthresh + 3- 每收到1个重复ACK,cwnd += 1(暂时增大窗口)- 收到新的ACK,cwnd = ssthresh,进入拥塞避免例子:发生快重传时:cwnd=20进入快恢复:ssthresh = 20 / 2 = 10cwnd = 10 + 3 = 13收到新ACK:cwnd = 10进入拥塞避免

完整状态转换:

慢启动(指数增长)cwnd: 1 → 2 → 4 → 8 → 16↓ cwnd >= ssthresh
拥塞避免(线性增长)cwnd: 16 → 17 → 18 → 19 → 20↓ 超时重传
慢启动(重新开始)ssthresh = cwnd / 2 = 10cwnd = 1↓
拥塞避免cwnd: 1 → 2 → 4 → 8 → 10 → 11 → 12↓ 收到3个重复ACK
快恢复ssthresh = cwnd / 2 = 6cwnd = 6 + 3 = 9↓ 收到新ACK
拥塞避免cwnd = 6

我原来的疑问

Q:为什么叫"慢启动",明明是指数增长很快?

这个名字是相对于"一开始就全速发送"来说的。虽然是指数增长,但从1开始,比直接全速发送要"慢"。


Q10: 项目中如何处理send()返回值?

我的实践经验

在实现Reactor项目时,我发现send()的返回值处理很重要,处理不当会导致数据丢失或程序卡死。

send()返回值的三种情况:

情况1:发送成功(返回值 > 0)

int send_data(int sockfd, const char* data, size_t len) {size_t total_sent = 0;while (total_sent < len) {int n = send(sockfd, data + total_sent, len - total_sent, 0);if (n > 0) {// 发送成功,可能没发完total_sent += n;// 关键:send()可能只发送部分数据// 因为受限于发送缓冲区大小和TCP窗口} else if (n == 0) {// 连接关闭return -1;} else {  // n < 0if (errno == EAGAIN || errno == EWOULDBLOCK) {// 发送缓冲区满,需要等待// 非阻塞模式下常见break;  // 等待下次EPOLLOUT事件} else {// 发送错误perror("send error");return -1;}}}return total_sent;
}

情况2:发送缓冲区满(EAGAIN/EWOULDBLOCK)

这是我在项目中遇到的最常见的情况:

// Reactor模式中的处理
class Connection {
public:void send_response(const string& response) {// 尝试直接发送int n = send(sockfd_, response.c_str(), response.size(), 0);if (n == response.size()) {// 全部发送完成return;}if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {n = 0;  // 一个字节都没发送}if (n < response.size()) {// 只发送了部分,剩余数据放入发送缓冲区send_buffer_.append(response.c_str() + n, response.size() - n);// 注册EPOLLOUT事件,等待可写epoll_event ev;ev.events = EPOLLIN | EPOLLOUT | EPOLLET;ev.data.ptr = this;epoll_ctl(epollfd_, EPOLL_CTL_MOD, sockfd_, &ev);}}void handle_write_event() {// 发送缓冲区中的数据while (!send_buffer_.empty()) {int n = send(sockfd_, send_buffer_.c_str(), send_buffer_.size(), 0);if (n > 0) {send_buffer_.erase(0, n);if (send_buffer_.empty()) {// 全部发送完成,取消EPOLLOUTepoll_event ev;ev.events = EPOLLIN | EPOLLET;ev.data.ptr = this;epoll_ctl(epollfd_, EPOLL_CTL_MOD, sockfd_, &ev);}} else if (n < 0) {if (errno == EAGAIN || errno == EWOULDBLOCK) {// 缓冲区又满了,等待下次EPOLLOUTbreak;} else {// 发送错误,关闭连接close_connection();break;}}}}private:int sockfd_;int epollfd_;string send_buffer_;
};

情况3:连接错误

void handle_send_error(int sockfd, int err) {if (err == EPIPE) {// 对方已关闭连接(Broken pipe)LOG_WARN("Connection closed by peer");close(sockfd);} else if (err == ECONNRESET) {// 连接被重置(对方发送RST)LOG_WARN("Connection reset by peer");close(sockfd);} else {// 其他错误LOG_ERROR("send error: %s", strerror(err));close(sockfd);}
}

我踩过的坑

坑1:以为send()会发送全部数据

// 错误代码
send(sockfd, data, len, 0);  // 以为发送完了
// 实际上可能只发送了一部分!

坑2:没有处理EAGAIN

// 错误代码
int n = send(sockfd, data, len, 0);
if (n < 0) {// 直接关闭连接close(sockfd);  // 错误!可能只是缓冲区满
}

坑3:没有使用应用层发送缓冲区

// 错误代码
int n = send(sockfd, data, len, 0);
// 只发送了部分,剩余数据丢失!

正确做法:

  1. send()可能只发送部分数据,必须循环发送
  2. 非阻塞模式下,处理EAGAIN,使用应用层发送缓冲区
  3. 注册EPOLLOUT事件,等待可写后再发送

学习总结

这段时间学习TCP可靠性和拥塞控制,收获很大:

理论层面:

  • 理解了TCP如何通过序列号、ACK、重传保证可靠性
  • 搞清楚了滑动窗口的工作原理
  • 理解了拥塞控制四大算法的状态转换

实践层面:

  • 在项目中正确处理send()返回值
  • 实现了应用层发送缓冲区
  • 使用EPOLLOUT事件处理发送缓冲区满的情况

遇到的坑:

  1. 以为send()会发送全部数据
  2. 没有处理EAGAIN导致连接被错误关闭
  3. 没有应用层发送缓冲区导致数据丢失

下一步学习:

  • HTTP协议基础
  • Cookie和Session机制
  • HTTP缓存机制

参考资料

  • 王道计算机网络视频课程

这篇文章记录了我对TCP可靠性和拥塞控制的理解。如果有错误或不准确的地方,欢迎指正!

http://www.dtcms.com/a/577987.html

相关文章:

  • 天津建设网站培训房地产怎么做网站推广
  • 20251106在荣品RD-RK3588开发板的Android13系统下配置单5GHz的softAP模式以提高网速
  • 有没有做长图的网站如何制作网站教程视频
  • Photoshop - Photoshop 工具栏(23)单列选框工具
  • 计算机图形中的法线矩阵:深入理解与应用
  • MySQL入门练习50题
  • SSM公办小学网络报名系统f3d3p(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 可以做投票的网站深圳网站建设制作设计企业
  • 在飞牛nas中docker使用nas挂载的硬盘问题(docker开机后自动重启)
  • 告别文件混乱!Adobe Bridge 2026 全媒体可视化管理,让设计流程更顺畅
  • 记录kubelet错误:Could not open resolv conf file
  • MATLAB基于Theil不等系数的IOWHA算子组合预测模型
  • 河南旅游集团 网站建设计算机是学什么内容的
  • 社交网站页面设计长春火车站是哪个站
  • 算法题(Python)数组篇 | 4.长度最小的子数组
  • 噬菌体展示技术:基因型 - 表型统一的分子筛选与研发利器
  • 江西网站开发公司电话宁夏水利建设工程网站
  • Ngram Overlap Example Selector in langchain
  • 温州网站改版哪家好郑州建设网站推广公司
  • 长沙网站推广 下拉通推广网站调用时间
  • [手写系列]Go手写db — — 第七版(实现Disk存储引擎、Docker化支持)
  • win11系统 Android Studio AVD 模拟器创建【记录】
  • 架构论文《论UP(统一过程)在开发中的设计和应用》
  • 中文域名网站好不好优化公司网站建设整体架构
  • 【LeetCode】102. 二叉树的层序遍历
  • 梅州市做试块网站wordpress mo
  • wps excel中把特定几列除以某一列,然后以百分比显示
  • 如何预览常见格式word、excel、ppt、图片等格式的文档
  • 免费网络短剧网站小学生编程软件
  • linux服务器常用组件巡检脚本