首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >JavaEE —— 网络编程 TCP协议(二)

JavaEE —— 网络编程 TCP协议(二)

作者头像
Han.miracle
发布2025-12-23 10:01:53
发布2025-12-23 10:01:53
1790
举报

TCP协议

核心特性五:流量控制

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

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

  • 接收端将自己可以接收的缓冲区大小放入TCP⾸部中的"窗⼝⼤⼩"字段,通过ACK端通知发送端;
  • 窗⼝大小字段越大,说明⽹络的吞吐量越⾼;
  • 接收端⼀旦发现自己的缓冲区快满了,就会将窗⼝大小设置成⼀个更⼩的值通知给发送端; 
  • 发送端接受到这个窗⼝之后,就会减慢自己的发送速度;
  • •如果接收端缓冲区满了,就会将窗⼝置为0;这时发送⽅不再发送数据,但是需要定期发送⼀个窗口探 测数据段,使接收端把窗⼝大小告诉发送端

这些图片是来自《图解TCP/IP协议》

《图解HTTP》

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

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

实际上,TCP⾸部40字节选项中还包含了⼀个窗⼝扩⼤因⼦M,实际窗⼝大小是窗⼝字段的值左移M位

核心特性六:拥塞控制

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

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

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

这两个都可以控制窗口的大小,这就要用到木桶效应了

如果说中间的设备转发能力有限,也会丢包

1. 前提:链路视为整体

当无法精准衡量通信链路中单个设备的性能时,直接将整个通信链路当作一个整体,不拆分单个设备的影响。

2. 核心方法:“实验式调整窗口大小”

把 “窗口大小” 等同于 “传输速度”,用 “面多加水,水多加面” 的思路,通过实际传输的反馈(丢包 / 不丢包)来动态调整速度,而非提前计算。

3. 具体调整流程
  1. 初始阶段:先以小窗口(低速度) 开始传输;
  2. 反馈调整:
    • 若传输顺利、不丢包:增大窗口(提速)
    • 若出现丢包(代表链路过载):减小窗口(降速)
  3. 循环优化:重复 “不丢包则提速、丢包则降速” 的操作,持续动态调整。
4. 最终目的:动态平衡

通过反复的 “提速 - 降速” 调整,让传输速度刚好匹配链路的承载能力 —— 既充分利用链路带宽,又不会因过载导致大量丢包,实现 “速率与链路能力的动态平衡”。

  •  此处引入⼀个概念程为拥塞窗⼝
  • 发送开始的时候,定义拥塞窗口大小为 1;
  •  每次收到⼀个ACK应答,拥塞窗口加 1;
  • 每次发送数据包的时候,将拥塞窗⼝和接收端主机反馈的窗⼝⼤⼩做⽐较,取较⼩的值作为实际发送 的窗⼝; 像上面这样的拥塞窗⼝增⻓速度,是指数级别的."慢启动"只是指初使时慢,但是增⻓速度⾮常快.
  • 为了不增⻓的那么快,因此不能使拥塞窗⼝单纯的加倍.
  • 此处引⼊⼀个叫做慢启动的阈值 • 当拥塞窗⼝超过这个阈值的时候,不再按照指数方式增长,而是按照线性⽅式增⻓
  • 当TCP开始启动的时候,慢启动阈值等于窗⼝最⼤值;
  • 在每次超时重发的时候,慢启动阈值会变成原来的⼀半,同时拥塞窗⼝置回1; 少量的丢包,我们仅仅是触发超时重传;⼤量的丢包,我们就认为⽹络拥塞;

当TCP通信开始后,⽹络吞吐量会逐渐上升;随着⽹络发⽣拥堵,吞吐量会⽴刻下降;

下面的图就表示了拥塞控制的工作过程

如果说丢包了,就会TCP reno 掉到丢包的一把这个点,并将它设为新的ssthresh值,然后就成线性增长的过程

拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对⽅,但是⼜要避免给⽹络造成太⼤压⼒的折 中⽅案.

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

核心特性七:延时应答

