☰
计算机网络自顶向下运输层:TCP/UDP抓包与代码实战
2026/9/29 13:48:17 网站建设 项目流程

简介:这份PPT课件面向计算机专业学生及网络初学者,系统讲解计算机网络体系结构中的运输层核心知识,帮助读者从自顶向下的视角理解端到端通信机制。内容围绕运输层服务展开,涵盖多路复用与多路分解、UDP无连接传输、TCP面向连接传输、可靠数据传输原理、流量控制、拥塞控制原则及TCP拥塞控制机制等模块,并配有rdt1至rdt3协议演进、回退N帧、选择重传、TCP吞吐量与公平性等具体知识点,适合课堂学习与期末复习对照使用。资源包共1个文件,为ppt格式,压缩包大小约1.82MB,结构紧凑便于直接打开浏览。目前已有58人学习下载,可作为运输层章节的配套讲义,帮助读者梳理协议运行逻辑、掌握TCP与UDP的差异及拥塞控制思路。

1. 从一份“计算机网络自顶向下.ppt”说起:运输层到底该怎么学才不飘

很多人手里都有一份“计算机网络自顶向下.ppt”,可能是老师发的课件,也可能是从各种渠道攒来的复习资料。打开一看,应用层、运输层、网络层、链路层一路铺开,图多、箭头多、术语密,翻到运输层那一叠幻灯片时,TCP 三次握手、四次挥手、UDP 无连接、端口复用这些词全挤在一起,看着都认识,合上就说不清。问题不在你记性差,而在这份 PPT 的定位是“课堂提纲”,它负责把知识铺开,不负责把一条报文从你的键盘送到对面主机再送回来。真正让运输层立住的,是把它当成一个可观测、可复现的系统:TCP 连接怎么建、UDP 数据报怎么发、端口怎么复用、丢包和乱序长什么样。这篇笔记就围绕这份 PPT 里运输层那部分,把概念、抓包验证、代码复现和踩坑一次讲透,适合正在啃自顶向下教材、准备考试,或者刚上手写网络程序的人。

2. 运输层在自顶向下体系里的位置:为什么 TCP 和 UDP 是两条路

自顶向下的讲法是从应用层往下走,到了运输层,你第一次真正碰到“端到端”这件事。应用层的 HTTP、DNS、视频流,最终都要交给运输层,由它决定用 TCP 还是 UDP 把数据送出去。这一章先把两条路的定位讲清楚,再落到端口、复用和分用这些 PPT 里一笔带过但考试和写代码都绕不开的点。

2.1 TCP 与 UDP 的分工:可靠字节流 vs 数据报

TCP 提供的是面向连接的、可靠的、基于字节流的服务。它把应用层交下来的数据看成没有边界的字节流,自己切段、编号、确认、重传、排序,最后按序交给对端应用。UDP 提供的是无连接的、尽最大努力交付的数据报服务,它保留应用层报文的边界,发出去就不管了,丢不丢、乱不乱、重不重,运输层不负责。

这个差别决定了选型。文件传输、网页、邮件这类不能丢数据的场景用 TCP;实时语音、视频、DNS 查询、游戏状态同步这类“宁可丢一点也不能等”的场景用 UDP。PPT 上通常只写一句“TCP 可靠、UDP 不可靠”,但真正要理解的是:可靠性不是免费的,它靠序号、确认、重传、窗口、拥塞控制一整套机制换来的,代价是延迟和开销。

对比项TCPUDP
连接面向连接,需三次握手无连接,直接发
可靠性确认重传,按序交付不保证到达和顺序
数据边界字节流,无边界保留报文边界
首部开销20 字节起8 字节
典型应用HTTP、FTP、SMTPDNS、DHCP、实时音视频

2.2 端口、复用与分用:一台主机怎么同时跑这么多连接

运输层用端口号来区分同一台主机上的不同应用进程。16 位端口号,0 到 65535,其中 0 到 1023 是熟知端口,1024 到 49151 是登记端口,49152 到 65535 是临时端口。复用是发送方多个应用进程共用运输层,分用是接收方运输层根据端口把数据交给正确的进程。

