TCP 和 UDP 是计算机网络里最基础的一对传输层协议。很多开发者背过“TCP 面向连接、可靠、慢;UDP 无连接、不可靠、快”的口诀,但一到实际定位问题就发懵:为什么bind: only one usage of each socket address?为什么curl: (35) tcp connection reset by peer?为什么 UDP 测试工具收不到包,Wireshark 却显示数据已经发出去了?这篇文章会把 TCP 与 UDP 的机制、报文、适用场景、调试工具、常见报错逐个拆开讲清楚。
先说核心结论:TCP 是传输控制协议,适合对数据完整性要求极高的业务;UDP 是用户数据报协议,适合低延迟、可容忍少量丢包的业务。现实中很多服务协议,比如 HTTP/HTTPS、WebSocket、Modbus TCP、SSH 走的是 TCP;而视频直播、语音通话、实时游戏同步、SNMP、组播经常走 UDP。下面从能力对比、协议机制、实际验证、排错方法四个维度展开。
1. TCP 与 UDP 核心能力速览
| 对比项 | TCP | UDP |
|---|---|---|
| 全称 | Transmission Control Protocol | User Datagram Protocol |
| 连接状态 | 面向连接,需要三次握手建立连接、四次挥手释放连接 | 无连接,发送前不需要建立连接 |
| 可靠性 | 可靠传输,支持确认、重传、去重、按序到达 | 不可靠,不保证送达、不保证顺序、不保证不丢包 |
| 数据边界 | 字节流,无边界,应用层需要自定义分包规则 | 数据报,每个报文独立,有明确边界 |
| 流量控制 | 支持滑动窗口流量控制 | 不支持 |
| 拥塞控制 | 支持慢启动、拥塞避免、快速重传 | 不支持 |
| 广播/组播 | 不支持一对多传输 | 支持一对多、多对多 |
| 头部开销 | 20 字节及以上,带选项时更大 | 固定 8 字节 |
| 传输效率 | 较低,受确认、重传、拥塞控制影响 | 较高,没有确认机制,延迟低 |
| 连接数量 | 高并发时服务端需要维护大量连接状态 | 服务端无需维护连接状态,因此可扩展性更高 |
| 典型应用 | HTTP、HTTPS、SSH、WebSocket、Modbus TCP、数据库连接 | DNS 查询、DHCP、TFTP、SNMP、RTSP 实时流、游戏同步、VoIP |
| 系统资源占用 | 高,每个连接都有状态内存、收发缓冲区 | 低,无连接状态,仅需要绑定端口 |
| 编程复杂度 | 高,要处理粘包、拆包、断线重连、心跳保活 | 低,直接发送报文,但业务层要自己处理可靠性 |
这张表可以快速作为选型依据:如果业务要求“丢一个字节都不能接受”,选 TCP;如果业务要求“延迟要低,丢少量数据可以接受”,选 UDP。
2. TCP 工作机制:连接、确认与状态机
2.1 TCP 三次握手
TCP 建立连接的过程是三次握手,目的是让通信双方确认彼此的收发能力,并同步初始序列号。
过程如下:
- 客户端发送
SYN=1, seq=x报文,进入SYN_SENT状态。 - 服务端收到后回复
SYN=1, ACK=1, seq=y, ack=x+1,进入SYN_RCVD状态。 - 客户端发送
ACK=1, seq=x+1, ack=y+1,双方进入ESTABLISHED状态。
用文本时序表示:
客户端 服务端 |------- SYN seq=x --------->| |<-- SYN+ACK seq=y, ack=x+1 --| |------- ACK seq=x+1 -------->| | 连接建立成功 |如果只握手两次,服务端无法确认客户端的接收能力是否正常;握手四次则多余,所以三次是完备方案。
2.2 TCP 四次挥手
断开连接时使用四次挥手,因为 TCP 是全双工连接,每个方向的关闭必须独立确认。
过程如下:
- 主动关闭方发送
FIN,表示不再发送数据,进入FIN_WAIT_1。 - 被动关闭方回复
ACK,进入CLOSE_WAIT;此时被动方仍然可以发送数据。 - 被动关闭方数据发送完毕后,发送
FIN,进入LAST_ACK。 - 主动关闭方回复
ACK,进入TIME_WAIT,等待一段时间后关闭。
主动关闭方 被动关闭方 |------- FIN ---------------->| |<------- ACK ----------------| | | |<------- FIN ----------------| |------- ACK ---------------->| | |这里经常会被问到:为什么主动关闭方要停留在TIME_WAIT?因为最后一个 ACK 可能丢失,如果被动方没有收到 ACK,它会重发 FIN,主动方必须保留足够时间处理重发的 FIN。同时,TIME_WAIT能防止旧的连接报文影响新连接。
线上经常会出现大量TIME_WAIT连接,一般不需要过度优化。如果机器短时间内产生上万条TIME_WAIT,可以先排查是否短连接创建过于频繁,再考虑调整系统参数。
2.3 TCP 状态机速查
排查连接问题经常要用netstat或ss查看连接状态。常见状态含义如下:
| 状态 | 含义 |
|---|---|
| LISTEN | 服务端正在监听端口 |
| SYN_SENT | 客户端发送 SYN 后等待服务端回应 |
| SYN_RCVD | 服务端收到 SYN 并回复 SYN+ACK,等待客户端 ACK |
| ESTABLISHED | 连接建立成功,正在进行数据传输 |
| FIN_WAIT_1 | 主动关闭方发送 FIN,等待 ACK |
| FIN_WAIT_2 | 主动关闭方已收到 ACK,等待被动方 FIN |
| CLOSE_WAIT | 被动关闭方收到 FIN,回复 ACK 后等待本地应用关闭连接 |
| LAST_ACK | 被动关闭方发送 FIN,等待主动方 ACK |
| TIME_WAIT | 主动关闭方收到 FIN 并回复 ACK,等待足够时间确保对端收到 |
排查时,如果服务端出现大量CLOSE_WAIT,通常是应用程序没有正确调用close()或连接未释放;出现大量TIME_WAIT,则常见于短链接服务,一般通过长连接或连接池优化。
3. UDP 工作机制:无连接、有边界、不保证送达
UDP 的核心特点是简单。发送端只要指定目的 IP 和端口,内核把数据报封装后直接丢到网络上,不需要握手,不需要确认,也不需要维护连接状态。
因为 UDP 没有确认和重传,所以丢包之后发送端无感知。接收端收到报文时,每个报文都是独立的,不会像 TCP 那样合并成字节流,因此 UDP 天然保留数据报边界。举个例子,如果发送端连续发送 3 个 UDP 报文,接收端调用recvfrom()三次,每次读到的是一个完整的原始报文,不会出现 TCP 的粘包拆包问题。这一点是很多入门开发者最容易混淆的地方。
但也正因为没有连接状态,UDP 也带来一些容易踩坑的问题:
- 没有拥塞控制,网络拥塞时 UDP 报文会被直接丢弃,视频、音频会出现卡顿或花屏。
- 没有传输层去重机制,应用层可能收到重复报文。
- 没有传输层分片重组之外的顺序保证,网络路径变化会导致报文乱序。
- 数据报长度受限:IPv4 下 UDP 报文最大长度理论上是 65507 字节,但实际链路 MTU 通常限制为 1500 字节,如果应用层发送过大报文,IP 层会分片,分片报文一旦有一片丢失,整个数据报都会被丢弃。
简单总结:UDP 的“不可靠”是协议层不保证可靠,不是网络环境雪崩的外在表现。开发者如果想在 UDP 上获得可靠传输,需要在应用层自己实现确认、重传、排序、去重逻辑,或者直接使用 KCP、QUIC 这类基于 UDP 改造的可靠协议。
4. TCP 与 UDP 报文结构对比
4.1 TCP 报文头
TCP 头部最少 20 字节,结构如下:
- 源端口(16 bit)
- 目的端口(16 bit)
- 序号(32 bit)
- 确认序号(32 bit)
- 数据偏移(4 bit)
- 保留(3 bit)
- 标志位(9 bit),包括 URG、ACK、PSH、RST、SYN、FIN 等
- 窗口大小(16 bit)
- 校验和(16 bit)
- 紧急指针(16 bit)
- 选项字段(可变)
SYN、FIN、RST这几个标志位在实际排错中很重要:
SYN:建立连接请求。FIN:正常关闭连接。RST:异常中断连接。比如服务端没有监听对应端口,客户端连接时可能收到RST;应用程序主动 Reset 连接,也会收到RST。
报文里的序号机制保证了字节流可以按序重组,这也是 TCP 可靠性的一个重要基础。
4.2 UDP 报文头
UDP 头部只有 8 字节:
- 源端口(16 bit)
- 目的端口(16 bit)
- 长度(16 bit),表示 UDP 头部和数据的总长度
- 校验和(16 bit)
头部足够简单,所以协议开销远小于 TCP。没有序号,没有确认序号,没有标志位,没有窗口。传输层能提供的信息非常有限,端口之外,剩下的可靠性完全交给网络环境和应用自身。
在 Wireshark 抓包时,TCP 报文可以直接看到 [SYN]、[ACK]、[FIN]、[RST] 等标记,也方便跟踪流;UDP 报文通常就一行源端口、目的端口和长度,内容要么放在 payload 里,要么配合 RTSP、SNMP 等协议解析。
5. 可靠传输、流量控制与拥塞控制
很多人只知道“TCP 可靠”,但不知道它靠什么机制可靠。TCP 的可靠性由几个机制叠加完成:
- 确认机制:接收方收到数据后返回 ACK。
- 超时重传:发送方如果长时间未收到 ACK,会重新发送数据。
- 去重:接收方根据序号去重,同一个数据即使多次收到也只保留一份。
- 按序重组:序号让接收方可以把乱序到达的报文重新排好。
- 校验和:接收方校验数据完整性,出错则丢弃并等待重传。
流量控制方面,TCP 使用滑动窗口机制。接收方会告诉发送方“我的接收窗口还剩多少”,发送方根据窗口大小决定一次性发送多少数据。这样能避免发送方过快导致接收方缓冲区溢出。
拥塞控制方面,TCP 有慢启动、拥塞避免、快速重传、快速恢复等策略。刚建立连接时,发送方不会一次性把大量数据打到网络上,而是从较小拥塞窗口开始,逐步增大窗口;出现丢包或超时后,主动降低发送速度。这种机制保证了网络的稳定性,但也造成了高 RTT 场景下的传输延迟。
UDP 则完全没有这些控制机制。它能做多少发送、发送多快,完全由应用自己控制。很多实时音视频方案选择 UDP,是因为实时性优先,宁可丢几帧,也不愿意等待重传造成延迟。
6. TCP 与 UDP 实际应用场景
选择 TCP 还是 UDP,最终取决于业务对“可靠”和“低延迟”的取舍。
6.1 适合 TCP 的场景
- HTTP/HTTPS:网页请求、接口调用,数据不能丢。
- WebSocket:实时推送、在线聊天、协作编辑,底层走 TCP。
- SSH:远程终端操作,命令传输和回显必须可靠。
- FTP/SFTP:文件传输,损坏一个字节都不可接受。
- 数据库连接:MySQL、Redis 客户端等,持久连接场景,需要可靠传输。
- Modbus TCP:工业现场 PLC 通信,使用 TCP 承载 Modbus 协议,依靠 TCP 保证请求响应可靠性。
- 邮件服务 SMTP/IMAP:内容完整性和顺序性优先。
- 消息队列:Kafka、RocketMQ 等内部通信,以及对 ACK 有依赖的业务,通常基于 TCP。
6.2 适合 UDP 的场景
- DNS 查询:域名解析请求通常用 UDP 报文完成,请求量小、响应快;域名响应过大时才会切换为 TCP。
- DHCP:客户端在无 IP 环境下用广播发送 DHCP 请求,必须走 UDP。
- TFTP:简单文件传输协议,用于网络设备固件升级等轻量场景。
- SNMP:网络设备监控,走 UDP 161/162 端口。
- 实时音视频直播:RTP/RTSP 的实时流媒体,接收方可以容忍少量丢包,但延迟必须低。
- 在线游戏:位置同步、移动操作等状态上报,要求低延迟,少量丢包不会造成致命问题。
- 组播/广播:UDP 天然支持一对多传输,适合局域网设备发现、服务发现场景。
- 物联网设备:很多嵌入式设备上报传感器数据使用 UDP,设备资源受限,协议越简单越好。
6.3 两个典型对比
- WebSocket 与 UDP:WebSocket 的底层是 TCP,它提供的是应用层的全双工通信通道;如果业务要求低延迟,并且允许丢包,可以考虑基于 UDP 的私有协议或 QUIC。
- Modbus TCP 与 RTU:Modbus 本身定义的 TCP 端口是 502,通信依赖于 TCP 的可靠字节流;但如果工业场景需要 PLC 与海康相机等设备快速同步状态,也可以选择 UDP 通道,但一定要在应用层做超时和重试。
7. 本地环境下的 TCP 与 UDP 实验验证
理论看完,可以在本机做一组实验,直接感受 TCP 和 UDP 的区别。下面提供三个可以直接运行的验证方案:使用nc工具、使用 Python socket、使用 Wireshark 抓包。
7.1 安装工具
在 Linux 上安装netcat-openbsd和iperf3:
sudo apt update sudo apt install -y netcat-openbsd iperf3在 Windows 上,可以使用 PowerShell 自带的Test-NetConnection来测试 TCP 连通性,UDP 测试可以用第三方工具或 Python socket。
7.2 TCP 回环测试
启动一个 TCP 服务端:
# 终端 A nc -l 127.0.0.1 8888连接并发送数据:
# 终端 B nc 127.0.0.1 8888 hello tcp在终端 A 能看到hello tcp。如果服务端没有启动,客户端会收到类似Connection refused的报错,这就是 TCP 的连接机制起作用了。
7.3 UDP 回环测试
启动一个 UDP 服务端:
# 终端 A nc -u -l 127.0.0.1 9999发送数据:
# 终端 B nc -u 127.0.0.1 9999 hello udp在终端 A 可以收到hello udp。如果把终端 A 关掉,终端 B 发送数据时通常不会立即报错,因为 UDP 是无连接的,发送端并不知道接收端是否在监听。
7.4 用 Python 写一个 TCP 回声服务
下面这个例子演示 TCP 服务端需要循环等待连接,并且每个连接单独处理数据:
import socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('127.0.0.1', 8888)) server.listen(5) print('TCP server listening on 127.0.0.1:8888') while True: conn, addr = server.accept() print(f'client connected: {addr}') with conn: while True: data = conn.recv(1024) if not data: break conn.sendall(b'echo: ' + data)用一行命令测试:
printf 'hello tcp\n' | nc 127.0.0.1 8888输出:
echo: hello tcp7.5 用 Python 写一个 UDP 回声服务
UDP 服务端没有accept(),只有一个 socket 反复接收数据:
import socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server.bind(('127.0.0.1', 9999)) print('UDP server listening on 127.0.0.1:9999') while True: data, addr = server.recvfrom(1024) print(f'client: {addr}, data: {data}') server.sendto(b'echo: ' + data, addr)测试:
printf 'hello udp\n' | nc -u -w1 127.0.0.1 9999注意,这里nc会立即发送数据并退出,UDP 服务端打印一次接收日志就算验证成功。
两个示例的服务端代码虽然都叫“回声服务”,但编程模型完全不同。TCP 多了一个连接维护的过程,UDP 则更接近“收包-回包”的简单循环。
8. 使用 Wireshark 抓包验证三次握手
如果没有抓包经验,可以先用loopback接口抓本机数据。打开 Wireshark,选择 Loopback 接口,然后在终端执行:
nc 127.0.0.1 8888输入任意内容后关闭连接。在 Wireshark 过滤栏输入tcp.port == 8888,可以看到三次握手的SYN、SYN+ACK、ACK报文,以及结束时的FIN、ACK报文。这个过滤条件适合验证端口号是否生效。
如果过滤栏输入udp.port == 9999,同样能看到 UDP 单次发送的报文。Wireshark 里面 UDP 的报文非常干净,没有握手,没有确认,一眼就能看出两种协议的区别。
这里补充一个常见困惑:为什么 Wireshark 设置了 UDP 过滤条件udp,但还是抓到 ICMP 报文?因为 Wireshark 的捕获过滤器(capture filter)和显示过滤器(display filter)是两套语法。如果使用抓包工具默认设置,实际上只在“显示”层面做了过滤,网卡物理收到的所有报文仍然进内存。要在捕获阶段就过滤,需要使用udp的 capture filter 语法,或者在显示过滤器中明确限制为udp且不包含其他协议。
9. 基于 iperf3 的性能测试
iperf3 是常用的网络性能测试工具,支持 TCP 和 UDP 两种模式。
9.1 启动 iperf3 服务端
iperf3 -s默认监听 5201 端口。如果要测试指定端口:
iperf3 -s -p 52029.2 TCP 带宽测试
客户端执行:
iperf3 -c 127.0.0.1 -p 5201 -t 10这里-t 10表示测试 10 秒。回显结果中会包含传输数据量、带宽、重传次数。TCP 测试重点关注重传次数,重传过高说明链路质量较差或拥塞控制触发频繁。
9.3 UDP 带宽与丢包测试
客户端执行 UDP 模式时,需要指定带宽上限:
iperf3 -c 127.0.0.1 -u -b 100M -t 10-u表示 UDP 模式,-b 100M表示以 100 Mbps 速率发送。回显中会包含Loss丢包率、抖动Jitter和每秒报文数。
UDP 测试结果需要区分发送端和接收端的统计。iperf3 输出中通常会显示本地发送段和远端接收段。如果只关心发送端是否成功发出,看 sender 统计;如果关心网络传输质量,看 receiver 统计。很多人在调 UDP 时只看了发送端没有报错,就以为没有丢包,这是明显的误区。
9.4 观察端口连接状态
测试时可以在另一个终端执行:
ss -tnp | grep 5201Linux 下可以用ss查看 TCP 连接状态。如果测试完毕还看到大量ESTABLISHED或TIME_WAIT,需要检查是否测试结束后没有正确关闭连接。
Windows 下使用:
netstat -ano | findstr "5201"也可以看到本地端口和远程端口处于什么状态。
10. 常见 TCP/UDP 问题与排查方法
下面是开发与运维中经常遇到的网络问题,按现象、原因、排查步骤和解决方案整理。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
启动服务时提示bind: only one usage of each socket address | 端口已经被占用,或者上一个服务进程没有退出 | 执行 `netstat -ano | findstr 端口或ss -lntp |
客户端连接服务端超时,tcp connect timeout | 服务端未启动、防火墙拦截、IP 不可达、服务端没有监听对应端口 | 先 ping 测试 IP,再用telnet IP 端口或nc -vz IP 端口测试连通性 | 启动服务端,确认监听地址不限于 127.0.0.1,检查防火墙规则 |
curl: (35) tcp connection reset by peer | 服务端主动重置连接;协议版本不匹配;nginx 后端配置错误 | 抓包看是否有 RST 报文,检查服务端日志 | 调整后端服务配置,检查 SSL 协议版本和 keepalive 设置 |
服务端出现大量CLOSE_WAIT连接 | 应用未调用 close,连接资源泄漏 | `ss -tanp | grep CLOSE_WAIT` 查看对应进程 |
服务端出现大量TIME_WAIT连接 | 短连接频繁建立和关闭 | 检查连接建立频率和客户端行为 | 客户端使用连接池或长连接;必要时调整tcp_tw_reuse,但不要盲目开启 |
| UDP 客户端发送数据没有报错,但接收端收不到 | UDP 无连接,发送端无法感知接收端是否在监听;报文被防火墙丢弃;端口不匹配 | 在服务端抓包确认报文是否到达,查看防火墙规则 | 确认端口和 IP,检查防火墙对 UDP 端口的放行策略,接收端调用 recvfrom 接收 |
| UDP 报文被分片后丢失 | 应用层发送数据超过路径 MTU,IP 层分片后其中一片丢失 | 用 ping 带-M do -s 1472测试 MTU | 控制 UDP 报文大小,建议不超过 1200-1400 字节,或使用 UDP-Lite、QUIC 等机制 |
| 抓包时设置了 UDP 过滤条件但抓到 ICMP | 使用的是显示过滤器,网卡捕获层没有过滤 | 使用 capture filter,如udp,条件语法与显示过滤器不同 | 在捕获前设置捕获过滤器,或持续用显示过滤器显示udp并忽略其他包 |
本机客户端连接本机服务端失败,提示Connection refused | 服务端未监听,或者监听地址不是客户端访问的地址 | 检查ss -lntp是否监听对应接口 | 修改监听地址为0.0.0.0或正确网卡地址 |
Docker 容器访问外部报docker: dial tcp: connection refused | 容器内网络访问宿主机或外部服务失败,服务未运行或防火墙拦截 | 进入容器执行curl、ping、nc测试 | 确认目标服务监听地址、容器网络模式为 host 或 bridge,放行端口 |
其中,error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address这类报错非常典型。它意味着进程要绑定的 127.0.0.1:11434 端口已经被占用。常见原因是本机之前启动过 Ollama 或其他本地服务,进程没有退出,再次启动时内核禁止重复绑定同一个地址。排查时先看 11434 端口被谁占用,再决定杀掉旧进程还是修改监听端口。
Windows 下查看端口占用:
netstat -ano | findstr "11434" tasklist | findstr "PID"Linux 下查看:
ss -lntp | grep 11434 lsof -i :1143411. 服务端程序设计选型建议
很多开发者写网络程序时,第一版就默认选择 TCP。大多数情况下没有问题,但如果并发连接数非常大、数据实时性要求高,TCP 的维护成本就会体现出来。建议按以下顺序做选择:
- 业务要求不丢包,且数据包不大、频率不高,首选 TCP。
- 业务要求高吞吐、低延迟,允许少量丢包或应用层做补偿,选择 UDP。
- 需要广播或组播,选择 UDP。
- 需要可靠传输但不想应用层造轮子,且网络环境复杂,考虑基于 UDP 的 QUIC 协议。
- 既有实时传输需求,又要求可靠传输,可以在 UDP 之上实现自己的确认重传机制,但工程复杂度不要低估。
- 嵌入式设备或 PLC 通信,优先考虑协议本身的成熟度。Modbus TCP 是 TCP 承载,Modbus RTU 走串口,两者不要混用。
如果选择 TCP,要特别注意粘包和拆包问题。TCP 是字节流协议,不保留应用层消息边界。例如发送方连续发送hello和world,接收方可能一次读到helloworld,也可能分两次读到hel和loworld。常见做法是在消息头里加长度字段,或者使用固定分隔符、固定长度消息、Protocol Buffers 等序列化方案。
如果选择 UDP,要设计好以下内容:
- 报文最大长度,避免 IP 分片。
- 校验机制,UDP 头部的校验和只能覆盖头部和数据,但应用层最好再加 CRC32 或更强校验,尤其是嵌入式场景。
- 序列号字段,用于乱序排序和去重。
- 超时重试,例如请求-响应模型里,客户端发送请求后等待响应,超时则重发。
- 心跳机制,UDP 没有连接状态,需要通过周期性心跳判断对端是否存活。
- 对端地址绑定,UDP 服务端可以同时接收来自多个客户端的请求,回包时必须使用
sendto带上收到的客户端地址,否则客户端收不到。
从代码实现来看,TCP 和 UDP 的另一个明显区别是接收数据的 API。TCP 使用recv(),接收的是任意字节数;UDP 使用recvfrom(),接收的数据报内容以调用方指定的缓冲区长度为准,缓冲区过小会被截断,过大则会浪费内存。
12. 常见误区与思路整理
关于 TCP 和 UDP,至少要纠正以下几点:
- UDP 比 TCP 一定快。不对。在无拥塞的小网络上,两者差异不大;在网络拥塞时,TCP 会主动降低速率,UDP 则会持续发送数据导致更严重的丢包。UDP 的“快”来自缺少连接维护和可靠性机制,但代价是数据不可靠。
- TCP 比 UDP 更安全。不对。TCP 同样会被欺骗、劫持、攻击,尤其是无加密的明文 TCP 连接。安全性取决于应用层协议和加密层,跟传输层协议关系不大。
- UDP 没有连接,所以服务器不需要维护状态。这也不完全对。应用层如果实现了会话机制,仍然需要维护状态。所谓“无连接”只是传输层没有连接状态机。
- TCP 一定适合所有可靠场景。不对。如果网络延迟极高、时延抖动大,TCP 的重传机制可能让整体表现比 UDP 更差。许多实时传输协议反而用 UDP 上承载自定义可靠方案。
- TCP 端口和 UDP 端口可以同时占用。对。同一个端口号可以同时被 TCP 和 UDP 的 socket 占用,因为协议不同。例如 DNS 服务器同时监听 TCP 53 和 UDP 53。如果报错
bind: only one usage of each socket address,说明可能是同协议端口被占用。
13. 调试建议与工程实践总结
在工程实践中,建议养成以下几个习惯:
- 记录连接状态。TCP 服务端要定时输出当前连接数、
ESTABLISHED数量、CLOSE_WAIT数量,这些是判断资源泄漏的早期信号。 - 统一端口规划。把 TCP 业务端口和 UDP 业务端口分开,避免 80、443、8080 等端口被随意占用,写进配置文件而不是写死在代码里。
- 抓包先看本机回环。使用 Wireshark 的 loopback 接口,能快速验证协议行为,不需要真实局域网环境。
- 防火墙规则要覆盖 UDP。很多人调试 TCP 服务时记得放行端口,调试 UDP 时却经常忽略防火墙,导致客户端发了数据、服务端没收到。
- 打日志要包含源地址和端口。UDP 服务端日志必须记录客户端地址,否则多客户端情况下很难排查是哪个设备发来的包。
- 批量任务或大量请求场景,建议使用连接池 + 超时重试。TCP 大量短连接会带来 TIME_WAIT 压力,UDP 大量报文则可能引发丢包风暴,这两种现象都是不同的故障特征。
回到最开始的问题:TCP 和 UDP 有什么区别?最简单的判断标准是,TCP 可以看作“打电话”:先接通,逐句确认,听不清就重说;UDP 可以看作“寄平信”:直接投递到门牌号,不保证对方收到,也不保证顺序。选择哪个协议,本质上是业务要什么样的传输保证。理解了三次握手和数据报边界,再看端口占用、连接重置、抓包过滤这些实际问题,思路就会清晰很多。
如果正在排查本地端口冲突,优先使用ss或netstat定位占用进程;如果遇到连接超时,先确认监听地址和防火墙;如果是 UDP 测试,务必用服务端抓包和丢包统计作为最终依据。这套方法能覆盖绝大多数网络协议开发场景,建议直接收藏备用。