05.*传输层、TCP(四层)
传输:
数据传输格式:
MAC层
定义了本地局域网设备
的传输行为IP层
定义了整个网络端到端
的传输行为传输层
定义应用程序到应用程序
的传输,基于端口区分- 这两层基本定义了包的特性:
- 网络传输是以包为单位的,
MAC层叫帧
Frame,IP层叫包
Packet,包被分片后叫Fragment,传输层叫段
Segment - MAC层和IP层的我们笼统地称为包。包单独传输,自行选路,在不同的设备封装、解封装,不保证到达
- 网络传输是以包为单位的,
UDP:
特性:
- UDP
不提供可靠交付
,不保证不丢失,不保证按顺序到达 - UDP
不提供拥塞控制
,无论网络情况,应用层想发就发 - UDP
是
面向数据报
的,一个完整报文
得进行收发(无论多大不分片) - UDP 是
无状态
的服务,只管发出去
协议格式:

包长度:该字段保存了 UDP 首部的长度跟数据的长度之和
UDP 数据最大值:
- 最大1472 字节
- 计算:1500(MTU) - IP头(20) - UDP头(8)
- 注意事项:
UDP不会对上层数据做分段,而是直接交给IP层处理
- 如果IP层设置了DF,则UDP数据会被丢弃,IP层发ICMP报文通知发送端
- 如果IP层未设置DF,则UDP会被IP层分成多片,Packet乱序到达后,由IP层进行重组
UDP 应用场景:
- 需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用
- 不需要一对一沟通建立连接,而是可以广播或多播的应用
- 需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩
具体应用:
- QUIC(Quick UDP Internet Connections):
- 在应用层的协议,会自己实现快速连接建立、减少重传时延,自适应拥塞控制
- 包总量较少的通信:如 DNS 、SNMP等
- 视频直播
- 基于UDP实现自己的协议
- 实时游戏
- IoT领域
- 物联网通信协议 Thread
- 移动通信领域:GTP 协议
- 广播通信
TCP:
TCP 连接:
- 建立连接的目的是为了用一定的数据结构来维护双方交互的状态
- TCP连接不是桥,是在码头上增加了
记录人员,核查人员和督导人员
- TCP用四个元组来表示是同一个连接
特性:
- TCP 提供可靠交付(无差错、不丢失、不重复、并且按序到达)
- TCP
是面向
字节流
的(发送的时候发的是一个流,流大小不超过MSS,流可以重组为报文) - TCP 是可以有拥塞控制的,根据网络情况调整发送策略
- TCP 是一个有状态服务(记录发送和接收状态)
TCP 基础概念:
- MSL( Maximum Segment Lifetime):报文最大生存时间
- RTO(Retransmission TimeOut):触发重传的超时时间
- RTT(Round Trip Time):一个数据包从发出去到回来的时间
- CWND( congestion Window):拥塞控制窗口
- SWND(Sliding Window):滑动窗口
TCP 协议格式:

