ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

计算机网络运输层详解:从UDP校验和到TCP拥塞控制

计算机网络运输层详解:从UDP校验和到TCP拥塞控制 简介这份PPT课件面向计算机专业学生与网络初学者系统讲解计算机网络自顶向下方法中的运输层核心内容帮助读者从应用层需求出发理解端到端通信机制。课件围绕运输层服务、多路复用与分解、UDP与TCP协议、可靠数据传输原理、流量控制及拥塞控制等主线展开涵盖rdt1至rdt3协议演进、回退N帧、选择重传、TCP报文段结构、连接管理与吞吐量公平性等关键知识点并配有家庭通信类比帮助理解网络层与运输层的分工。资源包共1个文件为ppt格式大小约1.82MB结构紧凑便于课堂演示与自学复习。目前已有58人学习浏览适合配合教材同步梳理运输层知识框架、备考复习或作为授课参考。1. 运输层到底在解决什么问题从一份自顶向下 PPT 说起很多人复习计算机网络时卡在运输层这一章TCP 三次握手背得滚瓜烂熟一问“为什么需要运输层”就答不上来。这份《计算机网络自顶向下》运输层 PPT 把整章拆成了七个要点——运输层服务、多路复用与分解、UDP、可靠数据传输原理、TCP、拥塞控制原则、TCP 拥塞控制从应用层需求一路推到网络层实现。它适合三类人正在准备计算机网络期末复习的学生、想系统梳理 TCP/UDP 协议栈的开发者、以及需要一份能直接讲的课件素材的讲师。PPT 里用“12 个孩子互相写信”的家庭类比讲网络层与运输层的区别用 rdt1/rdt2/rdt3 逐步构造可靠传输协议这些设计思路比单纯背结论有用得多。下面按“资源是什么 → 怎么用 → 坑在哪”的顺序拆开讲。2. 运输层服务与多路复用分解PPT 里的两张核心图怎么读2.1 运输层与网络层的分工逻辑通信 vs 主机通信PPT 第 3 页用了一个很直观的类比网络层是邮政服务负责把信从一栋房子送到另一栋房子运输层是 Ann 和 Bill 这两个人负责把信分给家里 12 个孩子中的正确那个。这个类比对应到技术概念上就是网络层提供主机到主机的逻辑通信运输层提供进程到进程的逻辑通信。理解这个分工是读懂后续所有内容的前提。网络层只认 IP 地址它不关心数据到了主机之后该交给哪个应用程序。运输层通过端口号来区分同一台主机上的不同进程这就是多路复用与多路分解要解决的问题。PPT 在第 6 页给出了明确定义在发送主机从多个套接字收集数据并用首部封装这叫多路复用在接收主机根据首部字段将段交付给正确的套接字这叫多路分解。这里有一个容易混淆的点UDP 套接字用二元组标识TCP 套接字用四元组标识。PPT 第 8 页和第 10 页分别用图示做了对比。UDP 的二元组是目的 IP 地址目的端口号意味着来自不同源地址但目的端口相同的 UDP 数据报会进入同一个套接字。TCP 的四元组是源 IP 地址源端口号目的 IP 地址目的端口号每个 TCP 连接有独立的套接字。这个差异直接导致了 Web 服务器在处理并发连接时的行为不同——非持久 HTTP 每个请求都会创建新的 TCP 套接字。提示PPT 里用 Java 的 DatagramSocket 举例说明 UDP 套接字的创建方式如果你不熟悉 Java把注意力放在端口号绑定和套接字标识的逻辑上即可语言本身不影响理解。2.2 用 Wireshark 验证复用与分解从抓包看端口号光看 PPT 上的图示容易停留在纸面实际抓一次包会理解得更牢。常见做法是用 Wireshark 抓一次 DNS 查询和一次 HTTP 请求对比 UDP 和 TCP 的首部字段差异。操作步骤如下第一步打开 Wireshark选择正在使用的网卡开始抓包。第二步在终端执行一次 DNS 查询nslookup www.example.com第三步在浏览器访问一个 HTTP 网站然后停止抓包。第四步在 Wireshark 过滤栏输入udp.port 53观察 DNS 查询的 UDP 报文。展开 UDP 首部你会看到源端口是一个随机高位端口比如 54321目的端口是 53。再展开 IP 首部记录源 IP 和目的 IP。第五步在过滤栏输入tcp.port 80观察 HTTP 请求的 TCP 报文。展开 TCP 首部对比源端口、目的端口、序列号、确认号等字段。UDP 首部8 字节 源端口(16bit) | 目的端口(16bit) 长度(16bit) | 检查和(16bit) TCP 首部20 字节起步 源端口(16bit) | 目的端口(16bit) 序列号(32bit) 确认号(32bit) 首部长度|保留|标志位 | 接收窗口 检查和 | 紧急指针 选项可变长度逻辑说明UDP 首部固定 8 字节只包含最基本的端口和校验信息这就是 PPT 第 14 页说的“没有不必要的基本要素”。TCP 首部至少 20 字节多出来的序列号、确认号、标志位、窗口大小等字段正是实现可靠传输、流量控制、连接管理的基础。参数方面源端口通常由操作系统随机分配范围 49152–65535 是动态端口目的端口由服务决定DNS 是 53HTTP 是 80HTTPS 是 443。如果你在抓包时看到目的端口是 53 但协议显示 TCP说明 DNS 走了 TCP 回退通常发生在响应报文超过 512 字节时。3. UDP 协议与校验和PPT 里那个回卷加法到底怎么算3.1 UDP 校验和的二进制运算手算一遍就懂了PPT 第 16 页和第 17 页用了整整两页讲 UDP 校验和但很多人看到那串二进制就跳过了。其实这个计算过程并不复杂核心规则只有两条把数据按 16 位分组做反码加法最高位的进位要回卷加到最低位。拿 PPT 上的例子走一遍。两个 16 位整数相加1111 0011 0011 0011 1110 1010 1010 1010 ----------------------- 1 1101 1101 1101 1101 ← 产生了最高位进位把最高位的 1 回卷加到最低位1101 1101 1101 1101 1 ----------------------- 1101 1101 1101 1110 ← 这就是和然后对和取反码得到校验和1101 1101 1101 1110 取反 0010 0010 0010 0001 ← 校验和接收方收到数据后把所有 16 位字包括校验和字段一起做同样的反码加法如果结果全是 1说明没有检测到差错。用 Python 验证这个计算过程def udp_checksum(data): 计算 UDP 校验和data 为 bytes 类型 if len(data) % 2 ! 0: data b\x00 # 补齐为偶数长度 total 0 for i in range(0, len(data), 2): word (data[i] 8) data[i 1] total word # 回卷进位 if total 0xFFFF: total (total 0xFFFF) 1 return ~total 0xFFFF # 测试对两个 16 位字做校验 test_data bytes([0b11110011, 0b00110011, 0b11101010, 0b10101010]) result udp_checksum(test_data) print(f校验和: {result:016b}) # 输出: 校验和: 0010001000100001逻辑说明total word累加每个 16 位字if total 0xFFFF检测是否产生了第 17 位的进位如果有就把进位加回最低位。最后~total 0xFFFF取反码并保留低 16 位。参数方面data是待校验的字节序列实际使用时需要把 UDP 伪首部源 IP、目的 IP、协议号、UDP 长度也拼进去一起算。注意 Python 的~运算符对整数取反会得到负数所以必须 0xFFFF截断。注意UDP 校验和是可选的在 IPv4 中如果校验和字段为 0 表示发送方没有计算校验和。但在 IPv6 中校验和是强制的。PPT 里没有展开讲这个差异实际抓包时如果看到 UDP 校验和为 0 不要觉得奇怪。3.2 UDP 适用场景什么时候该选它而不是 TCPPPT 第 14 页列出了 UDP 的几个特点无连接、无拥塞控制、首部开销小、不保证可靠交付。这些特点决定了 UDP 的适用场景。流式多媒体应用是 UDP 的典型用户。视频会议和在线游戏对时延敏感偶尔丢一两帧画面比等 TCP 重传导致卡顿要好得多。DNS 查询也用 UDP因为一次查询通常就是一个请求-响应往返建立 TCP 连接的三次握手开销反而更大。SNMP简单网络管理协议同样基于 UDP网络设备的状态查询不需要可靠传输保证。但这不意味着 UDP 不能做可靠传输。PPT 第 15 页提到“经 UDP 的可靠传输在应用层增加可靠性”。常见做法是在应用层实现确认与重传机制比如 QUIC 协议就是在 UDP 之上实现了可靠传输、拥塞控制和多路复用。如果你在开发实时音视频应用底层用 UDP上层自己维护序列号和重传逻辑这是业界很成熟的方案。4. 可靠数据传输原理rdt1 到 rdt3 的演进与流水线协议4.1 从 rdt1 到 rdt3每一步在解决什么问题PPT 第 18 到 20 页给出了可靠数据传输的基本框架然后用 rdt1、rdt2、rdt3 三个版本逐步完善。这个演进过程是整章最有逻辑性的部分理解了它TCP 的可靠传输机制就不需要死记硬背了。rdt1 假设底层信道完全可靠发送方直接把数据发出去接收方直接收。这显然不现实但它是起点。rdt2 引入了比特差错的可能性解决方案是确认ACK和否认NAK。发送方发完数据后等接收方回复收到 ACK 就继续发下一个收到 NAK 就重传。但 rdt2 有一个致命问题如果 ACK 或 NAK 本身在传输中损坏了怎么办发送方分不清收到的是 ACK 还是 NAK就只能重传但重传又会导致接收方收到重复数据。rdt2 的改进版引入了序列号给每个数据包编号 0 或 1。接收方通过序列号判断是否收到了重复包发送方通过序列号判断收到的确认对应哪个包。这就是停等协议的基本形态。rdt3 进一步假设信道可能丢包。如果数据包丢了接收方不会回复任何确认发送方就会一直等。解决方案是引入超时重传发送方发出数据后启动一个定时器如果在设定时间内没收到确认就重传。rdt3 发送方逻辑伪代码 等待上层调用 rdt_send(data) → 创建包(seq, data) → udt_send(pkt) → 启动定时器 等待 ACK/NAK 收到损坏的 ACK/NAK → 重传 → 重启定时器 收到 NAK → 重传 → 重启定时器 收到 ACK 且序号正确 → 停止定时器 → 等待下一个调用 超时 重传 → 重启定时器参数说明序列号只有 0 和 1 两个值因为停等协议每次只发一个包收到确认后才发下一个。定时器超时时间需要根据网络往返时延RTT来设定设太短会导致不必要的重传设太长会导致丢包后恢复慢。实际 TCP 实现中超时时间是根据 RTT 的滑动平均值动态计算的。4.2 流水线协议回退 N 帧与选择重传的取舍rdt3 的问题是效率太低。发送方发一个包就要等一个 RTT如果 RTT 是 30ms那每秒最多传 33 个包。PPT 第 18 页提到的“流水线可靠数据传输协议”就是为解决这个问题允许发送方连续发送多个包而不必等每个包的确认。流水线协议有两种主流实现回退 N 帧Go-Back-N和选择重传Selective Repeat。回退 N 帧的规则是发送方可以连续发 N 个包接收方只按序接收。如果第 3 个包丢了接收方丢弃第 4、5、6 个包发送方超时后从第 3 个包开始全部重传。优点是接收方不需要缓存乱序包实现简单。缺点是丢一个包要重传后面所有包带宽浪费大。选择重传的规则是接收方缓存乱序到达的包只要求发送方重传真正丢失的那个。优点是重传效率高缺点是接收方需要缓存空间实现复杂度更高。对比项回退 N 帧选择重传接收方缓存不需要需要重传范围丢失包及之后所有包仅丢失包发送方窗口NN接收方窗口1N适用场景带宽充裕、实现简单带宽紧张、丢包率高TCP 实际使用的是两者的混合方案累积确认类似回退 N 帧但通过 SACK选择性确认选项支持选择重传。PPT 里没有展开讲 SACK但如果你在 Wireshark 里看到 TCP 选项字段有 SACK Permitted说明双方支持这个扩展。5. TCP 连接管理与拥塞控制三次握手、四次挥手和那些容易翻车的点5.1 TCP 三次握手与四次挥手状态变迁和常见异常PPT 第 5 页和第 10 页涉及 TCP 连接管理虽然篇幅不多但三次握手和四次挥手是考试和面试的高频考点。这里结合抓包实际看到的字段来说。三次握手的过程客户端 服务器 |------ SYN, seqx -----------| |----- SYNACK, seqy, ackx1| |------ ACK, acky1 ---------| | 连接建立 |第一次握手客户端发送 SYN 标志位置 1 的报文随机选择一个初始序列号 x。第二次握手服务器回复 SYN 和 ACK 都置 1 的报文选择自己的初始序列号 y确认号设为 x1。第三次握手客户端发送 ACK 置 1 的报文确认号设为 y1。为什么需要三次而不是两次核心原因是防止已失效的连接请求突然到达服务器。如果只有两次握手一个延迟了很久的 SYN 到达服务器后服务器直接建立连接并等待数据但客户端根本不认这个连接服务器资源就白白浪费了。三次握手让客户端有机会拒绝这个旧连接。四次挥手的过程客户端 服务器 |------ FIN, sequ -----------| |----- ACK, acku1 ----------| |----- FIN, seqw ------------| |------ ACK, ackw1 ---------| | 连接关闭 |注意第二次和第三次之间服务器可能还有数据要发所以 ACK 和 FIN 不能合并这就是四次而不是三次的原因。客户端在发送最后一个 ACK 后进入 TIME_WAIT 状态等待 2MSL报文最大生存时间的两倍才真正关闭。这个等待是为了确保服务器能收到最后的 ACK如果服务器没收到会重发 FIN客户端在 TIME_WAIT 期间还能响应。提示PPT 里没有展开讲 TIME_WAIT 的时长和内核参数调整。实际运维中如果看到大量 TIME_WAIT 连接可以通过调整net.ipv4.tcp_tw_reuse参数来复用但生产环境改这个参数需要谨慎评估。5.2 TCP 拥塞控制慢启动、拥塞避免、快速重传与快速恢复PPT 第 7 页和第 18 页提到了拥塞控制的原则和 TCP 拥塞控制机制。TCP 的拥塞控制有四个核心算法它们协同工作来动态调整发送速率。慢启动连接刚建立时拥塞窗口cwnd初始值为 1 个 MSS最大报文段长度。每收到一个 ACKcwnd 翻倍。所以发送速率呈指数增长1、2、4、8、16……直到达到慢启动阈值ssthresh或检测到丢包。拥塞避免当 cwnd 达到 ssthresh 后改为线性增长每个 RTT 只增加 1 个 MSS。这样做的目的是在接近网络容量时放慢增速避免突然过载。快速重传当发送方收到三个重复 ACK 时不等超时就直接重传丢失的报文段。这比等超时快得多因为超时时间通常是几百毫秒而三个重复 ACK 可能在几十毫秒内就到达了。快速恢复配合快速重传使用。当检测到丢包时ssthresh 设为当前 cwnd 的一半cwnd 设为 ssthresh 加上 3 个 MSS然后进入拥塞避免阶段。这比慢启动从 1 开始要快得多。TCP 拥塞控制状态机简化 慢启动 cwnd ssthresh → 每 ACK 翻倍 cwnd ssthresh → 进入拥塞避免 拥塞避免 每 RTT cwnd 1 MSS 超时 → ssthresh cwnd/2, cwnd 1, 回到慢启动 三个重复 ACK → ssthresh cwnd/2, cwnd ssthresh 3, 进入快速恢复 快速恢复 收到新 ACK → cwnd ssthresh, 进入拥塞避免参数说明MSS 通常由链路层 MTU 决定以太网 MTU 是 1500 字节减去 IP 首部 20 字节和 TCP 首部 20 字节MSS 就是 1460 字节。ssthresh 的初始值通常设为一个较大的数比如 65535 字节这样慢启动可以持续到检测到第一次拥塞。实际 Linux 内核中这些参数可以通过sysctl查看和调整。6. 避坑与排查复习和实操中容易翻车的五个点6.1 把 UDP 校验和当成必选项现象抓包看到 UDP 校验和字段为 0以为抓包工具出错了。原因IPv4 中 UDP 校验和是可选的发送方可以不计算填 0 表示放弃校验。这不是错误是协议允许的行为。解决在 Wireshark 中看到校验和 0 不用管关注其他字段即可。如果需要强制校验在应用层自己加校验逻辑。6.2 混淆 TCP 套接字和 UDP 套接字的标识方式现象写了一个 UDP 服务器发现来自不同客户端的数据报都进了同一个套接字无法区分客户端。原因UDP 套接字用二元组目的 IP目的端口标识不区分源地址。TCP 用四元组每个连接独立。解决如果需要在 UDP 上区分客户端在应用层数据里带上客户端标识自己维护映射表。或者改用 TCP。6.3 慢启动阶段误以为网络出了问题现象用 iperf3 测试 TCP 吞吐量前几秒速率很低之后才升上来。原因这是慢启动的正常行为。cwnd 从 1 开始指数增长需要几个 RTT 才能达到链路容量。解决测试时加-t 30参数跑够时间或者用-O 5跳过前 5 秒的统计。不要看到前几秒速率低就以为配置有问题。6.4 回退 N 帧和选择重传的窗口大小搞混现象考试时遇到计算题不确定发送窗口和接收窗口该设多大。原因回退 N 帧的接收窗口固定为 1选择重传的接收窗口等于发送窗口。序列号空间必须大于发送窗口加接收窗口。解决记住公式——回退 N 帧需要序列号空间 ≥ N1选择重传需要序列号空间 ≥ 2N。如果序列号用 k 位表示回退 N 帧的 N ≤ 2^k - 1选择重传的 N ≤ 2^(k-1)。6.5 TIME_WAIT 状态导致端口耗尽现象服务器上大量连接处于 TIME_WAIT新连接无法建立。原因主动关闭方需要等待 2MSL 才能释放端口。高并发短连接场景下端口被快速消耗。解决让客户端主动关闭而不是服务器主动关闭或者调整内核参数net.ipv4.tcp_tw_reuse和net.ipv4.tcp_max_tw_buckets。但改参数前先确认业务是否真的需要短连接长连接加连接池通常是更好的方案。7. 用 PPT 里的 rdt 模型做一次协议模拟从伪代码到可运行验证PPT 里 rdt1 到 rdt3 的演进是理解可靠传输最好的素材但光看静态图容易忘。我一般会写一个简单的模拟器把发送方和接收方的状态机跑起来人为制造丢包和比特差错观察协议行为。下面用 Python 实现一个 rdt3 的简化模拟。import random import time class RDT3Sender: def __init__(self, channel): self.channel channel self.seq 0 self.timer_active False self.timer_start 0 self.timeout 0.5 # 500ms 超时 def send(self, data): pkt {seq: self.seq, data: data, checksum: hash(data) % 256} self.channel.send(pkt) self.timer_active True self.timer_start time.time() print(f[发送方] 发送包 seq{self.seq}, data{data}) def receive_ack(self, ack): if ack is None: print([发送方] 收到损坏的 ACK重传) self.send(self.last_data) return if ack[ack] self.seq: self.timer_active False self.seq 1 - self.seq # 翻转序列号 print(f[发送方] 收到正确 ACK准备发送下一个) else: print(f[发送方] ACK 序号不匹配忽略) def check_timeout(self): if self.timer_active and (time.time() - self.timer_start) self.timeout: print([发送方] 超时重传) self.send(self.last_data) class RDT3Receiver: def __init__(self, channel): self.channel channel self.expected_seq 0 def receive(self, pkt): if pkt is None: print([接收方] 收到损坏的包发送 NAK) self.channel.send_ack({ack: None}) return if pkt[seq] self.expected_seq: print(f[接收方] 收到正确包 seq{pkt[seq]}, data{pkt[data]}) self.channel.send_ack({ack: pkt[seq]}) self.expected_seq 1 - self.expected_seq else: print(f[接收方] 收到重复包 seq{pkt[seq]}发送 ACK) self.channel.send_ack({ack: pkt[seq]}) class UnreliableChannel: def __init__(self, loss_rate0.3, corrupt_rate0.2): self.loss_rate loss_rate self.corrupt_rate corrupt_rate def send(self, pkt): if random.random() self.loss_rate: print([信道] 包丢失) return if random.random() self.corrupt_rate: print([信道] 包损坏) self.receiver.receive(None) return self.receiver.receive(pkt) def send_ack(self, ack): if random.random() self.loss_rate: print([信道] ACK 丢失) self.sender.receive_ack(None) return self.sender.receive_ack(ack) # 运行模拟 channel UnreliableChannel(loss_rate0.3, corrupt_rate0.2) sender RDT3Sender(channel) receiver RDT3Receiver(channel) channel.sender sender channel.receiver receiver sender.last_data Hello sender.send(Hello)逻辑说明RDT3Sender维护序列号和定时器发送后等待 ACK。RDT3Receiver维护期望序列号收到正确包就交付并回复 ACK收到重复包也回复 ACK防止发送方一直重传。UnreliableChannel模拟丢包和比特差错丢包直接丢弃损坏则把包变成 None 传给接收方。参数方面loss_rate和corrupt_rate可以调整来观察不同网络条件下的协议行为。把loss_rate设成 0.5 你会看到大量重传设成 0 则一次成功。这个模拟器跑一遍rdt3 的超时重传和序列号机制就刻在脑子里了。从那以后我每次复习运输层都会先把这个模拟器跑一遍再去看 TCP 的实际实现理解起来顺畅很多。希望帮到你。本文还有配套的精品资源点击获取
返回列表