写网络排障这些年我最深的体会是很多人配IP、改路由、抓包都挺熟练可真碰上“端口起不来”“TCP重传一堆”“UDP疯狂丢包”这类问题还是会卡壳。说到底就是UDP和TCP的原理没有真正吃透。前阵子帮一个项目排查容器端口冲突报错信息是ports are not available: exposing port tcp 0.0.0乍看像是Docker的问题追到根上却牵扯出四元组、TIME_WAIT、端口复用这一整套TCP机制。今天就把UDP和TCP这两个传输层协议掰开揉碎讲清楚从协议设计思路到三次握手、dup ack、iperf3打流再到端口排障一次性说透。这篇文章适合网络工程师、后端开发、嵌入式开发者也适合刚学网络协议的学生。不光讲原理还会把原理映射到实际操作上比如怎么用netsh interface tcp show global看TCP全局参数怎么用iperf3做UDP打流测试怎么理解Modbus TCP主站这类工业协议为什么能稳定跑在TCP之上。反正看完你至少能明白遇到网络问题往哪一层想、用什么命令验证、怎么从根上解决。1. 先搞清楚TCP和UDP的设计哲学可靠与效率的取舍1.1 两种协议最根本的分歧UDP和TCP都工作在OSI模型的传输层都是在IP层之上做端到端的通信。但它们的出发点截然相反。TCP的核心诉求是“可靠、有序、不重不漏”而UDP的核心诉求是“轻量、低延迟、尽最大努力交付”。这两个目标本身就是矛盾的所以不存在谁更优秀只看你的业务更在意什么。我用寄快递来类比TCP像是发挂号信有面单、有签收、有回执丢一件就补发一件顺序乱了会重新整理收件方还得逐封确认。UDP则像直接往对方院子里扔纸飞机扔出去就不管了对方收没收到、收到几封、顺序对不对发送方一概不知。大多数实时音视频、游戏同步、设备探测用的就是纸飞机思路——因为游戏画面断一帧可以忍受但你不可能为了等一帧而让整个画面卡住。反过来文件传输、数据库事务、网页请求必须用挂号信丢一个字节都可能导致文件损坏或交易出错。这两个协议的底层数据结构差异也印证了设计思路。TCP头部最小20字节包含源端口、目的端口、序号、确认号、标志位、窗口大小、校验和等一系列字段UDP头部只有8字节就是源端口、目的端口、长度、校验和简单到几乎没有额外负担。这就是为什么UDP的传输效率天然更高因为每个包的开销小也没有确认、重传、排序这些额外流程。1.2 场景选型什么时候用UDP什么时候用TCP实际项目里到底怎么选型我给一个非常实用的判断清单看业务对丢包的容忍度。如果数据丢了会导致业务错误比如转账指令、配置下发、文件内容那就必须TCP。如果数据丢了只是短暂体验变差比如视频画面卡一下、语音断一个字那就优先考虑UDP。看对延迟的敏感度。TCP为了保证可靠性在弱网环境下会自动重传和降速这会让延迟急剧上升。UDP没有这些机制即使网络很差也保持恒定发送节奏延迟表现更稳定。看连接的规模。TCP是面向连接的维护每个连接需要占用内存和文件描述符大规模长连接场景需要考虑上限。UDP无连接服务端只需要一个socket就能服务所有客户端适合海量终端接入的物联网场景。看数据的时效性。如果一个包晚到了就没意义比如实时位置上报、心跳探测、交易行情那显然UDP更合适。如果一个包必须到达且必须按序处理那就选TCP。当然现实中的方案往往是两者结合。拿RTC实时通信来说信令走TCP保证可靠媒体流走UDP上层再加SRTP保证实时QUIC协议干脆在UDP之上自己实现了可靠传输用UDP的壳应用层的核。理解到这一层你就能明白为什么选型不能拍脑袋而是要对业务的容忍度和协议机制做精确的匹配。2. TCP原理拆解连接、确认、重传、流控到底怎么回事2.1 三次握手为什么偏偏是三次TCP连接建立的过程大家张口就能说SYN、SYN-ACK、ACK但为什么一定要三次不是两次也不是四次这个问题的答案藏在“双方都要确认自己和对方的收发能力都正常”这个前提里。第一次握手客户端发送SYN包服务端收到后能确认一件事客户端的发送能力正常自己的接收能力正常。第二次握手服务端回SYN-ACK客户端收到后能确认自己的发送能力正常、接收能力正常服务端的发送、接收能力也正常。这一步做完客户端已经确认了所有能力但服务端还不知道自己的发送是否被客户端收到所以需要第三次握手。客户端回一个ACK服务端收到后才知道自己的发送能力和客户端的接收能力都正常。如果只有两次握手服务端在收到SYN之后就认为连接建立但此时客户端可能根本没收到SYN-ACK就会一直重传SYN服务端却已经在等应用数据了两边状态不一致。四次握手当然也能达成同样的效果但第三次之后双方已经确认完毕第四次纯属多余浪费一个RTT。所以三次是数学上最优解。工程上的三次握手还有不少细节。服务端的listen状态背后有两个队列半连接队列也叫SYN队列和全连接队列也叫accept队列。SYN第一次到达时连接进入SYN队列完成第三次握手后移入全连接队列等待应用调用accept。如果SYN队列满了新连接会被直接丢弃如果全连接队列满了即使三次握手完成应用也accept不了客户端会感觉连接“通”了但请求迟迟没响应。排查这类问题可以用ss -lnt看Recv-Q和Send-QRecv-Q长期较大说明accept队列积压。2.2 四次挥手和TIME_WAIT连接关闭比建立更讲究断开连接需要四次挥手因为TCP是双工的每一方向上的关闭都要单独确认。假设客户端先发起FIN表示“我的数据发完了”服务端回ACK确认收到但服务端可能还有数据要发给客户端所以此时连接进入半关闭状态。等服务端把自己的数据发完再发FIN客户端回ACK连接才彻底关闭。这里最坑人的是TIME_WAIT状态。主动关闭连接的那一端通常是客户端在收到对方的FIN并回ACK后不会立刻进入CLOSED而是进入TIME_WAIT等待2MSL一个MSL通常是30秒或1分钟Linux下常见配置是60秒。这段时间内四元组源IP、源端口、目的IP、目的端口依然被占用不能立即复用。很多人写高并发客户端时都会踩到一个坑服务端重启后客户端疯狂重连报java.net.BindException: Address already in use就是这个原因。TIME_WAIT的意义在于确保最后一个ACK如果真的丢了对方重发的FIN还能被自己处理也防止上一连接的延迟数据串入下一连接。理解了这一点你就知道代码里光调SO_REUSEADDR不够还要理解它到底在哪个阶段生效、能不能解决你的场景。2.3 可靠传输的基石序号、确认号、重传机制TCP把数据看作字节流每个字节都有序号。发送方每发一个段都会带着这个段的起始序号接收方收到后回一个确认号表示“这个序号之前的数据我都收到了下一个我要的字节是这个”。这种“累计确认”机制是TCP可靠性的核心。但网络里难免丢包。如果发送方发了一个段超时没收到确认就会重传。早期TCP只有超时重传后来引入了快速重传接收方如果收到乱序的段会立刻重复确认最后收到的连续序号发送方连续收到3个重复确认也就是所谓的tcp dup ack就知道数据丢了不等超时立即重传。这里有个关键点为什么一定是3个不是1个或2个因为包到达顺序在正常网络里也可能乱序比如发出Seq1、2、3、4网络把2延迟了接收方可能收到1、3、4然后连发多次ACK2。如果收到1个重复ACK就启动重传正常乱序就会造成大量无效重传浪费带宽。3是经验平衡值既能识别真正丢包又不至于被常见乱序干扰。实际排查时抓包看到大量dup ack和乱序还不一定就是物理丢包有可能是一端网卡驱动导致TSOTCP Segmentation Offload异常或者是中间设备对数据包做了分片重组。所以要结合netstat -s的统计看看重传次数、乱序次数、丢包次数分别在哪一层增长再决定去哪儿查。2.4 滑动窗口与拥塞控制效率和安全如何平衡确认机制解决了可靠性但没有解决效率问题。如果发一个包等一个确认带宽根本用不满所以TCP引入了滑动窗口。发送方可以连续发送窗口内的数据不必等每个包确认。窗口大小由接收方的接收能力通告窗口和网络的拥塞程度拥塞窗口共同决定实际可用窗口是两者中较小者。netsh interface tcp show global在Windows上能看到的“接收窗口自动调谐级别”就是操作系统动态调节通告窗口的机制如果被关闭了TCP吞吐会明显下降尤其在大带宽长链路场景下表现非常明显。这里顺便说一个行业里经常被提起的词“TCP标定”。在不同语境下它有不同含义。在汽车电子、工业测控领域它指的是用TCP协议实现传感器或执行器的标定流程比如通过TCP连接下发标定参数在网络调优领域它更多指针对带宽延迟积BDPBandwidth-Delay Product调整窗口和拥塞控制算法让网络跑满。如果面试或项目里有人提“TCP标定”先把上下文对齐再往下聊。实际上BDP这个指标太重要了链路带宽乘以RTT才是这条链路能容纳的在途数据量。如果窗口远小于BDP带宽就浪费了所以高延迟长链路要把窗口调大。拥塞控制算法也在不断演进。传统的Cubic适合有损高带宽链路BBR则通过测量带宽和最小RTT来主动控速不依赖丢包信号。如果服务器跑在丢包严重的链路上BBR常常能比Cubic快不少因为它不把丢包作为减速的唯一依据。但BBR在缓冲区较小的路由器上可能导致排队延迟增加需要实测对比。3. UDP原理与实践无状态的高效和它的麻烦3.1 UDP协议头8字节背后的自由与代价UDP头部就4个字段源端口16位、目的端口16位、长度16位、校验和16位。没有序号没有确认号没有窗口没有状态机。发送方想发就发接收方收就收了不需要先建立连接。这就是UDP最大的优势零握手零状态零重传端到端延迟极低。但自由是要付出代价的。没有序号意味着应用层无法判断包是否乱序没有确认意味着应用层不知道数据是否到达没有拥塞控制意味着在拥塞网络中UDP会继续往链路灌数据挤占TCP的带宽。所以很多网络设备上做QoS策略时会限制UDP的带宽占比防止它把业务TCP流量饿死。还有个细节值得注意IPv4下UDP校验和是可选的——发送方如果把校验和设为0接收方就不校验但IPv6下UDP校验和是强制的。在嵌入式设备或FPGA上写UDP协议栈时这个坑尤其要小心很多自研协议栈只在IPv4环境里验证过切到IPv6就直接丢包。另外UDP校验和计算的是伪首部UDP头数据的校验和伪首部里包含源IP和目的IP所以就算在L3做NAT如果改写IP地址而没有重新计算UDP校验和也会导致接收端校验失败。3.2 UDP协议栈在嵌入式与高性能场景的实践要点最近很多搞FPGA、zynq这类平台的朋友都在做UDP测试因为UDP无状态、结构简单特别适合硬件逻辑实现。但真正写UDP协议栈时需要注意几点UDP传输天然不保证包序和完整性所以如果上层是视频流或文件块必须自己加序号和校验字段UDP包超过MTU常见1500字节就会触发IP分片分片包任何一个丢失整个UDP包都无法重组所以建议应用层控制每次发送的数据长度典型值是1472字节1500减去20字节IP头再减8字节UDP头如果对端用FPGA裸写MACIP头里的总长度、校验和UDP头里的长度字段都必须严格按大端格式填填错任何一个字段都可能导致对端丢包。我曾经排查过一个zynq平台的UDP丢包问题百兆网口跑大数据包吞吐一高就丢包。抓包发现是FPGA侧把跨页描述符处理错了简单说就是DMA没处理好导致部分描述符丢失。这类问题有点隐蔽因为软硬件接口边界的bug往往表现为随机丢包跑时间短根本发现不了。这也是嵌入式UDP调试比TCP调试更费劲的原因——TCP丢包重传会被掩盖UDP丢包就只能靠上位机肉眼数包。所以做UDP传输强烈建议应用层自带序号上位机按序号缺口就能精确定位丢包点和丢包率。3.3 UDP端口测试为什么不能像TCP那样“试通”TCP端口能不能通一条nc -vz 192.168.1.100 8080就有结论因为TCP有握手连得上就是连得上连不上就是连不上。但UDP没有握手你往一个UDP端口发数据目标主机即使收到了如果上层应用没有响应发送方也感知不到好像发了个寂寞。所以UDP端口测试的真正方法是要么有应用层的响应报文要么在目标主机上抓包确认数据确实到达。我自己常用的办法是目标机跑tcpdump -i eth0 udp port 9000源机用nc -u 192.168.1.100 9000发一串数据目标机tcpdump里能看到包就说明网络层和传输层路径是通的如果应用层需要响应就得看应用日志。还有一种办法是用iperf3专门起UDP服务端另一端用UDP打流通过收到的包数和带宽来判断链路质量这比盲目“测端口”靠谱得多。很多刚入门的朋友会拿着TCP的思路去测UDP发现“端口不通”就开始查防火墙、改路由但其实UDP“不通”要先分清是没发出、没到达、还是到了没人回。建议链路每一段都用对应的工具验证别拿一个没有响应的UDP测试结果直接下结论。3.4 iperf3使用UDP打流带宽、抖动、丢包一次看全iperf3做UDP打流真是网络排查的一把好手。比TCP打流更狠的地方在于UDP模式能同时测出带宽、抖动jitter和丢包率三个指标这些数据对评估实时音视频、工业控制链路非常有用。我常用的命令是# 服务端 iperf3 -s -u -p 5201 # 客户端 iperf3 -c 192.168.1.100 -u -b 100M -t 30 -i 1-b 100M表示以100Mbps的码率发送UDP数据流-t 30表示持续30秒-i 1表示每秒打印一次统计。跑完服务端会输出接收带宽、抖动和丢包率。需要注意UDP打流不像TCP那样有拥塞控制-b设多少它就拼了命发多少所以如果链路实际带宽只有50M而你设了100M丢包率会非常难看。这时候不是链路坏了而是你的配置超出了链路容量要先把-b逐级往上加找到刚好不丢包的临界带宽那才是链路真实的可用带宽。另一个使用注意iperf3默认开的是TCPUDP模式一定要加-u服务端和客户端参数要一致特别是端口和带宽。多线程UDP打流可以用-P 4但UDP多流的抖动数据就不一定代表单流真实体验了因为多流共享同一个出口互相挤占会使抖动偏高。测链路质量时我建议先单流测再按需加多流不要一上来就四流并跑。3.5 UDP探测靠应用层响应确认“活”端口除了测链路UDP探测还常被用来发现局域网设备。很多打印机、摄像头、智能家居设备都支持某种UDP广播或组播协议主机发一个探测报文设备收到后以自己的IP和端口信息回一个UDP包这样就能发现设备。这和TCP建立连接的探测方式完全不同更依赖应用层协议本身。实现一个UDP探测功能代码上并不复杂。发送端创建一个UDP socket往广播地址或组播地址发探测报文然后设置接收超时等待应答import socket import struct sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind((0.0.0.0, 5000)) sock.settimeout(3) message bDISCOVER sock.sendto(message, (255.255.255.255, 5000)) try: while True: data, addr sock.recvfrom(1024) print(f发现设备: {addr}, 响应: {data}) except socket.timeout: print(探测结束未发现更多设备)但这里面有一个只有踩过坑才懂的点UDP广播只能覆盖同一广播域跨VLAN或跨三层就收不到了这时要改用组播并让路由器使能IGMP或者直接用单播探测已知IP列表。另外Windows的防火墙默认会拦UDP广播自己的程序发的广播自己收不到也可能是因为防火墙把本机回环给拦了。调试时先关掉防火墙或加放行规则再把问题往代码层面找。4. 工程实战用原理解决端口、连接和抓包疑难杂症4.1 端口占用与容器发布的报错到底是谁占了我的端口文章开头提到的报错ports are not available: exposing port tcp 0.0.0.0在实际中出现频率很高。很多人一看到这个就以为是Docker的问题其实Docker只是转达了内核的拒绝。这个报错的核心含义是Docker尝试把宿主机的某个端口映射进容器时发现宿主机这个端口已经被其他进程占用了。但很多时候大家真正问的是占用的到底是谁排查步骤其实固定按顺序来就行。在宿主机上先看端口监听状态用ss -lntp或netstat -antp注意是查宿主机而不是容器里ss -lntp | grep 8080 lsof -i:8080关键是理解TCP监听端口分布在所有IP地址和特定IP地址的区别。如果你的服务只监听了0.0.0.0:8080那所有IP访问8080都被它占用如果服务监听的是192.168.1.10:8080那么另一个服务可以监听127.0.0.1:8080因为它们是不同的元组。这在多网卡服务器上特别常见。还有人会在Docker里看到宿主机端口占用是因为容器端口映射用了-p 8080:80宿主机8080被其他进程占了Docker就报这个错。另外还有一个容易忽略的情况是端口被同一个进程自己占用了。比如某些服务器软件启动时按CPU核心数开多个worker每个worker都想绑定同一端口如果没设置SO_REUSEPORT第二个worker就会bind失败报Address already in use。这不是别人占端口是自己人跟自己人打架。4.2 TIME_WAIT与地址复用高并发客户端为何老报Address already in usejava tcp客户端重连时报地址已在使用这类问题原理落在TCP的状态机上。客户端主动断开后进入TIME_WAIT在这个状态下同一个本地端口不能立即绑定到相同四元组。如果客户端代码里每次重连都显式绑定同一个本地端口比如new Socket(ip, port, localAddr, 5200)那在TIME_WAIT没消失前就会报Address already in use。解法通常有三个层面。第一应用层不要显式绑定固定客户端端口让操作系统自动分配这是最省事的第二如果要绑定固定端口可以在创建socket时设置SO_REUSEADDRLinux下它允许新连接复用TIME_WAIT状态下的端口。这里要注意SO_REUSEADDR和SO_REUSEPORT的区别SO_REUSEADDR解决的是TIME_WAIT端口复用SO_REUSEPORT解决的是多个socket同时绑定同一端口。Java里开启SO_REUSEADDR比较直接Socket socket new Socket(); socket.setReuseAddress(true); socket.bind(new InetSocketAddress(localAddr, localPort)); socket.connect(new InetSocketAddress(remoteIp, remotePort), 3000);还有一个容易被忽略的底层参数Linux临时端口范围。默认情况下Linux自动分配的临时端口范围是32768-60999如果连接数特别多、TIME_WAIT又积压得很快端口池可能被耗尽。此时可以用sysctl -w net.ipv4.ip_local_port_range10000 65535来扩大范围但这只是治标。真正要做的还是让服务端开启TCP快速回收或考虑连接复用池别让短连接疯狂重建。4.3 netsh interface tcp show globalWindows上也能看TCP全局参数如果是Windows服务器排查TCP问题有个很有用的命令netsh interface tcp show global。它能显示全局TCP参数包括接收窗口自动调谐级别、RFC 1323时间戳、初始RTO、ECN功能、Nagle算法是否禁用等。我在调优Windows Server时最常检查的就是接收窗口自动调谐级别它被禁用的话大带宽链路的吞吐会受限于默认窗口表现就是“网速跑不满”。可以用下面的命令查看netsh interface tcp show global如果发现自动调谐级别是disabled可以用管理员权限开启netsh interface tcp set global autotuninglevelnormal注意这个命令在不同Windows版本上支持程度不一样老版本Windows Server没有这么细的调优选项需要更新网卡驱动或使用注册表。微软有些系统更新会重置TCP全局参数升级完系统后如果发现吞吐大幅下降先看一眼全局参数是不是被重置了别急着找运营商报障。4.4 TCP最大连接数为什么不是65535很多人有个误解因为TCP源端口是16位所以最大65535个连接。这个说法忽略了四元组的作用。TCP连接由“源IP、源端口、目的IP、目的端口”唯一确定。服务器端固定监听一个端口比如8080但它可以和成千上万个客户端建立连接因为每个连接的源IP和源端口都不同。所以理论上限不是端口数量而是内存和文件描述符的限制。实际操作中量级上更容易受两个因素限制一是文件描述符上限ulimit -n每个socket占用一个fd默认1024的话并发连接超过1000个就会出现Too many open files二是内核的内存参数通过ss -s能看到socket内存占用。如果监控上看到Listen Overflows和Listen Drops增长说明全连接队列满了连接虽然接了但应用来不及accept表现就是客户端连接超时、请求堆积。另一个真实场景是Nginx作为反向代理时很多人会问“Nginx最大连接数能到多少”。Nginx的worker连接数受worker_connections配置和系统fd限制共同影响。正确的调优姿势是同时提高worker_rlimit_nofile、系统级文件描述符上限、以及Nginx的事件模型配置。这些参数调完后单机抗几万TCP连接并不是问题。但再多就要考虑用LVS、DPDK等方案做水平扩展了不能只压一台服务器。4.5 Modbus TCP工业控制里TCP协议的样子工业控制领域非常典型的例子是Modbus TCP。靠近底层的设备通信很多还是串口Modbus RTU但上位机和PLC之间越来越多直接用Modbus TCP。Modbus TCP本质上是把Modbus报文封装到TCP里用502端口通信寄存器地址、功能码、数据体都作为应用层载荷。功能上它和UART透传没太大区别但TCP的可靠性让运行维护省了很多心。去年帮客户做过fx5u modbus tcp主站功能的调试三菱FX5U做Modbus TCP主站下面是几台从站仪表。最典型的坑是PLC做主站时它会主动发起TCP连接如果从站断电重启TCP连接不会自动恢复主站也不知道就一直等着从站回包。很多工程师第一次遇到底层断线重连问题会去抓包其实抓包只能看到TCP重连失败根因是PLC的执行逻辑没有做连接状态监测。建议在主站轮询周期里增加心跳检测发现连接异常后重新建立TCP会话而不是一直等那个永远不来的响应。Modbus TCP还有一个细节报文长度单位是字节Modbus应用层报文自带长度字段TCP流里要靠它来拆包。因为TCP是字节流没有消息边界如果一次收到的TCP数据里包含多个Modbus帧就必须按长度字段逐个解析。我见过有的新人用recv一次收到的数据直接当一帧去解析结果数据稍微一多就错乱。正确做法是维护一个应用层接收缓冲区循环拆包while (recv_len 6) { // 至少包含事务ID协议ID长度字段 int len (buf[4] 8) | buf[5]; // 长度字段 if (recv_len len 6) { parse_modbus_frame(buf, len 6); buf len 6; recv_len - len 6; } else { break; // 等待更多数据 } }这种“按长度拆包”的思路适用于所有基于TCP的自定义协议关键就是别把TCP当成有消息边界的协议来用。4.6 用Wireshark验证TCP原理抓包看三次握手和重传学了再多理论不如亲手抓一次包看得明白。Wireshark抓TCP三次握手最直观打开抓包后随便访问一个网站过滤tcp.port 目标端口就能看到SYN、SYN-ACK、ACK三个包。看这三个包的Seq和Ack数字不断变化比背十遍状态机都有用。排查重传问题用tcp.analysis.retransmission过滤所有重传包会单独标出来看快速重传则用tcp.analysis.fast_retransmission。如果这两个过滤结果里包很多说明网络中确实存在丢包或延迟抖动。再看tcp.analysis.duplicate_ack可以看到那些重复ACK还原dup ack的触发现场。抓包时域名解析和生产环境流量很容易混在一起造成干扰。我习惯只抓自己关心的主机和端口比如tcpdump -i eth0 -w trace.pcap host 192.168.1.100 and tcp port 8080Wireshark里再配合Statistics - TCP Stream Graph - Time-Sequence Graph可以直观看到窗口增长、重传点、乱序点很多性能问题一张时序图就能看出来。抓包文件如果很大还可以先用Wireshark-Telephony-VoIP Calls这类工具筛选出音频流、视频流做质量分析不过那就是另一个话题了。5. 一手经验几个我实测下来很有用的排查习惯最后分享几个个人经验。第一个是排查网络问题时先区分“链路问题”还是“协议问题”。链路问题的典型特征是所有协议都受影响包括ping都丢包协议问题则是只有特定协议或特定端口异常。先用ping和iperf3确认链路再去查协议栈、防火墙、端口占用可以少走很多弯路。第二个是UDP调优一定要配合应用层诊断字段。凡是用UDP做可靠传输的业务强烈建议在协议里加上包序号和时间戳这样任何一次丢包都能定位到具体链路点和时间点而不是靠猜。硬件和FPGA上做UDP更要靠抓包软件做中间验证我见过很多项目是自由协议栈收发端自己写的“自己发给自己收”很难测出真实丢包必须引入独立抓包工具作为仲裁者。第三个是不要小看操作系统的TCP参数。很多“网络慢”“连接不稳定”的表象根因都在协议栈参数上。Linux下多用ss -s、netstat -s、ss -lntp这几个命令Windows下多用netsh interface tcp show global。先把参数现状摸清楚再谈调优不要瞎改sysctl否则很容易把正常参数改坏。网络问题排查从来不是单一知识点能解决的。TCP、UDP原理是地基各种命令和工具是手段但最值钱的是把这些串起来的经验。希望这篇文章能帮你把散落的知识点串成一条线下次遇到问题时能像老手一样先定位再动手少踩几个坑。