如果接收数据的主机⽴刻返回ACK应答,这时候返回的窗⼝可能⽐较⼩.

  • 假设接收端缓冲区为1M.⼀次收到了500K的数据;如果⽴刻应答,返回的窗⼝就是500K;
  • 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗⼝再放⼤⼀些,也能处理过来;
  • 如果接收端稍微等⼀会再应答,⽐如等待200ms再应答,那么这个时候返回的窗口大小就是1M; 

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

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

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

时间限制:超过最大延迟时间就应答⼀次; 具体的数量和超时时间,依操作系统不同也有差异;⼀般N取2,超时时间取200ms;

数量限制和时间限制是根据数量的多少,决定使用哪一个限制,

数量限制:数据量密集使用

时间限制:数据量比较少,使用时间限制

注意:但是不一定是百分百提高效率,是否提高效率取决于应用程序处理的缓冲区的数据的速度,如果再等待的过程中,没咋处理那效率就低了,如果处理的多,可以返回的窗口就大了,效率就随之提高了

延时应答,发送ack(内核发送) 延迟发送后,发送应答的时间就长了,等一会再发送ack,有可能就会使ack 就会被响应顺带捎着

核心特性八:捎带应答

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

核心机制九:面向字节流

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

  • 调⽤write时,数据会先写⼊发送缓冲区中;
  • 如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用 read接收缓冲区拿数据;
  • 另⼀方面,TCP的⼀个连接,既有发送缓冲区,也有接收缓冲区,那么对于这⼀个连接,既可以读数据, 也可以写数据.这个概念叫做全双工

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

  • 发送端:应用程序的 “写操作” 只是把数据丢进发送缓冲区,TCP 会自己决定何时、分多少段把缓冲区里的内容发出去(可能合并多次写的内容,也可能拆分一次写的内容)。
  • 接收端:TCP 收到的报文段会先存到接收缓冲区,应用程序的 “读操作” 是从缓冲区里取数据,可一次取走多段内容,也可分多次取一段内容。

例如

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

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

核心机制十:粘包问题

  • 首先要明确,粘包问题中的"包",是指的应用层的数据包.
  • 在TCP的协议头中,没有如同UDP⼀样的"报⽂⻓度"这样的字段,但是有⼀个序号这样的字段
  • 站在传输层的角度,TCP是⼀个⼀个报文过来的.按照序号排好序放在缓冲区中.
  • 站在应⽤层的⻆度,看到的只是⼀串连续的字节数据. • 那么应⽤程序看到了这么⼀连串的字节数据,就不知道从哪个部分开始到哪个部分,是⼀个完整的应 ⽤层数据包. 那么如何避免粘包问题呢?归根结底就是⼀句话,明确两个包之间的边界.
  • 对于定⻓的包,保证每次都按固定⼤⼩读取即可;例如上⾯的Request结构,是固定大小的,那么就从 缓冲区从头开始按sizeof(Request)依次读取即可;
  • 对于变长的包,可以在包头的位置,约定⼀个包总长度的字段,从而就知道了包的结束位置;
  •  对于变⻓的包,还可以在包和包之间使⽤明确的分隔符(应⽤层协议,是程序猿自己来定的,只要保证 分隔符不和正⽂冲突即可);

解决办法:

1.约定包与包之间的分隔符(包的结束标志)

比如:\n

2.约定包的长度:

比如:在前面设置一个包开头,就是数据包有多长

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

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

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

核心机制十一:异常情况的处理

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

进程崩溃的资源回收逻辑

进程崩溃和 “主动正常退出” 在资源回收层面无本质区别:当进程终止(无论崩溃 / 主动退出),操作系统会自动回收该进程持有的所有资源(包括文件描述符)—— 而 Socket 本身属于 “文件描述符” 的一种,因此系统会自动调用该 Socket 的close操作。

close操作触发 TCP 四次挥手

Socket 的close操作会让 TCP 协议栈发送FIN 报文,这是 TCP 四次挥手的 “关闭请求” 信号。因此,即使进程已经崩溃,只要系统完成了 Socket 的close调用,TCP 的四次挥手流程会正常启动,最终完成连接关闭。

2. TCP 连接信息的维护主体