这里有个常被忽略的点:TCP 连接由四元组唯一标识,源 IP、源端口、目的 IP、目的端口。所以同一台主机上,一个服务监听 80 端口,可以同时接受成千上万个连接,因为每个连接的四元组不同。UDP 没有连接,分用只看目的端口,所以两个进程不能绑同一个 UDP 端口,但可以用 SO_REUSEADDR 做地址复用,这个后面踩坑章节会细说。

提示:考试里常问“一个 TCP 连接能不能同时被两个进程使用”,答案是不能,四元组唯一;但一个监听套接字可以派生出多个已连接套接字。

2.3 从 PPT 到抓包:把三次握手和四次挥手看成报文序列

PPT 上画的三次握手是 SYN、SYN+ACK、ACK,四次挥手是 FIN、ACK、FIN、ACK。光背顺序没用,得知道每个报文里带了什么。SYN 报文会带初始序号 seq=x,SYN+ACK 带 seq=y、ack=x+1,第三次 ACK 带 ack=y+1。挥手时 FIN 表示“我没有数据要发了”,但还能收,所以是半关闭,两边都发 FIN 才彻底关。

用 tcpdump 或 Wireshark 抓一次本机访问网页的过程,你能直接看到这些报文。下面这条命令抓本机 80 或 443 端口的 TCP 报文,-n 不解析域名,-S 显示绝对序号,方便对照 PPT 上的 x、y。

# 抓取与 93.184.216.34 的 TCP 交互,只看前 20 个包 sudo tcpdump -i any -n -S 'tcp and host 93.184.216.34' -c 20

参数说明:-i any 监听所有网卡,-n 不做 DNS 反解,-S 显示绝对序号而不是相对序号,-c 20 抓满 20 个包退出。抓完对照 Flags 字段,[S] 是 SYN,[S.] 是 SYN+ACK,[.] 是 ACK,[F.] 是 FIN+ACK。这一步做完,PPT 上那几张握手图就不再是抽象箭头了。

3. 用代码把 TCP 和 UDP 跑起来:最小可复现实验

光看抓包还不够,自己写一遍才知道 API 和协议字段怎么对应。这一章用 Python 写最小 TCP 服务端客户端和 UDP 收发,再讲 iperf3 打流和 UDP 分片这两个高频场景。代码都能直接跑,参数怎么调、失败看什么,一并说清。

3.1 最小 TCP 回显服务:accept、recv、send 的对应关系

先写服务端。socket 创建、bind、listen、accept、recv、send、close,这几步和 PPT 上运输层提供的接口一一对应。bind 绑定端口就是分用的入口,listen 把套接字变成被动打开,accept 返回的是新的已连接套接字,四元组已经确定。

import socket # 创建 TCP 套接字,AF_INET 表示 IPv4,SOCK_STREAM 表示字节流 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 允许地址复用,避免重启时 TIME_WAIT 导致 bind 失败 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('0.0.0.0', 9000)) # 绑定所有网卡的 9000 端口 server.listen(5) # 半连接队列长度 5 print('listening on 9000') while True: conn, addr = server.accept() # 阻塞直到有连接,返回新套接字和对端地址 print('connected from', addr) data = conn.recv(1024) # 最多收 1024 字节,TCP 是字节流,不保证一次收完 if data: conn.sendall(data) # 回显,sendall 保证全部发完 conn.close() # 关闭已连接套接字,触发四次挥手

逻辑说明:accept 返回的 conn 是新的套接字,原 server 继续监听。recv 返回空字节表示对端关闭。sendall 内部循环调用 send,直到所有数据进入发送缓冲区。参数上,listen 的 5 是 backlog,Linux 下实际队列长度还受 somaxconn 影响,高并发场景要调大。

客户端对应写:

import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1', 9000)) # 触发三次握手 client.sendall(b'hello transport layer') resp = client.recv(1024) print('echo:', resp) client.close() # 触发四次挥手

connect 返回时三次握手完成,close 时如果还有数据没发完,内核会尝试发完再挥手。想看握手和挥手,就在客户端 connect 前后用 tcpdump 抓 9000 端口。

