资讯动态

TCP与UDP区别详解:从握手机制到Socket编程实践

发布时间:2026/9/8 6:09:27 来源:尧图企业网站定制
这次我们直接聊一个所有做网络开发的人都会遇到的问题TCP 和 UDP 到底有什么区别不只是背八股文里的“一个可靠一个不可靠”而是从连接机制、报头结构、实际抓包、Socket 编程到线上排错把这两个协议在真实场景里的行为差异讲清楚。这篇文章会包含一个完整的对比表格然后是三次握手、四次挥手的数据包交互过程再给出 Python 环境下 TCP Socket 和 UDP Socket 的调用示例最后用一张常见问题排查表处理你大概率会遇到的端口冲突、连接超时、握手重置、UDP 丢包和缓冲区调优等问题。如果你是刚接触网络编程或者已经在写 C#、C、Qt、Linux 下的通信程序但经常被 TCP 的粘包、UDP 的丢包、Wireshark 过滤结果莫名出现 ICMP 这些现象绕晕那这篇文章可以直接收藏。1. TCP 和 UDP 核心特性对比速览先给结论后面再展开。TCP 是面向连接的、可靠的、基于字节流的传输层协议。UDP 是无连接的、不可靠的、基于数据报的传输层协议。对比项TCPUDP全称Transmission Control ProtocolUser Datagram Protocol连接状态面向连接需要三次握手建立连接四次挥手释放连接无连接发送方直接发数据不需要预先建立会话可靠性可靠有确认应答、超时重传、乱序重排机制不可靠发送后不确认不重传数据边界字节流应用层需要自己处理粘包和拆包数据报每个报文独立接收方一次读取一个完整报文报头大小标准 20 字节带选项字段时更长固定 8 字节传输效率相对低有连接建立和确认开销相对高无连接建立过程发完即走流量控制有滑动窗口流量控制没有内建流量控制靠应用层或缓冲控制拥塞控制有慢启动、拥塞避免、快重传、快恢复没有拥塞控制不会主动降速通信模式仅支持单播一对一支持单播、广播、组播顺序保证数据按发送顺序到达应用层不保证顺序后发可能先到端口号范围0-65535常用端口有约定0-65535常用端口有约定典型应用HTTP/HTTPS、SSH、MySQL、Modbus TCP、FTPDNS、DHCP、SNMP、TFTP、RTP 音视频流、在线游戏这里要特别提醒一点很多人在理解“UDP 不可靠”时容易直接得出“UDP 一定比 TCP 慢”的结论。实际上不完全对。UDP 少了握手和确认机制在数据较少、实时性要求高的场景里传输延迟往往更低。真实影响选择的是业务对丢包、乱序和数据完整性的容忍度。2. 它们分别解决什么问题适用场景与边界2.1 TCP 适合什么TCP 解决的核心问题是应用层希望“发送的字节流最终能完整、有序地到达对端”。文件传输、网页访问、远程登录、数据库连接、消息队列都依赖 TCP。像 Modbus TCP 这类工业协议底层也是走 TCP因为工厂数据控制指令不允许丢。如果业务里要求所有请求都必须有响应并且响应和请求一一对应TCP 是更稳妥的选择。2.2 UDP 适合什么UDP 解决的核心问题是数据要尽快发出去允许少量丢包但不能容忍高延迟等待重传。音视频通话、直播推流画面丢一帧可以等下一帧但不可能停下来等重传。在线游戏里的角色位置、操作指令只关心最新状态旧数据重传没有意义。DHCP 和 DNS 这类基础网络协议很多场景直接用 UDP因为报文短、查询快。组播和广播场景也只能选 UDPTCP 不支持多目标投递。2.3 不适合什么场景不要把 UDP 用于需要强事务保证的业务比如订单支付、登录鉴权、配置下发。如果一定要用必须在应用层自己实现超时重传和状态确认这会把 UDP 的简单优势全丢掉。也不要在广域网长时间跑大流量的无拥塞控制 UDP 应用这会导致链路拥塞影响其他应用。现在很多高性能传输方案之所以选择在 UDP 上做可靠层也是因为 TCP 的拥塞控制策略在某些场景下不够灵活。3. 实操前置抓包与测试工具准备理解 TCP 和 UDP 不能只靠背概念建议自己动手抓包验证。需要准备的工具有工具用途平台Wireshark抓包分析三层、四层协议头观察握手和挥手报文Windows / Linux / macOStcpdump命令行抓包适合排查服务器问题Linuxnetcatnc快速建立 TCP/UDP 连接发送测试数据Windows / Linuxiperf3测试 TCP 和 UDP 的吞吐、丢包、抖动Windows / LinuxPython3写最小 Socket 客户端和服务端Windows / Linux / macOS网络调试助手可视化调试 TCP/UDP 收发适合工控场景Windows推荐先跑 Python 脚本做本机回环测试再用 Wireshark 抓回环接口上的包观察三次握手和四次挥手。如果是 Windows 的 Wireshark需要安装 Npcap。如果你在 Linux 服务器上排查也可以直接用 tcpdump# 抓取本机 5000 端口的所有 TCP 流量不解析 DNS sudo tcpdump -i any tcp port 5000 -nn -vv # 抓取发送到 127.0.0.1 的 UDP 流量 sudo tcpdump -i lo udp port 5001 -nn注意抓本机回环流量时接口要写lo或any如果写物理网卡名回环报文是抓不到的。4. TCP 三次握手与四次挥手用抓包看懂连接机制4.1 三次握手过程TCP 建立连接时客户端和服务端会交换三个报文客户端 服务端 |------- SYN seqx -------| |----- SYNACK seqy ackx1 -| |------- ACK seqx1 ------|第一次握手客户端发送 SYN 报文并随机生成一个初始序列号 seqx表示请求建立连接。第二次握手服务端收到 SYN 后发送 SYNACK 报文服务端自己的序列号为 y确认号 ackx1表示已经收到客户端的连接请求。第三次握手客户端收到 SYNACK 后发送 ACK 报文确认号 acky1。此时连接建立完成。从状态机的角度看客户端经历了CLOSED - SYN_SENT - ESTABLISHED服务端经历了CLOSED - LISTEN - SYN_RCVD - ESTABLISHED。三次的原因可以这样理解它保证了双方都确认“自己能发、对方能收”。如果只有两次握手服务端无法确认自己的应答能力对客户端是有效的很容易因为历史失效的 SYN 请求建立多余连接。4.2 四次挥手过程断开连接时需要四个报文主动关闭方 被动关闭方 |------- FIN --------------| |------ ACK ---------------| |----- FIN ---------------| |------- ACK --------------|与握手不同挥手是双向独立的。主动方先关闭发送方向的连接发送 FIN对端收到后回复 ACK然后关闭自己的发送方向发送 FIN主动方再回复 ACK连接彻底关闭。这里有一个常见坑被动关闭方回复 ACK 和发送 FIN 并不是同时发生的中间可能间隔一段时间因为应用层需要先处理完剩余数据再 close。这就是为什么 Wireshark 里有时候看到是四次报文有时候看到是三次报文。4.3 Wireshark 里怎么看用 Python 起一个简单 TCP 服务端然后用客户端连接并退出。# server.py import socket sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((127.0.0.1, 5000)) sock.listen(1) print(listening on 127.0.0.1:5000) conn, addr sock.accept() print(client connected:, addr) data conn.recv(1024) print(received:, data) conn.sendall(bok) conn.close() sock.close()在 Wireshark 启动回环抓包后运行客户端再退出就能在过滤栏输入下面任一过滤器tcp.port 5000正常结果里会看到SYN、SYNACK、ACK三个包建立连接然后数据报文带PSH, ACK标志最后FIN, ACK、ACK、FIN, ACK、ACK完成挥手。5. UDP 无连接通信实验数据报传输验证UDP 的行为要简单很多没有握手、没有挥手发送端直接通过sendto把数据包丢给目标地址。Python 写一个最小 UDP 服务端# udp_server.py import socket sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((127.0.0.1, 5001)) print(udp server listening on 127.0.0.1:5001) while True: data, addr sock.recvfrom(2048) print(received from, addr, :, data) sock.sendto(back, addr)UDP 客户端# udp_client.py import socket sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(bhello udp, (127.0.0.1, 5001)) try: sock.settimeout(3) data, addr sock.recvfrom(2048) print(server response:, data) except socket.timeout: print(no response within 3 seconds)UDP 的recvfrom一次读取一个完整数据报不会出现 TCP 那种粘包问题因为每个 UDP 报文都带有明确的边界。在 Wireshark 里抓包时过滤器用udp.port 5001你会看到客户端发送的报文后服务端回复的报文这两个报文之间没有 TCP 的三次握手过程。6. 编程调用示例Socket API 如何选型6.1 TCP Socket 标准调用流程服务端流程是socket - bind - listen - accept - recv/send - close客户端流程是socket - connect - send/recv - close。# tcp_server.py import socket server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((0.0.0.0, 5000)) server.listen(5) while True: conn, addr server.accept() with conn: print(connected by, addr) while True: data conn.recv(1024) if not data: break conn.sendall(data.upper())# tcp_client.py import socket client socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((127.0.0.1, 5000)) client.sendall(bhello tcp) client.settimeout(5) resp client.recv(1024) print(response:, resp) client.close()调用recv(1024)时应用层拿到的不一定和对方send的报文一一对应。TCP 是字节流底层可能把两次send的内容合并成一次返回也可能把一个大数据包拆成多次读取所以业务层需要自定义消息边界常见做法是增加 4 字节长度头。比如 Modbus TCP 的报文头里就带长度字段也是这个原因。6.2 UDP Socket 标准调用流程服务端流程是socket - bind - recvfrom/sendto客户端流程是socket - sendto/recvfrom。# udp_echo_server.py import socket sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((0.0.0.0, 5001)) print(udp echo server running) while True: data, addr sock.recvfrom(4096) print(data from, addr, len(data), bytes) sock.sendto(data, addr)UDP 场景下如果服务端 CPU 处理不过来数据包会被内核缓冲区丢弃客户端是感知不到的。如果你要保证可靠性必须自己实现确认包和超时重传。6.3 选型建议客户端服务端都要交互响应且不能丢TCP。实时音视频、游戏位置同步、终端远程控制类场景UDP。局域网内小报文广播UDP。广域网传输大文件优先 TCP或考虑 QUIC基于 UDP 的可靠协议等方案。工业现场设备通信看设备协议Modbus TCP 用 TCP有些自由协议为了实时性会走 UDP具体看设备手册。7. 性能观察TCP 的额外开销与 UDP 的缓冲区问题7.1 TCP 的额外开销TCP 的性能开销主要体现在四个地方每次连接建立需要一次三次握手RTT 至少增加一次往返。连接关闭需要四次挥手如果大量短连接频繁建立会消耗较多资源。确认报文、重传机制、滑动窗口控制会增加额外的网络流量和处理逻辑。TCP 的 Nagle 算法会将小包合并后发送如果应用没有禁用延迟确认交互型小报文可能感觉到延迟。当一个服务出现大量TIME_WAIT状态连接时基本可以判断是短连接创建太快连接没有做完双端关闭就重新发起连接这也是线上常见的性能瓶颈。7.2 UDP 的性能特征UDP 不建立连接没有确认发送方在理想条件下吞吐上限主要取决于系统发送缓冲区和网卡能力。但它也有自己的问题接收缓冲区溢出如果应用层读取慢内核缓冲区满后新到的 UDP 报文会被直接丢弃。分片当 UDP 报文超过 MTU 时IP 层会分片只要一片丢失整个 IP 数据报都会被丢弃。在 Windows 下如果出现大量 UDP 丢包可以在应用层调大SO_RCVBUF而不是依赖于修改系统全局缓存区因为系统级修改影响面大而且不是所有版本都开放配置。7.3 如何观察资源占用抓包频率不能过高。如果线上排查用 tcpdump 抓特定端口并限制每个报文长度# 只抓包内容前 96 字节减小文件体积 sudo tcpdump -i eth0 -s 96 -w /tmp/tcp.pcap tcp port 8080 # 抓 UDP 丢包相关看 ICMP 报文 sudo tcpdump -i eth0 icmp -nn在 Windows 下也可以直接用netstat -ano | findstr :5000查看 TCP 端口监听状态再配合任务管理器或资源监视器查看进程占用。8. 常见问题与排查方法问题现象可能原因排查方式解决方案启动报错error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address端口已被其他进程占用查看端口占用进程换端口或先释放占用进程TCP connect 超时目标 IP 无法路由、防火墙拦截 SYNC、服务端 backlog 满telnet 目标端口、tcpdump 看 SYN 是否有回应检查路由和防火墙确认服务端处于 LISTEN 状态curl 报错tcp connection reset by peer服务端直接 RST 连接可能是端口未监听、应用层主动拒绝、防火墙 reset抓包看 RST 来源方向检查服务端端口和防火墙规则Wireshark 过滤udp但抓到 ICMP 报文目标 UDP 端口不可达系统回 ICMP Port Unreachable看 ICMP 内容里的原始端口确认服务端 UDP 进程是否在监听iperf3 打 UDP 流丢包严重达到链路带宽上限、接收端缓冲区不足分别查看客户端和服务端统计降低发送带宽或调大接收缓冲接收方收不到 UDP 数据防火墙拦截、多播路由问题、网卡未启用组播tcpdump 确认包是否到达网卡检查防火墙规则和组播地址注册TCP 粘包导致数据解析错乱应用层没有定义消息边界打印每次 recv 的字节数增加长度头或使用分隔符协议大量 TIME_WAIT 连接堆积短连接频繁接入主动关闭连接过多netstat 统计 TIME_WAIT 数量使用连接池或在应用层主动 long-lived 连接修改 Windows 全局 UDP 缓存区后效果不明显应用系统缓存区和 Winsock 调用配置可能不对查看应用层是否设置 SO_RCVBUF在代码里设置接收缓冲不要只依赖系统配置ESP01S 或单片机无法发 TCP 消息到手机设备与手机不在同一网段或服务器端口没监听确认局域网 IP 和端口连通性用同一路由器关闭终端防火墙针对几个高频问题再补充一下。端口冲突是最常见的。Windows 下查看命令netstat -ano | findstr :11434 tasklist | findstr PID如果确认要强制结束进程taskkill /PID PID /FLinux 下ss -lntp | grep 11434 lsof -i:11434在线排查路由问题时很多工程师第一反应是 ping但 ICMP 通不代表 TCP 通因为防火墙策略经常放行 ICMP 却拦截 TCP。更稳妥的方式是用nc或直接业务请求测试。9. 协议选型与工程实践建议9.1 决策判断顺序在实际工程项目里建议按下面顺序做选择数据能否容忍丢失完全不能容忍选 TCP 或基于 UDP 的可靠协议。是否广播或多目标通信必须 UDP 或应用层复制。对延迟是否敏感高实时交互场景优先 UDP。通信数据量是否以短报文为主短请求响应频繁时TCP 的握手开销影响较大UDP 更轻量。是否经过公网传输公网环境丢包和拥塞不可避免优先选有重传机制的协议。是否需要中间网关或代理TCP 和 UDP 经过 NAT 和防火墙时行为不同比如代理内网穿透时UDP 组播要特殊处理。9.2 实操建议第一次测试时先小包跑通再加大数据量。TCP 小包没问题不代表大包没问题粘包、缓冲、超时都会在大请求下暴露出来。UDP 也是一样先测 64 字节再测 1400 字节最后测 64KB 甚至更大观察分片情况。超过 MTU 的 UDP 报文经公网转发很容易丢所以业务层最好控制报文小于 1400 字节。建议保持一套最小可运行的代码和命令在项目里用于随时验证网络状态。比如下面这个简单的 TCP 连通性检查比 telnet 更直观nc -vz 192.168.1.100 502UDP 连通性检查不能用nc -vz这种方式判断因为 UDP 没有握手-vz对 UDP 的“成功”只是成功发送不代表对端收到了。要确认 UDP 服务在线需要抓包看是否有响应或者是否有 ICMP Port Unreachable。在批量任务和持续运行的进程里日志必须包含时间戳、对端地址、收发的字节数、错误码。排查网络问题时没有日志等于盲人摸象。9.3 合规与安全边界TCP 和 UDP 都是基础网络能力本身没有风险问题但在实际工程中要特别注意不主动扫描他人网络资产端口扫描和流量探测在未经授权的情况下是不合规的。公网服务必须设置防火墙白名单不要盲目暴露 TCP/UDP 端口。UDP 在公网容易被用于反射放大攻击配置防火墙时需要限制来源和速率。涉及工业控制系统时优先使用带明确规范的标准协议比如 Modbus TCP并在交换机上做端口隔离。对数据帧中出现的第三方系统、设备、人员信息注意脱敏处理。10. 总结与下一步TCP 和 UDP 的本质区别就是TCP 用建立连接、确认、重传、拥塞控制这些机制换来了“可靠有序”UDP 用去掉这些机制换来了“低延迟、可广播、简单直接”。没有哪个更好只有哪个更合适。建议你按这个顺序做一次完整验证先用 Python 跑通 TCP 和 UDP 的最小收发流程。用 Wireshark 抓回环流量亲眼看一下 SYN、SYNACK、ACK 这三个报文。故意停掉 UDP 服务端再抓包看能不能看到 ICMP Port Unreachable 报文。用 iperf3 分别测一下 TCP 和 UDP 在本机回环链路上的吞吐差异。把端口冲突、连接超时、粘包拆包这几个常见问题在测试环境里各复现一次记录日志特征。最容易踩的坑不是记不住协议头而是把 TCP 的字节流当数据报处理或者把 UDP 的丢失问题丢给内核不管。把这两个认知补上网络编程里很多疑难杂症就少了一半。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价