序号
Sequence Number:用来解决网络包乱序问题
确认序号Ack
Acknowledgement Number:用于确认收到
,可以发现是否出现丢包- Ack确认的是收到的
最后一个连续
的包 - SeqNum和Ack以字节数为单位
- Ack = Seq + Len(包大小)
- Ack确认的是收到的
窗口大小
:滑动窗口大小- TCP Flags类型:
SYN
:希望建立连接。握手阶段使用ACK
:确认应答,最初建立连接时的SYN
包之外的包该值都为1
RST
:连接出现异常,必须强制断开FIN
:数据发送结束,希望断开连接。挥手阶段使用。
- TCP Options:
- MSS设置
- SACK设置
- 其他
MSS
:- Max Segment Size,TCP最大分段长度
- TCP对应用层的数据按照TCP认为最合适的大小进行分段,不一定取最大值
- 范围:1 ~ 1420/1460 字节
- 计算:1500 - IP头(20) - TCP头(20) - TCP 选项(0~40)
TCP 可靠性的依赖:
1. 维护连接状态:
TCP状态机
:TCP 握手、挥手过程在内核协议栈进行
,TLS在应用层
三次握手
:- 握手为什么至少三次:
- 原因一:避免历史连接
- 原因二:需要同步
双方
初始序列号 - 原因三:避免资源浪费
- 四次握手的浪费:
- 四次握手的浪费:
四次挥手
:- TCP是全双工的,发送Fin之后还可接收数据。所以发送方和接收方都需要做
Fin
和Ack
TIME_WAIT
- 作用:
- 防止历史连接中的数据,被后续相同的四元组连接错误的接收
- 保证「被动关闭连接」的一方接收到ACK,被正确的关闭
- 一般设为:2MSL
- 2MSL原因:让原连接中在网络上的数据包都被正常处理或者丢弃,避免被后续连接收到且被确认(因为Seq 的循环使用)
- 作用:
四次挥手变成三次挥手
- 挥手特殊说明:
- 挥手过程在内核协议栈进行
- 机器宕机,无法正常挥手
- 进程崩溃,正常挥手
- 挥手过程在内核协议栈进行
2. 顺序问题:
- 初始ISN(Init Sequence Number)
- 每次握手都不一样的原因:
- 避免误收
- 防止篡改
- 如何随机:
- ISN = M + F(localhost, localport, remotehost, remoteport)
M
是一个计时器,这个计时器每隔 4 微秒加 1F
是一个 Hash 算法
- 每次握手都不一样的原因:
- 每个包都带有
SEQ
number 和ACK
number
3. 丢包问题:
- 问题描述:
确保发出去的包最终一定被收到,如果丢失,通过重传机制再次发送
重传机制
:1.超时重传
:- 定义:
以时间驱动重传
。发送端设置定时器 RTO
(Retransmission TimeOut)基于网络环境,RTT是动态计算的- 内核参数设置次数:
tcp_retries2
- TCP RTO 算法:
- 核心:
采样 RTT数据,以及 RTT 的波动范围
SRTT
:平滑的 RTTDevRTR
:平滑的 RTT 与 最新 RTT 的差距(波动)- 具体算法:
- 经典算法:
SRTT
= ( α * SRTT ) + ((1- α) * RTT)) // α [0.8, 0.9]RTO
= min [ UBOUND, max [ LBOUND, (β * SRTT) ]] // β [1.3, 2.0]- UBOUND是最大的timeout时间、LBOUND是最小的timeout时
- Karn / Partridge 算法:
- 忽略重传,不把重传时的RTT 用作采样数据
- 存在问题:重传时的网络情况被忽略导致出现问题无法发现
- Jacobson / Karels 算法(
最优解
):- 解决:因为平滑导致RTT的波动导致网络波动问题被忽略
SRTT
= SRTT + α (RTT – SRTT)DevRTT
= (1-β)DevRTT + β(|RTT-SRTT|)RTO
= µ SRTT + ∂ DevRTT- 其中 α = 0.125,β = 0.25,μ = 1,∂ = 4
- 经典算法:
- 核心:
- 定义:
2.快速重传
(Fast Retransmit):- 定义:
以数据驱动重传
。接收端因为数据没有收到,会重复ACK可能丢失的包(累计确认),发送端连续收到3次
就重传。 - 过程:
- 接收端 Seq 1 到达,
回复Ack 2
- 接收端 Seq 2 没收到,Seq 3
到达了,于是
还是 回复 Ack 2
- 接收端 Seq 4 和 Seq 5 都到了,因为 Seq 2
还没有收到,
还是回复 Ack 2
- 发送端收到了
三个 Ack 2
的确认,知道了 Seq 2 还没有收到,就会在定时器过期之前,重传丢失的 Seq 2
(在开启SACK情况下) - 最后,接收端收到了 Seq 2,此时因为 Seq3,Seq4,Seq5
都收到了,于是回复
Ack 6
- 接收端 Seq 1 到达,
- 定义:
- 重传发生时,应该重传哪些数据:
- 问题:如果只重传ack的那个包,效率低;如果重传ack之后的所有包,资源浪费;
SACK
选择性确认:接收方
额外返回已收到的数据区间
,让发送端有选择的重传,而不必重传已收到的包
D-SACK
Duplicate SACK:接收方
使用SACK告诉发送方自己重复接收到的数据
,用于发送方优化DSACK
可以让发送方知道:- 是发出去的包丢了,还是回来的ACK包丢了
- 是不是自己的timeout太小了,导致重传
- 网络上出现了先发的包后到的情况(又称reordering)
- 网络上是否把包做了复制
4.流量控制问题
(照顾通信对象):
- 问题描述:
无法改变接收者的接收能力,只能调整自身发送速度
滑动窗口
Sliding-Window:
- 作用:协调双端的发送能力和接收能力。让发送端提升发送效率,接收端避免压垮
- 原理:
- 滑动窗口是一个缓存空间,发送方在等到
确认应答返回之前
,必须在缓冲区中保留已发送的数据
。 - 如果按期收到确认应答,此时数据就可以从缓存区清除
- 如果未收到确认应答,此时需要重传数据。
- 滑动窗口是一个缓存空间,发送方在等到
Window
字段:定义了接收端的接收能力
- 发送方滑动窗口示意图:
向右滑动
可用窗口计算:
SND.WND
:发送窗口的大小
(接收方指定)SND.UNA
:已发送但未收到确认
的第一个字节的序列号SND.NXT
:未发送但可发送范围
的第一个字节的序列号可用窗口大小 = SND.WND -(SND.NXT - SND.UNA
- 接收方滑动窗口示意图:
- 计算:
RCV.WND
:接收窗口的大小
RCV.NXT
:期望从发送方发送来的下一个数据字节的序列号
- 计算:
- 接收窗口的大小约等于发送窗口的大小
Zero Window
窗口关闭:- 作用:
接收端通过ACK
告诉发送端
自己的可用窗口大小为0,让发送端不发数据
- 如何结束不可发送状态:
- 收到接收端的ACK告知
window
不为0 - 发送端发ZWP (Zero Window Probe)包主动询问
- 具体做法:
- 发送端主动发送3次ZWP包给接收端询问
window
- 如果不为0,则发送端进行发送
- 如果一直为0,则发送端触发RST
- 发送端主动发送3次ZWP包给接收端询问
- 具体做法:
- 收到接收端的ACK告知
- 作用:
Silly Window Syndrome
糊涂窗口综合症:- 描述:发送端/接收端因为
可用window
过小,发送小于MSS的数据,导致的效率低的问题 - 解决方案:必须同时满足
- 接收端当
window
如果小于 min(MSS,缓存空间/2),直接发送window=0
关闭窗口 - 发送端开启
Nagle’s算法
,将包堆积到一定大小后再发- Nagle’s算法:(再次发送满足以下任意一个)
- 可用
Window
>=MSS && 累积的数据 >= MSS
- 可用
- 收到之前发的包的ACK回复
- 注意事项:
- Nagle算法没有禁止小包发送,只是禁止了
大量的小包发送
- 在发送大量小包的场景,需要关闭该算法 ####
5.
拥塞控制问题
(照顾通信环境):
- Nagle算法没有禁止小包发送,只是禁止了
- Nagle’s算法:(再次发送满足以下任意一个)
- 接收端当
- 描述:发送端/接收端因为
- 问题描述:无法改变网络状况,但可以
控制自己发送的速度,自我牺牲
(在发送端) 拥塞窗口 cwnd
:- 发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的
发送窗口
= min(拥塞窗口
,发送窗口
)
- 拥塞控制主要过程:
慢启动门限 ssthresh
(slow start threshold)
慢启动
:- 核心思想:
刚刚加入网络的连接,一点一点地提速
- 慢启动的算法:
- 初始化cwnd = 1(表明可以传一个MSS大小的数据)
- 每收到一个ACK,cwnd++;
呈线性上升
- 每经过一个RTT,cwnd = cwnd *2 ;
呈指数让升
- 当cwnd >=
ssthresh时,就会进入
拥塞避免算法
(一般来说ssthresh的值是65535字节)
- 核心思想:
拥塞避免
:- 核心思想:
避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。是一个线性上升的算法
- 具体算法:
- 每收到一个ACK时,cwnd = cwnd + 1/cwnd
- 当每过一个RTT时,cwnd++
- 核心思想:
拥塞发生
:- 核心思想:
通过强烈地震荡快速而小心得找到在拥塞时流量的平衡点
基于重传类型分为两种
:- 如果出现
超时重传
:- 修改 sshthresh = cwnd /2
- cwnd 重置为初始值 1
- 进入慢启动过程`
- 如果出现
快速重传
:TCP Tahoe
:- 修改 sshthresh = cwnd /2
- cwnd 重置为初始值 1
- 进入快速恢复算法
TCP Reno
:(更优)- cwnd = cwnd/2
- sshthresh = cwnd
- 进入快速恢复算法
- 如果出现
- 核心思想:
快速恢复
:- 核心思想:
如果收到
3个Duplicated Acks说明网络也不那么糟糕,从而开启恢复
。快速恢复是对拥塞发生后直接进入慢启动的一个优化。 快速重传和快速恢复算法一般同时使用
TCP Reno 快速恢复
:- 过程:
- cwnd = sshthresh + 3(3 的意思是3个确认包被收到了)
- 重传duplicated ACK指定的丢失的数据包
- 如果再收到 duplicated Acks,那么cwnd ++
- 当收到了重传数据的Ack,那么cwnd =
sshthresh,进入
拥塞避免
的算法(收到新的Ack意味着恢复阶段结束,此时需要重新进入之前的拥塞避免状态)
- 和超时重传不一样,没有重置cwnd=1,而是还在比较高的值,后续呈线性增长
- 局限性:
- 该算法依赖3个重复的acks,如果丢失多个包且
未开启SACK
,接收方只会重传duplicated ACK指定这一个包。其他丢失的包会触发超时重传
,这样会将cwnd降低到1/2。
- 该算法依赖3个重复的acks,如果丢失多个包且
- 过程:
TCP New Reno 快速恢复
:- 对TCP
Reno的改进,在
没有SACK的支持
下改进快速恢复算法 - 过程:
- 当接收到3个Duplicated ACKs,进行指定包的快速重传
- 判断重传的包返回的Ack信息能否覆盖发送端已发送未确认的所有数据
- 如果不能覆盖则表示不止一个包丢失,继续重传滑动窗口内未被ack的包,直到所有包正常发出
- 如果可以覆盖,则快速恢复结束
- 示意图:
- 对TCP
Reno的改进,在
- FACK 算法:
- 作用:对重传过程做拥塞流控,防止重传很多数据包,导致本来就很忙的网络就更忙
- 其他快速恢复算法:
- TCP Vegas 拥塞控制算法
- HSTCP(High Speed TCP) 算法
- TCP BIC 算法
- TCP WestWood算法
- 核心思想:
TCP 半连接、全连接队列:
- 半连接队列(
SYN
队列):- 服务端收到
第一次
握手后,会将 sock`加入到这个队列中 - 队列内的sock都是
SYN_RECV
状态
- 服务端收到
- 全连接队列(
ACCEPT
队列):- 服务端收到
第三次
握手后,将半连接队列的 sock取出,放到全连接队列中 - 队列里的sock都是
ESTABLISHED
状态 - 这里面的连接,就等着服务端执行
accept()
后被取出
- 服务端收到
- 图例:
- 底层结构:
- 全连接队列:链表
- 半连接队列:哈希表、hashkey(sourceip+port)
- 查看队列大小:
- 全连接:ss -lnt
- 半连接:netstat -nt | grep -i ‘127.0.0.1:8080’ | grep -i ‘SYN_RECV’ | wc -l
TCP SYN攻击:
* 说明: - 当服务端接收到客户端的 SYN
报文时,会创建一个半连接的对象,然后将其加入到内核的「SYN 队列」; -
接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文; - 服务端接收到
ACK 报文后,从「SYN
队列」取出一个半连接对象,然后创建一个新的连接对象放入到「Accept
队列」; - 应用通过调用
accpet()
socket 接口,从「Accept
队列」取出连接对象。 * 攻击原理: * SYN
攻击方式最直接的表现就会把TCP半连接队列打满
,后续正常SYN报文就会被丢弃,导致客户端无法和服务端建立连接
* 避免方式: - 调大 netdev_max_backlog - 增大 TCP 半连接队列´ - 开启
tcp_syncookies
(资源分配后置) - 减少/合并 SYN+ACK
重传次数
TCP 延迟确认机制:
- 背景:接收方做ACK相应时,没有携带数据报文,协议头却占40字节,传输效率低。
- 作用:TCP
延迟确认,通过
延迟发送Ack,将延迟时间内的多个Ack合并
,提升传输效率
- 具体策略:
- 接收方有响应数据要发送时,
ACK 会随着响应数据一起立刻发送给对方
- 没有响应数据要发送,
ACK 将会延迟一段时间
,以等待是否有响应数据可以一起发送 - 但是如果在延迟等待发送 ACK
期间,对方的第二个数据报文又到达了,这时就会
立刻发送 ACK
- 接收方有响应数据要发送时,
- Nagle 算法
TCP 累计确认机制:
*
解决:接收方的响应Ack是对之前已收到的包的累计确认。当接收方给发送方的某个ACK丢包时,发送方可以通过后续的收到的ACK知道数据已被接收方确认。
* 图中的 ACK 600 确认应答报文丢失,但发送方收到了ACK 700
确认应答,这意味着 700
之前的所有数据
接收方都收到了
。这个模式就叫累计确认
或者累计应答
* 存在的问题: *
如果接收方接受的数据包发生丢失,接收方无法使用累计确认Ack来确认后续的数据包。在这种情况下,接收方需要使用选择性确认SACK
来确认非连续的数据包
TCP 选择性确认机制:SACK
- 问题:发给接收方的包出现丢失,如果只重传ack的那个包,效率低;如果重传ack之后的所有包,资源浪费;
接收方
额外返回已收到的数据区间
,让发送端有选择的重传,而不必重传已收到的包
TCP 快速建立连接:TCP Fast Open
- 作用:
- 减少建立TCP连接所需的往返时间,在首次的SYN包中就携带数据
- 机制说明:
客户端首次连接时进行正常的三次握手
。在这个过程中,服务器会生成特殊的cookie
,在SYN-ACK包中一并发送给客户端当客户端再次与服务器建立连接时
,在初始的SYN包中携带这个cookie
和用户数据
- 服务器在收到这个SYN包,会检查cookie的有效性:
- 如果cookie有效,正常处理
用户数据
,并通过SYN-ACK包对数据和SYN做确认响应 - 无效,则丢弃
用户数据
,只对SYN做确认
- 如果cookie有效,正常处理
- 客户端根据SYN-ACK包,决定是否需要重发
用户数据
- TFO 存在的问题:
- 安全性问题
- 重放攻击
TCP 保活机制:TCP Keepalive
- 作用:用于主动检测一个TCP连接是否仍然有效的机制
- 机制说明:
- 默认不开启
- 需要长连接,数据发送不频繁的场景使用更好
- net.ipv4.tcp_keepalive_time=7200 //没有活动的时间
- net.ipv4.tcp_keepalive_intvl=75 //探测报文发送频率
- net.ipv4.tcp_keepalive_probes=9 //探测报文重试次数
- 过程:
- 当一个TCP连接在
一段时间
内没有任何活动,TCP会发送保活探测报文(Keepalive probe)来检查连接是否仍然有效 - 探测失败会按照一定频率进行重试,如果
多次
探测报文都没有得到响应,TCP会认为连接已经断开,并关闭连接
- 当一个TCP连接在
TCP 挑战确认: TCP Challenge ACK
- 作用:应对TCP
序列号预测攻击
- 机制:
- 当TCP连接的某些行为引发怀疑时(如收到不符合预期的SYN包、ACK包),接收方会发送一个挑战ACK包。这个包包含当前正确的序列号和一个新的确认号,要求发送方确认它们
- 具体流程:
- 客户端和服务端处于
连接状态
- 客户端异常中断,服务端仍处于
连接状态
- 在服务端TCP保活机制未发现客户端断开前,客户端又使用相同的端口号发送SYN包给服务端进行握手
- 连接状态的服务端,收到这个SYN握手报文,会回复一个Ack。Ack携带的是历史连接的Seq
num和Ack num而不是SYN包的。这个包就叫
Challenge ACK
- 客户端收到这个ACK,发现确认号并不是自己期望收到的,于是回复
RST报文
关闭连接
- 客户端和服务端处于
- 实际应用:
- killcx:
- 伪造客户端发送 SYN 报文,服务端收到后就会回复正确「序列号和确认号」的 ACK 报文(Challenge ACK)
- 利用 确认号伪造 RST 报文给服务端
- 利用 序列号伪造 RST 报文给客户端
- killcx:
TCP 总结:
- 顺序问题,稳重不乱
- 丢包问题,承诺靠谱
- 连接维护,有始有终
- 流量控制,把握分寸
- 拥塞控制,知进知退
参考 * https://mp.weixin.qq.com/s/Tc09ovdNacOtnMOMeRc_uA
本文作者:navyum
本文链接:https://www.cnblogs.com/navyum/p/18509329
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步