3.2 最小 UDP 收发:sendto、recvfrom 与报文边界

UDP 的 API 更直接,没有连接,每次 sendto 指定目的地址,recvfrom 返回数据和来源地址。注意 UDP 保留报文边界,一次 sendto 对应一次 recvfrom,缓冲区小了会截断。

import socket # UDP 服务端 server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server.bind(('0.0.0.0', 9001)) while True: data, addr = server.recvfrom(2048) # 一次收一个数据报 print('from', addr, 'len', len(data)) server.sendto(data.upper(), addr) # 原样返回大写
import socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) client.sendto(b'udp test', ('127.0.0.1', 9001)) data, addr = client.recvfrom(2048) print('reply:', data) client.close()

参数说明:recvfrom 的 2048 是接收缓冲区大小,如果数据报大于这个值,多余部分被丢弃,且没有通知。UDP 首部里的长度字段是 16 位,所以一个 UDP 数据报最大 65535 字节,减去 IP 首部和 UDP 首部,实际载荷更小。超过 MTU 会触发 IP 分片,这就是热词里“udp 划分 ip 数据报片”的来源。

3.3 iperf3 打流与 UDP 分片观察

测吞吐常用 iperf3。TCP 模式直接跑,UDP 模式要指定带宽,否则默认 1Mbps 看不出问题。

# 服务端 iperf3 -s # 客户端 TCP 测试,跑 10 秒 iperf3 -c 192.168.1.10 -t 10 # 客户端 UDP 测试,目标 100Mbps,报文长度 1400 iperf3 -c 192.168.1.10 -u -b 100M -l 1400 -t 10

参数说明:-u 用 UDP,-b 指定目标带宽,-l 指定报文长度。把 -l 设成 1400 通常不触发分片,设成 3000 就会分片。想看分片,在服务端抓包,过滤udp and port 5201,Wireshark 里会看到 IP 层有 “Fragmented IP protocol” 标记。分片带来的问题是:任何一片丢了,整个数据报都废,重传只能靠应用层,所以实时音视频一般把载荷控制在 MTU 以下,避免分片。

注意:UDP 分片后,接收端要等所有片到齐才能重组,重组有超时,超时整个数据报丢弃。这就是为什么大 UDP 包在弱网下丢包率会陡增。

4. 运输层避坑与排查:那些 PPT 不会写的翻车现场

这一章全是血泪经验。PPT 讲原理,不讲你 bind 失败、连接卡死、UDP 收不到包时该看什么。下面五条按“现象 → 原因 → 解决”写,都是实际调试中反复遇到的。

4.1 bind 报 Address already in use

现象:服务端重启时 bind 报OSError: [Errno 98] Address already in use,明明进程已经退了。

原因:TCP 主动关闭方会进入 TIME_WAIT,持续 2MSL,通常是 60 秒。这段时间四元组还被内核占着,新进程 bind 同一端口就失败。

解决:设置 SO_REUSEADDR,允许绑定处于 TIME_WAIT 的地址。代码里server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)放在 bind 之前。注意 SO_REUSEADDR 和 SO_REUSEPORT 不同,后者允许多个进程绑同一端口做负载均衡,Linux 3.9 以后支持,但行为要谨慎。

4.2 TCP 连接建立成功但收不到数据

现象:客户端 connect 成功,send 也返回了,服务端 accept 到了连接,但 recv 一直阻塞。

原因:常见有两种。一是服务端 accept 后没有继续 recv,数据在接收缓冲区里;二是客户端 send 后没发 FIN,服务端 recv 在等更多数据。TCP 是字节流,recv 返回 0 才表示对端关闭,返回正数只表示收到了这么多字节,不表示消息结束。

解决:应用层自己定边界,比如固定长度、长度前缀或分隔符。调试时用ss -tnp看连接状态和收发队列,ss -tn state established能看到 Recv-Q 和 Send-Q,队列有积压说明对端没读。

4.3 UDP 收不到包但抓包能看到

现象:tcpdump 抓到 UDP 包到了网卡,但应用层 recvfrom 一直阻塞。