TCP 的连接状态(如连接的端口、状态、缓冲区数据等)是由操作系统内核维护的,而非进程本身。所以:进程崩溃后,进程虽然消失,但内核中该 TCP 连接的信息仍存在,因此四次挥手可以依赖内核正常执行 —— 只是连接释放的时机,会比进程主动调用close稍晚(因系统需要先完成进程资源的回收流程)。

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

1. A 这边(主动关机方):直接随系统终止被清空

当 A 完成关机(断电 / 系统停止运行),A 的所有内存数据(包括内核中保存的 “与 B 的 TCP 连接信息”)会被直接清除

  • 这些信息(比如 B 的 IP、端口、连接状态、未发送的缓存数据等)存储在 A 的系统内存 / 内核中,一旦系统关机停止运行,内存数据会立即丢失,不会有 “主动清理” 的过程,而是随着系统断电 / 终止被直接清空。
2. B 这边(被动方):TCP 协议栈主动清理连接信息

B 内核中保存的 “与 A 的 TCP 连接信息”,存储在TCP 连接控制块(TCB,Transmission Control Block) 中(包含 A 的 IP、端口、序号、窗口大小等核心数据)。当 B 多次重传 FIN 仍收不到 A 的 ACK 后,TCP 协议栈会判定 “对端(A)连接异常”,此时会:

  1. 主动删除对应的 TCB(清除关于 A 的连接信息);
  2. 释放该连接占用的内存、端口等资源。
  3. 流程如下:
  4. A 触发关机,先发送 FIN 报文给 B(四次挥手第 1 步);
  5. B 收到 FIN 后,回复 ACK(四次挥手第 2 步),但 B 自身的 FIN 报文发送较晚;
  6. 此时 A 已完成关机(断电 / 停止运行),无法接收 B 发送的 FIN 报文,更无法回复 ACK;
  7. B 发送 FIN 后,会启动TCP 超时重传机制(多次重发 FIN 报文),但始终收不到 A 的 ACK;
  8. 当重传次数达到系统阈值后,B 的 TCP 协议栈会判定 “对端(A)出现严重异常”,主动释放该连接,清除内核中保存的 A 的连接信息(如端口、状态、缓冲区数据等)。

最终,A 的 “B 的信息” 随关机丢失,B 的 “ A 的信息” 被主动清理,双方关于这次连接的信息都会彻底消失。

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

接收方掉电:
1. 异常触发与初步处理

A 突然掉电离线后,B 后续发送的数据无法收到 A 的 ACK(确认)响应,此时 B 会触发TCP 超时重传机制:重复发送未被确认的数据,但因 A 已离线,重传无法解决问题。

2. RST 复位报文的触发与作用

当重传次数达到系统设定的阈值时,B 会触发 “重置 TCP 连接” 逻辑,主动发送RST 报文(TCP 头部的 RST 标志位被置位的报文):

  • RST 报文的核心作用是强制重置连接,属于异常场景下的连接中断方式,无需等待对方的 ACK 响应;
  • 该操作的本质是 “废弃当前连接的所有状态”,实现 “从头开始、既往不咎” 的强制中断。
3. 最终连接释放

由于 A 已掉电,B 发送的 RST 报文同样无法得到 ACK 响应,此时 B 会单方面释放该连接,清除内核中保存的该连接的所有信息(如端口、状态、缓冲区数据等),完成资源回收。

补充:RST 报文的特性

RST 是 TCP 的 “强制中断标志”,区别于正常四次挥手的 “优雅关闭”,仅用于异常场景(如对端无响应、连接状态异常),可快速释放无效连接资源。

1. 核心背景:TCP 滑动窗口的 “窗口关闭” 问题

TCP 接收方会在 ACK 报文中携带 “接收窗口大小(rwnd)”,发送方以此控制发送速率。若接收方缓冲区已满,会返回rwnd=0(窗口关闭),此时发送方会暂停数据发送。

2. 窗口试探包的作用与场景

当发送方因rwnd=0暂停发送后,无法主动获知接收方缓冲区何时空闲(窗口重新打开),此时会发送窗口试探包

  • 报文特征:通常是无业务数据的小报文(仅含 TCP 头部);
  • 触发逻辑:发送方周期性发送该报文,目的是让接收方返回 ACK,并在 ACK 中携带当前的rwnd
  • 结果:若接收方返回的rwnd>0,发送方恢复数据传输;若仍为0,则继续等待并周期性试探。
