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

TCP传输层协议(4)

TCP应用层协议(4)

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.

因此 TCP 支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);

• 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过 ACK 端通知发送端;

• 窗口大小字段越大, 说明网络的吞吐量越高;

• 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;

• 发送端接受到这个窗口之后, 就会减慢自己的发送速度;

• 如果接收端缓冲区满了, 就会将窗口置为 0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端.

在这里插入图片描述

接收端如何把窗口大小告诉发送端呢? 回忆我们的 TCP 首部中, 有一个 16 位窗口字段, 就是存放了窗口大小信息;

那么问题来了, 16 位数字最大表示 65535, 那么 TCP 窗口最大就是 65535 字节么?

实际上, TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M, 实际窗口大小是 窗口字段的值左移 M 位;

拥塞控制

在这里插入图片描述

如果是大面积丢包
在这里插入图片描述

这时候如果我们是多个客户端多个服务端呢

在这里插入图片描述

滑动窗口是拥塞窗口和对方窗口的最小值,谁小谁是主要矛盾,即考虑了网络拥堵问题和对方的接受能力,什么是拥塞窗口,拥塞窗口就是一个临界值,网络不会拥塞,这是衡量是否拥塞的标准

虽然 TCP 有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.

因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.

TCP 引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

• 此处引入一个概念称为拥塞窗口

• 发送开始的时候, 定义拥塞窗口大小为 1;

• 每次收到一个 ACK 应答, 拥塞窗口加 1;

• 每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;

像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快.

• 为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.

• 此处引入一个叫做慢启动的阈值

• 当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长
在这里插入图片描述

• 当 TCP 开始启动的时候, 慢启动阈值等于窗口最大值;

• 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回 1;少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;当 TCP 通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;拥塞控制, 归根结底是 TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.

TCP 拥塞控制这样的过程, 就好像 热恋的感觉

延迟应答

如果接收数据的主机立刻返回 ACK 应答, 这时候返回的窗口可能比较小

• 假设接收端缓冲区为 1M. 一次收到了 500K 的数据; 如果立刻应答, 返回的窗口就是 500K;

• 但实际上可能处理端处理的速度很快, 10ms 之内就把 500K 数据从缓冲区消费掉了;

• 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;

• 如果接收端稍微等一会再应答, 比如等待 200ms 再应答, 那么这个时候返回的窗口大小就是 1M;

一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;

那么所有的包都可以延迟应答么? 肯定也不是;

• 数量限制: 每隔 N 个包就应答一次;

• 时间限制: 超过最大延迟时间就应答一次;

具体的数量和超时时间, 依操作系统不同也有差异; 一般 N 取 2, 超时时间取 200ms;

在这里插入图片描述

捎带应答

在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 “一发一收” 的. 意味着客户端给服务器说了 “How are you”, 服务器也会给客户端回一个 “Fine,thank you”;那么这个时候 ACK 就可以搭顺风车, 和服务器回应的 “Fine, thank you” 一起回给客户端

面向字节流

创建一个 TCP 的 socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;

• 调用 write 时, 数据会先写入发送缓冲区中;

• 如果发送的字节数太长, 会被拆分成多个 TCP 的数据包发出;

• 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;

• 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;

• 然后应用程序可以调用 read 从接收缓冲区拿数据;

• 另一方面, TCP 的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工

由于缓冲区的存在, TCP 程序的读和写不需要一一匹配, 例如:

• 写 100 个字节数据时, 可以调用一次 write 写 100 个字节, 也可以调用 100 次write, 每次写一个字节;

• 读 100 个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100 个字节, 也可以一次 read 一个字节, 重复 100 次;

粘包问题

• 首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包.

• 在 TCP 的协议头中, 没有如同 UDP 一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段.

• 站在传输层的角度, TCP 是一个一个报文过来的. 按照序号排好序放在缓冲区中.

• 站在应用层的角度, 看到的只是一串连续的字节数据.

• 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包. 那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界.

• 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的 Request 结构, 是固定大小的, 那么就从缓冲区从头开始按 sizeof(Request)依次读取即可;

• 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;

• 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可);

思考: 对于 UDP 协议来说, 是否也存在 “粘包问题” 呢?

• 对于 UDP, 如果还没有上层交付数据, UDP 的报文长度仍然在. 同时, UDP 是一个一个把数据交付给应用层. 就有很明确的数据边界.

• 站在应用层的站在应用层的角度, 使用 UDP 的时候, 要么收到完整的 UDP 报文, 要么不收. 不会出现"半个"的情况.

TCP 异常情况

进程终止: 进程终止会释放文件描述符, 仍然可以发送 FIN. 和正常关闭没有什么区别.

机器重启: 和进程终止的情况相同.

机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行 reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.

另外, 应用层的某些协议, 也有一些这样的检测机制. 例如 HTTP 长连接中, 也会定期检测对方的状态. 例如 QQ, 在 QQ 断线之后, 也会定期尝试重新连接

TCP 小结

为什么 TCP 这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能.

可靠性:

• 校验和

• 序列号(按序到达)

• 确认应答

• 超时重发

• 连接管理

• 流量控制

• 拥塞控制

提高性能:

• 滑动窗口

• 快速重传

• 延迟应答

• 捎带应答

其他:

为什么 TCP 这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能.

可靠性:

• 校验和

• 序列号(按序到达)

• 确认应答

• 超时重发

• 连接管理

• 流量控制

• 拥塞控制

提高性能:

• 滑动窗口

• 快速重传

• 延迟应答

• 捎带应答

其他:

• 定时器(超时重传定时器, 保活定时器, TIME_WAIT 定时器等)

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

相关文章:

  • 微信实名认证组件
  • 二十四、Mybatis-基础操作-删除(预编译SQL)
  • SAP ALV导出excel 报 XML 错误的 /xl/sharedStrings.xml
  • Android协程的用法大全
  • 汽车电子:现代汽车的智能核心
  • Unity_数据持久化_Json
  • 使用原生css实现word目录样式,标题后面的...动态长度并始终在标题后方(生成点线)
  • 第七十章:告别“手写循环”噩梦!Trainer结构搭建:PyTorch Lightning让你“一键炼丹”!
  • Codeforces Deque工艺
  • 用 FreeMarker 动态构造 SQL 实现数据透视分析
  • STM32学习笔记12-串口数据包收发FlyMcuST-LINK Utility
  • Shortest Routes II(Floyd最短路)
  • 管家婆辉煌系列试用版/期限说明
  • Shader开发(十三)理解片元插值
  • 淘米自动签到脚本
  • 大气负氧离子自动监测站:解密空气的科技密码
  • 有红帽认证证书可以0元置换华为openEuler-HCIA/HCIP认证
  • OpenSCA开源社区每日安全漏洞及投毒情报资讯|13th Aug. , 2025
  • MyBatis StatementHandler核心原理详解
  • Nginx反向代理Tomcat实战指南
  • mysql-DDLy语句案例
  • 基于asp.net#C##VUE框架的独居老人物资配送系统的设计与实现#sql server#visual studio
  • OpenZeppelin Contracts 架构分层分析
  • 基于机器学习的赌博网站识别系统设计与实现
  • 【计算机视觉与深度学习实战】02基于形态学的权重自适应图像去噪系统
  • 【车联网kafka】常用参数及其命令总结(第八篇)
  • 【展厅多媒体】数字展厅小知识:实物识别桌是什么?
  • 杂记 02
  • Java研学-SpringCloud(四)
  • YOLO12 改进、魔改|幅度感知线性注意力MALA,提升小目标、遮挡的检测能力