原因:可能是防火墙拦了,也可能是绑定了错误的地址。bind 到 127.0.0.1 就只能收本机回环的包,外部网卡来的包收不到。还有可能是接收缓冲区溢出,内核直接丢包,应用层无感知。

解决:先确认 bind 地址是 0.0.0.0 还是具体网卡 IP。用ss -ulnp看 UDP 监听状态和 Recv-Q,如果 Recv-Q 经常非零,说明应用读得慢,调大 SO_RCVBUF。防火墙用 iptables 或 firewalld 查对应端口。

4.4 三次握手成功但 TLS 握手失败

现象:TCP 层抓包看到三次握手正常,但应用层报连接重置或超时。

原因:这通常不是运输层问题,而是应用层协议不匹配,比如客户端发 HTTP 请求,服务端等 TLS ClientHello。也可能是中间设备改了报文。

解决:抓包看握手后第一个应用层报文的内容,确认协议一致。运输层只负责把字节送到,不关心里面是什么。排查时先分层,TCP 通了就往上查。

4.5 大量 TIME_WAIT 导致端口耗尽

现象:压测时客户端报Cannot assign requested address,ss 一看大量 TIME_WAIT。

原因:客户端主动关闭连接,每个连接占一个临时端口,TIME_WAIT 期间不能复用,端口范围有限,高并发短连接很快耗尽。

解决:用连接池复用连接,或者让服务端主动关闭。也可以调内核参数net.ipv4.tcp_tw_reuse=1允许复用 TIME_WAIT 连接,但只在客户端有效且时间戳开启时安全。根本办法还是减少短连接。

5. 把运输层学扎实的进阶技巧:从抓包到协议栈参数

学到这一步,PPT 上的知识点已经能对应到实际报文和代码了。再往上走,有两个方向值得投入:一是用抓包和工具验证每一个机制,二是理解操作系统协议栈的可调参数,知道边界在哪。

先说验证。三次握手、四次挥手、重传、滑动窗口、拥塞控制,这些都能在 Wireshark 里看到。重传看 TCP Retransmission 标记,滑动窗口看 Window Size 字段,拥塞控制看 cwnd 变化,Linux 下用ss -ti能看到每个连接的 cwnd、rtt、retrans 计数。下面这条命令看已建立连接的详细 TCP 信息:

ss -ti state established '( dport = :443 or sport = :443 )'

输出里的 cwnd、rtt、retrans 就是协议栈内部状态,比看 PPT 上的曲线直观得多。想复现拥塞控制,用 iperf3 打流同时用 ss 观察 cwnd 从慢启动到拥塞避免的变化,丢包时 cwnd 会掉,这就是 PPT 上那张锯齿图的来源。

再说参数。Linux 协议栈有一堆可调项,sysctl -a | grep tcp能看到。常用的几个:net.ipv4.tcp_syncookies防 SYN Flood,net.ipv4.tcp_max_syn_backlog调半连接队列,net.core.somaxconn调 accept 队列,net.ipv4.tcp_rmem和tcp_wmem调收发缓冲区。这些参数不是背的,是遇到问题时查的。比如高并发下 accept 慢,先看 somaxconn 和 backlog 是否够。

参数作用常见调整
net.core.somaxconnaccept 队列上限高并发调到 4096
net.ipv4.tcp_max_syn_backlog半连接队列防 SYN Flood 调大
net.ipv4.tcp_tw_reuse复用 TIME_WAIT客户端短连接可开
net.ipv4.tcp_rmem接收缓冲区高带宽延迟积调大

最后说一个我自己的习惯:每学一个运输层机制,就写一段最小代码或抓一次包去验证它。三次握手就抓 connect,四次挥手就抓 close,UDP 分片就把报文设大再抓。PPT 是地图,抓包和代码是走路,走一遍才知道哪里是坑。我当初就是靠反复抓包才把 TIME_WAIT 和 CLOSE_WAIT 分清,CLOSE_WAIT 多说明应用没 close,TIME_WAIT 多说明主动关闭太频繁,这两个状态在 ss 里一眼就能看出来。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询