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 核心能力速览对比项TCPUDP全称Transmission Control ProtocolUser Datagram Protocol连接状态面向连接需要三次握手建立连接、四次挥手释放连接无连接发送前不需要建立连接可靠性可靠传输支持确认、重传、去重、按序到达不可靠不保证送达、不保证顺序、不保证不丢包数据边界字节流无边界应用层需要自定义分包规则数据报每个报文独立有明确边界流量控制支持滑动窗口流量控制不支持拥塞控制支持慢启动、拥塞避免、快速重传不支持广播/组播不支持一对多传输支持一对多、多对多头部开销20 字节及以上带选项时更大固定 8 字节传输效率较低受确认、重传、拥塞控制影响较高没有确认机制延迟低连接数量高并发时服务端需要维护大量连接状态服务端无需维护连接状态因此可扩展性更高典型应用HTTP、HTTPS、SSH、WebSocket、Modbus TCP、数据库连接DNS 查询、DHCP、TFTP、SNMP、RTSP 实时流、游戏同步、VoIP系统资源占用高每个连接都有状态内存、收发缓冲区低无连接状态仅需要绑定端口编程复杂度高要处理粘包、拆包、断线重连、心跳保活低直接发送报文但业务层要自己处理可靠性这张表可以快速作为选型依据如果业务要求“丢一个字节都不能接受”选 TCP如果业务要求“延迟要低丢少量数据可以接受”选 UDP。2. TCP 工作机制连接、确认与状态机2.1 TCP 三次握手TCP 建立连接的过程是三次握手目的是让通信双方确认彼此的收发能力并同步初始序列号。过程如下客户端发送SYN1, seqx报文进入SYN_SENT状态。服务端收到后回复SYN1, ACK1, seqy, ackx1进入SYN_RCVD状态。客户端发送ACK1, seqx1, acky1双方进入ESTABLISHED状态。用文本时序表示客户端 服务端 |------- SYN seqx ---------| |-- SYNACK seqy, ackx1 --| |------- ACK seqx1 --------| | 连接建立成功 |如果只握手两次服务端无法确认客户端的接收能力是否正常握手四次则多余所以三次是完备方案。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 并回复 SYNACK等待客户端 ACKESTABLISHED连接建立成功正在进行数据传输FIN_WAIT_1主动关闭方发送 FIN等待 ACKFIN_WAIT_2主动关闭方已收到 ACK等待被动方 FINCLOSE_WAIT被动关闭方收到 FIN回复 ACK 后等待本地应用关闭连接LAST_ACK被动关闭方发送 FIN等待主动方 ACKTIME_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 与 UDPWebSocket 的底层是 TCP它提供的是应用层的全双工通信通道如果业务要求低延迟并且允许丢包可以考虑基于 UDP 的私有协议或 QUIC。Modbus TCP 与 RTUModbus 本身定义的 TCP 端口是 502通信依赖于 TCP 的可靠字节流但如果工业场景需要 PLC 与海康相机等设备快速同步状态也可以选择 UDP 通道但一定要在应用层做超时和重试。7. 本地环境下的 TCP 与 UDP 实验验证理论看完可以在本机做一组实验直接感受 TCP 和 UDP 的区别。下面提供三个可以直接运行的验证方案使用nc工具、使用 Python socket、使用 Wireshark 抓包。7.1 安装工具在 Linux 上安装netcat-openbsd和iperf3sudo 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(fclient connected: {addr}) with conn: while True: data conn.recv(1024) if not data: break conn.sendall(becho: 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(fclient: {addr}, data: {data}) server.sendto(becho: 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、SYNACK、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 -anofindstr 端口或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 -tanpgrep CLOSE_WAIT 查看对应进程服务端出现大量TIME_WAIT连接短连接频繁建立和关闭检查连接建立频率和客户端行为客户端使用连接池或长连接必要时调整tcp_tw_reuse但不要盲目开启UDP 客户端发送数据没有报错但接收端收不到UDP 无连接发送端无法感知接收端是否在监听报文被防火墙丢弃端口不匹配在服务端抓包确认报文是否到达查看防火墙规则确认端口和 IP检查防火墙对 UDP 端口的放行策略接收端调用 recvfrom 接收UDP 报文被分片后丢失应用层发送数据超过路径 MTUIP 层分片后其中一片丢失用 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 PIDLinux 下查看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 测试务必用服务端抓包和丢包统计作为最终依据。这套方法能覆盖绝大多数网络协议开发场景建议直接收藏备用。