目录
1.前言
哈喽大家好呀,好久没有给大家继续带来关于Java网络原理的学习了,前一段时间网络原理的学习是大部分关于应用层的,接下来就该进入传输层的详细讲解了,今天主要给大家分享的是传输层的两大核心协议——UDP与TCP,前面学习有提及过一点点,这篇博文就给它详细讲解完。
2.正文
这里简单科普俩句,如果做业务开发的,UDP/TCP更少,HTTP更多;如果做的是基础架构开发,UDP/TCP更多,HTTP更少。
2.1UDP协议
UDP:无连接,不可靠传输,面向数据包,全双工~~
2.1.1UDP协议端格式
先总体概览下:
端口号:
- 服务器的端口是程序员指定的(提前制定好,客户端才能访问到)
- 客户端的端口是系统自动分配的空闲端口(如果提前指定了,可能会和你客户端上的其他程序冲突)
各两个字节,共32bit位。一个端口号的取值范围,0->65535。
实际上,一般把 1024 以下的端口保留,咱们写代码都是用1024->65535 这个范围的,如果设置不在这个范围内,非法端口号。
长度:
长度由报头+载荷总长度组成。
校验和:
验证数据是否发生修改的手段。
- HTTPS 的数字签名,为了防止黑客篡改
- UDP 的校验和,不是为了防人,和安全性无关,而是防止出现传输过程中的“比特翻转”
(光信号,电信号,电磁波,收到外界干扰可能会使高低电平/高低频光信号发生改变)
校验流程:
- 发送之前,先计算一个校验和,把整个数据包的数据都代入
- 把数据和校验和一起发送给对端。
- 接收方收到之后重新计算一下校验和,和收到的校验和进行对比(UDP 发现校验和不一致,就会直接丢弃)
- UDP 的校验和使用了 CRC 方式来进行校验 (循环冗余校验)
- 把每个字节(除了校验和位置的部分之外),都当做整数,进行累加,溢出也没关系,继续加
- 最终得到结果,crc 校验和
- 传输到对端,如果数据出现错误了,对端再次计算的校验和,就会和第一个校验和不一样了~~
另外,如果两个校验和相同,原始数据一定也相同[可能存在变数],这个变数即有极小概率会出现这种情况:前一个字节bit翻转刚好小了1,后一个字节bit翻转刚好大了1,最终加到一起,校验和是一样的。虽说原理上有这种情况存在,但比特翻转本身极小概率,恰好两个翻转抵消了影响,小之又小。
载荷:
就是数据本身了~
2.1.2UDP的特点
核心特点如下:
无连接通信
无需握手过程:"即发即走"模式
示例:DNS查询直接发送请求包,无需预先建立连接
不可靠传输
不保证数据到达
不保证顺序正确
无重传机制
无拥塞控制
网络拥堵时仍按原速率发送
优势:避免TCP的"减速等待"问题
轻量级头部
固定8字节开销(TCP至少20字节)
减少网络传输负担
2.1.3理解UDP的“不可靠”
UDP的不可靠性体现在三个层面:
丢包风险:网络拥堵时路由器直接丢弃UDP包
乱序问题:后发数据可能先到
无错误修复:校验失败直接丢弃不重传
✅ 设计哲学:
UDP的"不可靠"本质是用可靠性换取性能,适合能容忍丢包的场景(如视频通话丢几帧不影响整体)
2.1.4面向数据报
与TCP的字节流不同,UDP保持应用层消息边界:
# 发送端
sendto("Hello".encode()) # 发送5字节数据报
sendto("World".encode()) # 发送5字节数据报
# 接收端
data1 = recvfrom() # 收到完整"Hello"
data2 = recvfrom() # 收到完整"World"
核心特征:
发送次数 = 接收次数
数据包大小保持不变
不存在TCP的粘包问题(粘包问题后文会详细讲解)
单次读写完整报文
2.1.5基于UDP的应用层协议
协议 | 端口 | 应用场景 | 可靠性实现 |
---|---|---|---|
DNS | 53 | 域名解析 | 应用层重试 |
DHCP | 67/68 | IP地址分配 | 广播+超时重试 |
NTP | 123 | 时间同步 | 冗余采样 |
TFTP | 69 | 简单文件传输 | 块确认+重传 |
RTP | 动态 | 实时音视频 | 序号+时间戳 |
QUIC | 443 | HTTP/3底层 | 自定义可靠传输 |
2.2TCP协议
TCP:有连接,面向字节流,可靠传输,全双工
2.2.1TCP协议端格式
传输层核心内容:16位源端口号+16位目的端口号
首部长度:选项的存在,导致tcp报头长度是可变的
保留:UDP 的问题,长度不够,又不能扩展~~TCP 的设计者就考虑到这样的问题。TCP 报头中就预留了一些“保留位”(现在先不用,但是占个位子)。
标志位:TCP最核心的六个标志位(里面有俩个较为少见的,所以说六个~)
16位校验和:用来校验数据是否出现错误的。
序号与确认号:
一个TCP 的载荷是多个字节构成的~~ 每个字节都分配一个编号,并且是连续递增的。序号字段填写载荷部分的第一个字节的序号,序号连续递增。
引入序号之后,接收方就可以根据序号对数据进行排序~~这里需要引入后发先至的概念啦~TCP 需要处理后发先至的情况,确保应用程序通过 socket api 读到的数据顺序是正确的~。
TCP 在接收方这里会安排"接收缓冲区"(内存,操作系统内核里)通过网卡读到的数据,先放到接收缓冲区中,后续代码里调用read) ,也是从接受缓冲区来读的。
根据序号来排序,序号小的在前面,大的在后面确保前面的数据已经到了,然后 read 才能接触。如果是后面的数据先到,read 继续阻塞,不会读取到数据。
基于 TCP 写代码的时候,完全不必担心数据顺序的问题~(代码写起来就方便了)
如果是基于 UDP,实现拆包组包,,就需要考虑顺序,自己实现排序逻辑~~
2.2.2TCP十个核心机制
可靠性:网络通信,是非常复杂的此处的可靠性,不是说A给B发消息,B100% 能收到~而是 A 给 B发了消息之后,尽可能的让B收到~并且还要让A 能够知道 B 是否收到了~~
2.2.2.1确认应答
核心作用:保障数据传输的可靠性与有序性,解决网络传输中的丢包、乱序问题。
工作流程:
sequenceDiagram Sender->>Receiver:发送数据包(Seq=100,Len=100,数据:100-199) Note right of Receiver:收到完整数据 Receiver->>Sender:回复ACK(Ack=200) Sender->>Sender:滑动窗口右移,释放缓冲区
序列号(Seq):
每个字节的唯一编号(初始值随机,防攻击)
例如:发送100字节数据,Seq=100 → 覆盖100~199号字节
确认号(Ack):
期望收到的下一字节编号
Ack=200 表示199号及之前所有字节已确认收到
累积确认:
Ack=N 意味着所有小于N的字节均已正确接收
例如:收到Ack=300后,无需再确认Seq=250的包
TCP头部中实现ACK的字段:
+-+-+-+-+-+-+-+-+ | 控制标志(6位) | | ...ACK=1... | → ACK标志位必须置1 +-+-+-+-+-+-+-+-+ | 确认号(32位)| → 携带Ack值(期望的下字节编号) +-+-+-+-+-+-+-+-+
对比UDP的可靠性实现:
特性 TCP确认应答 应用层自实现(如QUIC) 可靠性保证 内核层自动完成 用户空间逻辑控制 性能开销 每个数据包需ACK 可批量确认(如每10包确认1次) 实时性 依赖RTT(通常10-100ms) 可定制确认策略
🔍 设计哲学:
通过空间换时间(增加ACK头部开销),换取100%数据可达性,适用于对可靠性要求极高的场景(如金融交易、文件传输)
2.2.2.2超时重传
针对丢包的情况做出处理 ~~
核心概念讲解:
丢包产生的原因:
- 为啥会丢包呢,网络结构,非常复杂的~数据报经过某个路由器,交换机转发的时候,该路由器/交换机已经非常繁忙了,导致当前需要转发的数据量超出路由器/交换机的
转发能力上限。如果此时接收缓冲区满了,只能丢弃后来包。如何判断丢包:
达到等待时间的上限,还没有收到 ack,A 就认为传输中发生丢包了:
- A->B发的数据丢了
- B->A 返回的 ack 丢了
假设当前 A ->B 发送数据,丢包的超时时间阈值为T,当 A 给 B传输发生超时之后,就会延长这个时间阈值,会继续延长这个时间,这个时间不是无休止的。超时次数达到一定程度/等待时间达到一定程度,认为网络出现严重故障,放弃这一次传输~
时间阈值怎么来的:
- 随着进行重传,如果发现数据无法到达对方的概率越来越高。说明即使我们增加了概率,还是不能成功,意味着当前丢包概率是一个非常大的数值,意味着网络上大概率已经出现严重故障了~
俩种丢包情况:
- A ->B发的数据丢了
- B ->A 返回的 ack 丟了
对于A而言,无法分辨这俩种情况,则都是对数据进行重传,B就不一样了。在第二种情况下,B会收到两份相同的数据,这个时候TCP会在内部进行去重操作,根据序号在缓冲区寻找。
核心流程解析:
初始发送
发送数据包(如Seq=100, Len=100)
同时启动重传计时器(初始RTO通常为1秒)
正常确认
发送方->>接收方: 数据包(Seq=100) 接收方->>发送方: ACK=200 发送方->>发送方: 停止计时器,更新RTT
超时重传
发送方->>接收方: 数据包(Seq=100) 发送方->>发送方: 启动计时器(RTO=1s) Note over 发送方: 1秒后未收到ACK 发送方->>接收方: 重传相同数据包 发送方->>发送方: RTO=2s(指数退避)
确认应答+超时重传 vs 三次握手
未来面试的时候很容易有一个误解的概念,这里加以区分:保证TCP可靠传输的是确认应答+超时重传机制而不是“三次握手”。
本质区别:阶段与目的不同
机制 作用阶段 核心目的 对可靠传输的贡献 确认应答+超时重传 数据传输阶段 保障数据包可靠传输 直接保证 三次握手 连接建立阶段 初始化通信参数 间接基础(非直接保证) 技术本质分析
握手为可靠传输奠定基础(交换初始序列号)
但真正的可靠性由数据传输机制实现
权威佐证(RFC 793)
TCP标准定义(4.2节):
“Reliability is achieved by assigning a sequence number to each octet transmitted, and requiring a positive acknowledgment (ACK) from the receiving party. If the ACK is not received within a timeout interval, the data is retransmitted.”关键翻译:
“可靠性通过为每个传输的字节分配序列号,并要求接收方返回确认(ACK)来实现。如果在超时间隔内未收到ACK,数据将被重传。”
2.2.2.3连接管理
连接管理,包括建立连接与断开连接,建立连接采用的是“三次握手”的方式实现,而断开连接是采用的是“四次挥手”。
![]()
图片引自网络,侵删
三次握手解析:
1. 核心原理
首次握手:客户端发送SYN包(SYN=1),携带随机序列号x
相当于敲门:"有人在吗?我想建立连接"
二次握手:服务端回复SYN+ACK(SYN=1,ACK=1),携带随机序列号y,确认号x+1
相当于回应:"我在!请确认你收到"
三次握手:客户端发送ACK(ACK=1),确认号y+1
相当于确认:"收到!开始通信吧"
2. 解决的问题
防历史连接干扰:
若客户端发送旧SYN包(因网络延迟),服务端回应后,客户端发现序列号不匹配会发送RST终止连接双向通道验证:
第一次握手:服务端确认客户端发送能力正常
第二次握手:客户端确认服务端收发能力正常
第三次握手:服务端确认客户端接收能力正常
资源合理分配:
服务端在第三次握手后才分配连接资源,避免SYN洪水攻击3. 为什么不是两次?
4. 为什么不是四次?
第三次握手已包含数据发送能力验证,额外握手增加延迟无实质收益
现代TCP允许在第三次握手携带应用数据(TCP Fast Open)
四次挥手解析:
1. 核心原理
首次挥手:主动方发送FIN包(FIN=1),序列号为u
相当于说:"我说完了"
二次挥手:被动方回复ACK确认收到
相当于回应:"知道了"
三次挥手:被动方发送FIN包
相当于说:"我也说完了"
四次挥手:主动方回复ACK确认
相当于回应:"好的,再见"
2. 为什么需要四次?
TCP连接是全双工通道,需独立关闭两个方向
关键差异:
被动方需要时间处理:
收到FIN后,被动方可能还有数据要发送(如服务器需发送最后响应)3. TIME_WAIT状态的意义
持续时间:2×MSL(报文最大生存时间,通常60秒)
核心作用:
确保最后一个ACK到达(可重传)
让网络中旧报文失效(防止新连接混淆)
4. 为什么不能是三次?
理论可能:被动方将ACK与FIN合并发送(实际常见)
限制条件:
被动方需立即关闭时才可合并,若有数据发送仍需分开
三、核心问题解答
Q1:为什么建立连接三次,断开却要四次?
建立连接 断开连接 特点 双方无数据传输 双向通道独立关闭 动作 纯控制报文 被动方需处理残留数据 合并 服务端SYN+ACK可合并 被动方ACK+FIN可条件合并 Q2:握手/挥手失败如何处理?
握手失败:
客户端SYN无响应 → 指数退避重试(默认重试6次)
服务端SYN+ACK无ACK → 重试5次(tcp_synack_retries)
挥手失败:
主动方FIN无ACK → 重传FIN(tcp_orphan_retries)
被动方FIN无ACK → 重传FIN(tcp_max_orphans)
2.2.2.4滑动窗口
想必大家听到这个词也是耳熟能详了,算法当中滑动窗口可是鼎鼎大名,但事实上算法上的滑动窗口就是来源于TCP中的~,因为TCP在保证可靠性的时候,付出了效率的代价,所以滑动窗口的设计就是为了提高点效率~
一、滑动窗口是什么?
通俗比喻:
把网络想象成一条流水线,滑动窗口就是允许连续作业的区域
窗口大小 = 流水线可容纳的未完成品数量
ACK到达 = 完成品离开流水线,新原料可加入
技术定义:
发送方维护的连续发送数据范围
窗口内的数据可无需确认直接发送
窗口随ACK到达向右滑动
二、核心组成结构
关键指针:
SND.UNA
:滑动窗口左边界(Send Unacknowledged)
SND.NXT
:下一个要发送的数据位置(Send Next)窗口大小 =
WIN.END - SND.UNA
(动态变化)工作流程:
初始状态:窗口覆盖字节1-200
发送字节1-100 → 进入已发未确认区域
收到ACK=101 → 窗口向右滑动,101-200变为可发送区
新窗口覆盖101-300 → 继续发送新数据
三、滑动窗口四大核心作用
流量控制
接收方通过窗口字段通告可用缓冲区
发送方动态调整发送速率
可靠传输
窗口内数据必须被确认
未确认数据会重传
拥塞控制
拥塞窗口(cwnd)限制最大发送量
与通告窗口取最小值作为实际窗口
提升吞吐量
允许连续发送多个数据包
消除停等协议的效率瓶颈
2.2.2.5流量控制
滑动窗口是在可靠性基础上提高效率,滑动窗口窗口越大,效率就越高,但是也不能无限大,太大了会影响到可能性,接收方的处理能力是有限的。
一、流量控制本质
通俗比喻:
接收方是水桶(缓冲区),发送方是水管。流量控制就是动态调节水龙头开度,保证水不溢出。
水桶大小 = 接收窗口(rwnd)
水流量 = 发送速率
定义:
接收方通过TCP头部通告接收窗口(rwnd)
发送方保证:
已发送未确认数据量 ≤ rwnd
动态平衡点:
rwnd = 接收缓冲区剩余空间
二、工作流程全景解析
阶段1:正常数据传输
接收方缓冲区:总大小64KB ┌───────────────┬───────────────┐ │ 已处理数据30KB │ 剩余空间34KB │ → 通告rwnd=34KB └───────────────┴───────────────┘ 发送方行为: - 连续发送34KB数据 - 等待数据确认
阶段2:缓冲区趋近饱和
接收方状态: ┌────────────────┬──────┐ │ 待处理数据62KB │ 剩余2KB │ └────────────────┴──────┘ 处理策略: if(剩余空间 < min(MSS, 缓冲区/2)) 通告 rwnd=0 // 激活零窗口保护
阶段3:零窗口处理(关键!)
当rwnd=0时触发特殊流程:
发送方行为:
立即停止发送应用数据
启动持续计时器(默认5秒)
定时发送1字节探测包(序列号=最后字节+1)
接收方响应:
若缓冲区仍满 → 回复rwnd=0
若缓冲区释放 → 回复最新rwnd值
示例时间线: T0: 接收方通告rwnd=0 T5: 发送方发送探测包(Seq=1001) T5: 接收方仍满 → 回复ACK=1001, rwnd=0 T10: 发送方再次探测 T10: 接收方已释放20KB → 回复ACK=1001, rwnd=20480 T10: 发送方立即发送20KB数据
2.2.2.6拥塞控制
流量控制是依据接收方处理能力,进行限制的。(根据缓冲区的空余空间来定量衡量)
拥塞控制是依据传输链路的转发能力,进行限制的.
核心使命:在网络带宽未知的情况下,动态探测可用带宽,避免因过度发送导致全网瘫痪。
一、拥塞控制本质:网络资源的公平竞争
核心矛盾:
发送方期望:尽可能占用更多带宽
网络承载极限:路由器缓冲区溢出 → 全网丢包 → 吞吐量暴跌
下文的讲解配合上面的图片食用效果更佳哦~
二、四大核心算法详解
1. 慢启动(Slow Start)
探测逻辑:指数增长快速逼近网络瓶颈
运作流程:
初始
cwnd = 1 MSS
(约1460字节)每RTT(往返时间)窗口翻倍
直到触发:
到达慢启动阈值(ssthresh)
发生丢包(超时/重复ACK)
~
2. 拥塞避免(Congestion Avoidance)
保守增长:线性增加避免突破瓶颈
本质:每RTT增加1个MSS
RTT内收到N个ACK → 每个ACK增加 1/N MSS
~
3. 快重传(Fast Retransmit)
丢包判定:收到3个重复ACK(非超时)
~
4. 快恢复(Fast Recovery)
优化策略:丢包后避免回归慢启动
重传丢失包(快重传触发)
设置
ssthresh = cwnd/2
cwnd = ssthresh + 3 MSS
(补偿重复ACK)进入拥塞避免阶段
3.拥塞控制本质总结
- 1. 慢启动:指数探底 → 快速逼近网络瓶颈
- 2. 拥塞避免:线性爬坡 → 谨慎试探上限
- 3. 快重传/恢复:丢包应急机制 → 避免全局崩溃
- 4. BBR革命:基于模型而非启发 → 直接控制带宽与时延
2.2.2.7延迟应答
默认情况下,接收方都是在收到数据报第一瞬间,就返回 ack,但是可以通过延时返回 ack 的方式来提高效率~~(即利用延时时间,赶紧消费队列中国的数据)
核心目标:减少ACK报文数量,提升网络吞吐量,同时保持TCP可靠性。
一、延迟应答的本质
技术悖论:
传统模式:每收到一个数据包立即回复ACK → 可靠性高但效率低
延迟应答:短暂等待后再回复ACK → 减少报文数量,提升有效带宽
通俗比喻:
把ACK想象成快递签收回执:
立即签收:每到一个包裹就发回执(可靠但快递员跑断腿)
延迟签收:等几个包裹一起到,合并发一次回执(高效且省资源)
二、核心工作原理
1. 标准ACK机制的问题
sequenceDiagram Sender->>Receiver: 数据包1 Receiver->>Sender: ACK1(立即回复) Sender->>Receiver: 数据包2 Receiver->>Sender: ACK2(立即回复) Sender->>Receiver: 数据包3 Receiver->>Sender: ACK3(立即回复)
缺陷:ACK报文占比过高(50%带宽浪费)
2. 延迟应答的优化
sequenceDiagram Sender->>Receiver: 数据包1 Receiver->>Receiver: 启动延迟计时器(200ms) Sender->>Receiver: 数据包2 Receiver->>Receiver: 重置计时器 Sender->>Receiver: 数据包3 Receiver->>Sender: 合并ACK1+2+3(等待超时)
优势:
ACK数量减少50%-70%
允许接收方在ACK中携带更大的窗口通告
三、触发条件与实现逻辑
1. 操作系统级规则
操作系统 默认延迟时间 最大延迟 其他条件 Linux 40ms 200ms 每2个包强制ACK Windows 15ms 200ms 收到>1个MSS时立即ACK macOS 100ms 200ms 窗口变化超过10% 2. 强制ACK场景(立即发送)
收到乱序报文(触发快重传)
接收缓冲区满(通告窗口=0)
收到紧急数据(URG标志)
延迟计时器超时(默认40ms)
3. 延迟优化逻辑
if (收到新数据) { if (未启动延迟计时器) { 启动计时器(40ms); } else { 重置计时器; } if (待确认包数 >= 2) { // Linux策略 立即发送ACK; } }
2.2.2.8捎带应答
TCP 已经有了延时应答了,基于延时应答,引入"捎带应答"。返回业务数据的时候,顺便把上次的 ack 给带回去~
如果没有延时应答,返回 ack 的时机和返回响应的时机就是不同时机~~引入了延时应答,ack可以往后延时一定时间,恰好这个时候要返回响应数据,此时就可以把 ack 也代入到响应数据中,一起返回。
一、捎带应答的本质:网络传输的"顺风车"
技术对比:
传输模式 报文数量 带宽利用率 延迟 独立ACK 高(2N) 低(≤70%) 固定RTT/2 捎带ACK 低(N) 高(≥95%) 接近0 通俗比喻:
想象两人对话:
独立ACK:A说"吃了吗?" → B回"收到了" → B再说"吃过了"(冗余确认)
捎带ACK:A说"吃了吗?" → B直接回"吃过了"(隐含确认)
二、触发条件与工作原理
1. 必要条件
双向数据流:通信双方同时存在数据传输需求
时间窗口匹配:ACK生成时,反向数据正在准备发送
延迟应答启用:为捎带创造时间窗口(通常40ms内)
2. 运作流程
sequenceDiagram participant Client participant Server Client->>Server: HTTP请求(PSH,ACK Seq=100 Data="GET /") Note over Server: 生成响应数据(耗时5ms) Note over Server: 收到请求包,标记需ACK=150 Server->>Client: HTTP响应(PSH,ACK Seq=300 Ack=150 Data="200 OK")
关键点:
服务器将ACK=150 搭载 在HTTP响应报文中
节省1个纯ACK包(40字节头部)
3.本质总结
捎带应答是TCP的隐形加速器,默认提升性能15%-30%
核心生效条件:双向数据流 + 延迟应答窗口
协议设计黄金法则:
请求-响应模型优先
响应生成时间 < 延迟ACK超时(40ms)
禁用Nagle算法(
TCP_NODELAY
)避免阻塞监控命令:
nstat -az TcpPureAcks
>tcpdump
>ss -ti
2.2.2.9面向字节流(粘包问题)
我们都知道TCP有一个特点是面向字节流,那么这里就要引入一个问题,粘包问题。通过字节流方式传输,很容易混淆包和包之间的边界,从而接收方无法去区分从哪里到哪里是一个完整的应用层类数据包~
一、粘包问题的本质与定义
粘包现象是TCP协议面向字节流特性引发的特有现象,指接收方从接收缓冲区读取的数据流中,多个应用层消息的字节流粘连成无法区分的连续数据块。其本质源于两大特性:
- 无消息边界:TCP将数据视为连续的字节流,不维护应用层消息的起始与终止标识
- 动态分段机制:TCP根据网络状况(MTU、MSS、滑动窗口)自动切割/合并字节流,与应用程序的写入/读取操作无关
典型表现(以客户端发送"Hello"和"World"为例):
- 理想情况:接收端分两次读取"Hello"和"World"
- 粘包情况:接收端一次性读取"HelloWorld"(正向粘包)或分次读取"Hel"+"loWorld"(边界错位)
二、解决方案
方案类型 实现原理 优点 缺点 定长协议 所有消息固定长度(如512字节),不足补填充符 实现简单,解析效率高 浪费带宽,不适用于变长数据 分隔符协议 用特殊字符(如 \r\n
)标记消息结尾,需转义处理兼容变长数据,直观易调试 需处理内容转义,复杂度较高 在HTTP中,这俩种方案都有体现:
- 1.GET 请求,,没有 body,使用空行,作为结束标记
- 2.POST 请求,有 body 的时候,通过 Content-Length 决定 body 多长~~
三、对比UDP
特性 TCP粘包 UDP无粘包机制 数据单元 无边界的字节流 独立数据报(保留发送边界) 协议层处理 需应用层解析 直接获取完整报文 典型优化方向 协议设计、缓冲区管理 分片重组、应用层重传
自定义应用层协议,做的事情就是这个。解决粘包问题,也是咱们在自定义应用层协议的时候要考虑的问题~当然也有成熟方案,json, protobuf 都已经把粘包解决掉了~~
2.2.2.10异常情况
当然TCP在通信过程中也存在特殊情况~
一、进程崩溃场景分析
现象:当某进程崩溃时(如Java程序抛出未捕获异常),操作系统内核将接管TCP连接资源回收流程。
TCP处理流程:
- 资源回收:内核立即回收进程的PCB(进程控制块),关闭文件描述符表中的Socket资源。
- 四次挥手触发:
- 若进程崩溃时连接处于ESTABLISHED状态,内核自动发送FIN报文启动四次挥手流程。
- 即使进程已终止,内核仍能完成FIN-ACK交换,保证连接正常关闭。
- 特殊场景处理:
- 若进程崩溃时存在未发送数据,内核缓冲区中的数据仍会继续传输(延迟关闭机制)。
- 若接收方在FIN到达前已发送数据,将触发TCP重置机制(RST包)。
应用层感知:
- 对端应用会立即收到EOF(End Of File)信号,read()返回0值。
- 若对端正在发送数据,可能触发ECONNRESET错误(连接被重置)。
二、主机关机场景分析
现象:操作系统执行关机流程时,所有TCP连接进入强制关闭阶段。
TCP处理流程:
- 进程终止阶段:
- Init进程发送SIGTERM信号给所有进程,等待5秒后发送SIGKILL。
- 存活进程有机会发送FIN包完成四次挥手(如数据库事务回滚)。
- 内核级关闭:
- 未完成挥手的连接进入TIME_WAIT状态(2*MSL时间,默认60秒)。
- 关机前未发送的ACK包可能导致对端超时重传(重试次数由tcp_retries2控制)。
- 异常场景:
- 强制关机(长按电源键)等效于停电场景处理。
- 虚拟化环境中可能触发TCP连接迁移(如VMware vMotion)。
三、主机停电(掉电)场景分析
现象:物理断电导致TCP连接完全失去状态维护能力。
TCP处理机制:
- 发送端停电:
- 对端持续发送数据触发超时重传,经历以下阶段:
- 指数退避重传(1s, 3s, 7s, 15s...)
- 达到tcp_retries2阈值(默认15次)后发送RST包
- 应用层收到ECONNRESET错误
- 接收端停电:
发送端通过KeepAlive机制检测:
KeepAlive流程: 1. 空闲7200秒后发送探测包(默认值) 2. 每隔75秒重试,最多9次 3. 判定连接失效总耗时:7200 + 75*9 = 7875秒(约2小时11分)
应用层可通过设置SO_KEEPALIVE优化检测。
四、网线断开场景分析
现象:物理链路中断导致TCP连接失去传输介质。
TCP处理机制:
- 立即检测型断开:
- 交换机端口状态变化触发TCP RST包(需开启LLDP/CDP协议)
- 路由协议更新导致连接重置(OSPF/BGP收敛时间影响)
- 静默断开检测:
- 发送端通过以下机制感知:
连续发送3个KeepAlive探测包无响应 根据RTO(Retransmission Timeout)计算重传超时: math RTO = SRTT + max(G, 4*RTTVAR)
3. 应用层表现:
- 出现"Network is unreachable"或"Host unreachable"错误
- 数据库连接池进入连接熔断状态
网络恢复处理:
- 短时断开(<RTO时间):TCP自动恢复,应用无感知
- 长时断开:
- 应用需实现重连机制(如指数退避算法)
- 使用TCP持久化特性(RFC 5482)防止路由表过期
2.3对比UDP与TCP
对标UDP和TCP就着重在传输的可靠程度与效率进行对比啦~
1. 可靠性
TCP通过确认机制、重传机制、流量控制和拥塞控制确保数据完整性和顺序,适用于对数据完整性要求高的场景(如文件传输、邮件收发)
UDP则不提供确认或重传机制,采用“尽力而为”的交付方式,可靠性较低,但适合实时性要求高的场景(如视频通话、在线游戏)
2. 高效率
UDP因无连接建立、无确认过程和更小的头部开销(仅8字节 vs TCP的20字节)而传输效率更高,延迟更低
TCP的连接管理(三次握手/四次挥手)、错误检查和重传机制增加了开销,导致传输速度较慢。
总结以下~
- 可靠传输:TCP更优,适合数据完整性优先的场景。
- 高效率:UDP更优,适合实时性要求高的场景。
3.小结
今天的分享到这里就结束了,喜欢的小伙伴点点赞点点关注,需要之前所有的源代码可以去我的gitee上就可以啦~你的支持就是对我最大的鼓励,大家加油!
爱吃烤鸡翅的酸菜鱼 (crjs-hao) - Gitee.comhttps://gitee.com/crjs-hao另外最后的最后,欢迎大家加入我的社区哦,初创社区难免经验不足,请大家多多包涵,也欢迎大家前来多多交流。