3. 与其他报文的区别

窗口试探包≠心跳包:心跳包是探测对端是否在线,而窗口试探包是探测对端的接收能力(窗口状态),仅用于流量控制场景。

发送方掉电:

当发送方(A)突然掉电,接收方(B)会失去 A 的消息,但无法直接区分 “A 是故障离线” 还是 “暂时无业务数据发送”。

2. 接收方(B)的处理流程
  • 阶段 1:有限等待B 会先等待一段时间,但不会无限等待(避免资源长期占用)。
  • 阶段 2:周期性发送心跳包等待超时后,B 会周期性发送 “心跳包”—— 这是一种无业务数据(无载荷)的特殊报文,核心作用是触发 A 的 ACK 响应。
  • 阶段 3:判定故障并释放连接若 B 多次发送心跳包后,始终未收到 A 的 ACK 响应,则判定 A 已故障离线,随后主动释放与 A 的 TCP 连接,清除对应连接信息。
3. 补充:心跳包的本质

这一逻辑对应 TCP 的 “保活(keep-alive)机制”:系统默认会配置保活超时时间(如 2 小时)、心跳包发送周期(如 75 秒)、重试次数(如 9 次),当达到重试阈值仍无响应,就会终止连接,避免无效连接长期占用资源。

网线断了:

消耗更长的时间,双方分别删除这个TCP信息

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

如何基于 UDP 实现可靠传输的?

核心思路是在应用层模仿 TCP 的可靠传输机制(即 “往 TCP 上套”)

1. 序号与确认机制
  • 为每个 UDP 报文分配唯一序号,接收方收到报文后,返回包含对应序号的确认报文(ACK);
  • 发送方通过序号跟踪已发送报文的状态(已确认 / 未确认),接收方通过序号实现报文去重、乱序报文的排序与重组。
2. 超时重传机制
  • 发送方发送报文后启动超时定时器,若超时未收到对应序号的 ACK,则重传该报文;
  • 超时时间采用自适应算法(模仿 TCP):基于报文的往返时间(RTT)动态调整,避免固定超时导致的重传过早 / 过晚。
3. 流量控制(滑动窗口)
  • 接收方在 ACK 中携带当前 “可用接收窗口”(即接收缓冲区剩余容量);
  • 发送方依据该窗口大小调整发送速率,避免因发送过快导致接收方缓冲区溢出,实现应用层的滑动窗口管理。
4. 拥塞控制
  • 模仿 TCP 的拥塞控制策略(如慢启动、拥塞避免):通过重传频率、RTT 变化等指标判断网络拥塞状态,动态调整发送速率,减少拥塞导致的丢包。
5. 报文处理机制
  • 去重:接收方通过报文序号识别重复报文并丢弃;
  • 排序:接收方缓存乱序报文,按序号重组为有序数据流后,再提交给应用层。
6. 虚拟连接管理
  • 在应用层实现 “虚拟连接”:通信双方先交换初始化报文(类似简化版 TCP 三次握手),确认对方可达性并建立会话,模拟 TCP 的连接生命周期管理。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • TCP协议
    • 核心特性五:流量控制
    • 核心特性六:拥塞控制
      • 1. 前提:链路视为整体
      • 2. 核心方法:“实验式调整窗口大小”
      • 3. 具体调整流程
      • 4. 最终目的:动态平衡
    • 核心特性七:延时应答
    • 核心特性八:捎带应答
    • 核心机制九:面向字节流
    • 核心机制十:粘包问题
    • 核心机制十一:异常情况的处理
      • 接收方掉电:
      • 补充:RST 报文的特性
      • 发送方掉电:
  • 如何基于 UDP 实现可靠传输的?
    • 1. 序号与确认机制
    • 2. 超时重传机制
    • 3. 流量控制(滑动窗口)
    • 4. 拥塞控制
    • 5. 报文处理机制
    • 6. 虚拟连接管理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档