先说一个我上个月参与的排查场景业务方反馈线上接口大面积超时后端拿着错误日志说“上游返回502”网络组说交换机端口有轻微丢包测试的同学补了一句“我这边ping网关延迟有点高”。五个人开了半小时会结论依然是“大家都没问题”。这个场面我见过太多次问题往往不在技术本身而是把TCP、UDP、ICMP、HTTP、HTTPS这五个最常挂在嘴边的协议混在一锅讨论各说各话谁也说服不了谁。这篇东西就是给这类场景准备的。我会把这五个协议的分工、各自的排障思路和常见调试方法拆开讲清楚适合刚入门的后端开发、运维工程师以及做嵌入式联网设备ESP01S、STM32这类的同学。看完你至少能明白为什么ping不通不代表服务挂了为什么有端口能连却报502为什么UDP偶尔会收到一个让你摸不着头脑的10054错误。1. 想搞清楚这五个协议先别急着背报文格式1.1 一张表看明白它们的层级归属先说结论这五个协议不是“五个同类选手”而是不同层面、互相配合的五种角色。IP负责找路TCP和UDP负责把数据搬到对端ICMP是网络层的“客服”HTTP、HTTPS则是应用层基于TCP的请求-响应语言IGMP这种组播管理协议暂且不展开。协议所在层有无连接是否可靠典型用途TCP传输层有连接可靠、有序文件传输、Web、数据库UDP传输层无连接尽力而为音视频、DNS、IoT上报ICMP网络层无连接尽力而为错误报告与探测HTTP应用层基于TCP由传输层保证网页、API接口HTTPS应用层基于TCP加密且可靠登录、支付、业务接口为什么要先分层因为排障最忌讳跨越层次跳来跳去。页面打不开你直接去宿主机上tcpdump抓包抓一百个包也说不清问题在哪。正确的顺序是从上往下先看应用层状态码再看传输层连没连上最后看网络层通不通。从顶层一层层往下剥多数故障在第二三层就能定位。1.2 数据封装一个请求要穿过几层“信封”发送一个HTTPS请求流程是应用数据先经过TLS加密交给TCP包装上TCP头再交给IP增加源目地址最后放进以太网帧里发出去。抓包工具里看到的就是一层一层包裹。很多新人第一次用Wireshark看不懂就是因为缺了“封装视角”。可以用快递来类比信纸上的内容就是应用数据TCP是快递服务条款IP是收件人地址以太网是实际跑运输的货车ICMP则是物流公司打给你的回执电话。这样想五个协议之间到底怎么配合一下子就清楚了。2. TCP可靠传输的基石三次握手与状态排查2.1 三次握手为什么是三次而不是两次或四次TCP是有连接协议建立连接前要先协商初始序列号。第一次客户端发SYN携带seqx第二次服务器回SYNACK携带seqy、ackx1第三次客户端回ACKacky1连接建立。为什么必须有第三次因为服务器需要确认“客户端能收到我发出的序列号”。如果只有两次握手服务器无法确认自己发的SYNACK是否到达了客户端收到重复SYN时还会凭空建立一堆半连接。那为什么又不是四次因为三次已经完成了一个最小的双向确认闭环再多就是浪费。抓包验证很简单过滤tcp.flags.syn 1能看到一条TCP流里依次出现SYN、SYNACK、ACK三个包这就是一次完整握手。线上如果出现大量SYN_RCVD状态堆积重点怀疑三件事对端应用根本没监听、防火墙丢弃了SYN包、半连接队列被打满。我处理过一个案例安全策略把某个网段的SYN包全部丢弃客户端拼命重试服务器端完全看不到最后在防火墙日志里才定位到排掉策略立刻恢复。2.2 四次挥手的暗坑TIME_WAIT与CLOSE_WAIT断开连接也不是一蹴而就主动方发FIN被动方回ACK被动方再发FIN主动方最后回ACK一共四个包。主动方随后进入TIME_WAIT等待2MSL后再彻底释放。TIME_WAIT是为了确保最后一个ACK能到达对方同时让网络中残留的旧报文段完全消失。所以看到TIME_WAIT不要慌这是正常状态。真正需要警惕的是CLOSE_WAIT堆积。它表示对端已经关闭连接内核也知道了这件事但本地的应用进程迟迟没有调用close。CLOSE_WAIT一多文件描述符和端口都被占着最终会耗尽资源。我修复过几次类似问题根因无非三种读完了响应却没关闭socket连接池把失效空闲连接反复拿出来用异步框架里的回调没有走退出分支。遇到CLOSE_WAIT别第一时间调内核参数先查代码代码不关连接内核参数改再多也白搭。排查命令给你两组# 统计当前连接状态 ss -s # 查看8080端口上CLOSE_WAIT状态的连接 ss -tan | grep CLOSE_WAIT | grep :8080如果想知道具体是哪个进程占着连接用ss -tanp需要root能看到PID和进程名。经常有服务“关不掉”一查就是CLOSE_WAIT堆在那里进程僵着不退出。2.3 面向字节流的“粘包”一次读到的未必是一条消息TCP是字节流协议没有消息边界。你send两次对端可能一次就读完你send一次对端可能分两段接收。所以应用层必须自己定义“帧”边界最经典的做法就是“长度前缀”魔数消息长度消息体。这个规则在嵌入式场景同样成立。做Modbus TCP或者用W5500这类硬件协议栈芯片时数据收发按寄存器读写不能假设一次recv就是一整帧Modbus报文用ASIO这类异步框架做TCP Server时更要注意回调里socket的关闭与缓冲区的生命周期很多CLOSE_WAIT正是异步回调漏关socket导致的。C#开发里处理TCP接收也要把收到的字节流先存到缓冲区按帧头、长度字段找到一帧完整数据再解析而不是拿一次收到的字节数组直接开干。2.4 TCP排障日常端口、防火墙、握手重试还有个高频问题连接建立缓慢或反复失败。用tcpdump抓握手包能直观判断sudo tcpdump -i eth0 -nn port 80 and host 1.2.3.4如果只看到SYN没有SYNACK多半是对端没监听或中间防火墙丢弃如果SYN重发了几次然后放弃通常也是被拦截。不用把所有参数都背下来记住“SYN和ACK是否成对出现”这一条就够了。嵌入式设备联网时还有一个常见坑设备比如ESP01S发TCP消息到手机或服务器建立连接后如果长时间没有数据运营商或路由器会默默断开这条连接设备还自以为连着下次一发数据就收到RST。解决办法就是应用层加心跳或设计成“收到RST后自动重连”。3. UDP无连接也硬核数据报的分片与调试3.1 UDP的得与失快、简、但不可靠UDP和TCP最大的差异在于TCP是打电话UDP是寄平邮。UDP发出数据报后网络层尽力投递不保证到达、不保证顺序、不保证不重复也没有拥塞控制。但反过来它没有握手、没有连接状态、头部只有8字节端到端延迟很低还天然支持广播和组播。实时音视频、游戏同步、DNS查询这类场景丢几帧无所谓但对延迟极其敏感所以它们都选UDP。一旦在工程里选了UDP就得自己在业务层补可靠性的课加序列号、加ACK、限速、重传。很多实时通信框架本质上是“用UDP的外壳实现TCP的心”既要低延迟又要可靠代价就是协议复杂度剧增。3.2 UDP数据报长短与IP分片需要算清楚UDP报文长度字段是16位理论上最大65535字节但这是“理论值”。以太网链路的MTU通常是1500字节IPv4头加UDP头一共28字节所以一次能放进一个IP报文里的UDP载荷大约是1472字节。超过这个值IP层就会把数据报拆成多个分片。分片带来两个大坑一是只要一个分片丢失整个数据报都丢弃IP层不负责重传二是很多防火墙会直接丢弃分片。所以设计UDP发送时载荷老老实实控制在1400字节以内更稳妥。“UDP划分IP数据报片”就是这么来的。C#或Java写UDP发送端如果要在应用层发2KB的数据包不能一把梭直接send应该在应用层先按固定长度切片给每个分片加包序号、总片数、结束标志接收方再按序号重组。很多跨平台联调问题最后都能追溯到“大包被分片后丢了某个分片”。3.3 Python写UDP收发小工具的正确姿势一个最小UDP服务端import socket sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((0.0.0.0, 12001)) print(UDP listening on 12001) while True: data, addr sock.recvfrom(2048) print(addr, data.hex()) sock.sendto(back, addr)发送端import socket sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(bhello, (127.0.0.1, 12001))两个细节值得注意。第一recvfrom返回的addr是“对端地址”回包时必须用这个地址不能写死。第二UDP socket没有“连接”概念你要同时向多个目标发包时每个地址的回包都要单独处理这和TCP“一条连接”的思路完全不同。3.4 iperf3用UDP打流测出真实的链路质量UDP吞吐测试常用iperf3。服务端开iperf3 -s -u客户端执行iperf3 -c 192.168.1.10 -u -b 100M -t 30输出里能看到接收总量、丢包率和抖动jitter。丢包率超过0.1%实时音视频已经能感知到超过1%语音基本没法听。但这里必须提醒一句UDP没有拥塞控制你指定多少带宽它就发多少生产网络慎用最好在维护窗口或者独立测试环境里打流。测试结果差的时候先检查物理链路和交换机端口协商速率再看Wi-Fi信道。我排过好几个“UDP打流只能跑几十M”的案例最后发现都是客户端连到了2.4G干扰严重的信道。很多“网络玄学”其实只是没用对工具、没做对照测试。3.5 read udp: unknown error (code10054) 到底是怎么回事Windows下写UDP工具经常遇到read udp: unknown error (code10054)。这个问题要从ICMP说起你发了一个UDP数据包给某台主机的某个端口但那台主机上根本没人监听那个端口它就会通过ICMP回一个“Destination Unreachable / Port Unreachable”消息你的UDP socket感知到这个错误后就抛了10054。换句话说这不是UDP逻辑写错了而是ICMP在替你“排雷”。处理办法很简单如果业务不关心这个异步错误可以在发送端捕获并忽略它如果不想收到这类错误用不绑定目标的unconnected socket或者干脆在应用层做心跳确认。我一般还会在UDP工具里加一个“对方是否在监听”的预检逻辑比盲目发包靠谱得多。4. ICMP网络层的情报员ping与traceroute的原理4.1 ICMP不是传数据的它负责当“报幕员”ICMP全称Internet Control Message Protocol中文叫互联网控制报文协议。它用IP封装但又不是普通传输层协议没有TCP/UDP那种端口概念。它的职责是传达网络层的错误和控制信息你ping它它回Echo Reply你访问一个不存在的端口网络设备会回“目的不可达”包在路由器之间转圈还没到目的地路由器会回“超时”。所以我说它像快递公司的客服不帮你寄件但会告诉你包裹到底卡在哪。几类常用类型记一下ICMP类型含义典型触发场景0 / 8Echo Reply / Echo Requestping3Destination Unreachable网络、主机、端口不可达5Redirect路由器提示更优路径11Time ExceededTTL耗尽traceroute依赖它4.2 ping命令与ICMP协议分析ping的原理一句话就能说清发一个ICMP Echo Request如果能收到Echo Reply说明目标可达。它在排障里地位极高但真不是万能药。很多服务器会禁用ICMPping不通但业务完全正常反过来能ping通只代表目标主机的网络栈活着80端口、443端口是否在监听跟ping没关系。所以我的习惯是先ping判断链路再用端口连通性判断服务最后看应用层返回值三步缺一不可。在Wireshark里过滤icmp能看到Request和Reply包成对出现每个包都有identifier和sequence number用来匹配“这条请求对应哪条回复”。如果只有Request没有Reply要么中间设备丢了回包要么目标防火墙把ICMP处理了。这时再去配合HTTP/HTTPS访问试试两边对照很快缩小范围。另外ping的报文大小也可以调。比如ping -s 1400 目标IP能模拟接近满载的载荷。但日常连通性检查默认大小就够了不用一上来就把报文撑满。4.3 traceroute为什么能画出路径地图traceroute利用的正是ICMP超时机制。它先发TTL1的包第一跳路由器收到后发现TTL变成0就回一个ICMP Time Exceeded接着发TTL2的包第二跳回超时报文……一路下去每一跳的IP和延迟都被记下来路径就出来了。Windows的tracert默认发ICMP Echo RequestLinux的traceroute默认发UDP两者探测结果可能略有差异。中间出现星号不一定是网络断了很多路由器主动丢弃探测报文。我排过一条“去某云主机路径第6跳全星号”的故障实际业务流量完全正常只是那个设备不响应ICMP。以后看到星号把它理解成“探不到”而不是“坏了”不要一看到星号就急着报障。4.4 ICMP在UDP不起眼处的存在感前面说的UDP 10054错误根因就是ICMP的目的不可达报文被上层socket感知到了。这说明ICMP虽然低调却是整个网络栈保障自检的一部分。排障时如果你在Windows下遇到UDP工具报10054第一反应不应该是“UDP代码写错了”而应该想“对端到底有没有服务在监听”。用netstat去确认一下端口问题往往立刻清楚。这种跨协议联动的思路是了解整个协议栈最有价值的地方。5. HTTP应用层通用语言状态码与连接复用5.1 先看懂HTTP报文再谈排障HTTP跑在TCP之上报文结构非常直白请求行请求头空行请求体响应是状态行响应头空行响应体。用curl看一遍比看十遍文档都管用curl -v http://127.0.0.1:8080/api/demo输出里会依次出现TCP连接建立、发送请求头、等待响应、接收响应头与响应体。这套原始过程看熟了后面理解连接复用、超时、502就都有了底。状态码是排障的第一线索常用速查状态码含义排障指向200OK一切正常301/302重定向看Location检查是否跟随重定向400请求语法错误查请求体、Content-Type401/403未认证 / 禁止访问查鉴权、Cookie、Token、权限404资源不存在查URL路径、路由配置500服务器内部错误看服务端日志502网关拿到无效响应查上游服务、网关配置503服务暂时不可用查限流、健康检查、重启504网关超时查上游响应时间、超时设置不要把502直接等同于“服务端应用崩了”。502发生在网关这一层最常见的原因是“网关后面没有能用的服务”上游进程崩了、端口换了、健康检查挂了都会表现为网关回502。遇到unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:15721/...这类报错我第一件事就是把URL里的地址直接拿去浏览器或curl访问一遍。本机能访问而程序访问不了问题在程序与网关之间本机也502就去看那个端口上的真实服务。还有一种“000”类错误比如Conda安装包时的HTTP 000 CONNECTION FAILED。这属于连接层面的失败DNS解析失败、TCP三次握手失败、TLS握手失败都可能归类为000。排查顺序别乱先用nslookup看域名解析再用telnet或nc试TCP通不通最后用curl看TLS是否正常。这个思路适用于任何“HTTP层啥都没得到”的情况。5.2 HTTP连接复用keep-alive为什么值得重视HTTP/1.1默认开启连接复用也就是Connection: keep-alive。同一个TCP连接上可以连续完成多个请求和响应这样就不必为每一个小资源都做一次完整的TCP握手、挥手。一个网页有几十个静态资源连接复用的性能收益非常明显。如果客户端或服务端任意一方“响应完就关闭连接”代价就是每个资源都来一次完整握手TIME_WAIT大量堆积网关和负载均衡的压力也会变大。连接复用的坑在于双方要协调关闭时机。客户端以为连接还能用服务端却悄悄关了下一次请求恰好撞上已关闭的socket就会报Connection reset by peer。解决办法是客户端设置合理的keep-alive超时服务端在关闭前明确告诉客户端。调试时用Wireshark过滤http看同一个TCP流里是否连续出现多次请求和响应一眼就能确认有没有复用成功。JMeter录制HTTPS脚本时也要理解这一点如果录制出来的脚本里每个请求都新建TCP会话压测结果基本没什么参考价值。5.3 HTTP在嵌入式与工程集成里的小坑嵌入式设备跑HTTP客户端要重点考虑内存、超时、连接生命周期。STM32这类MCU上做HTTP请求常见做法是拿现成http库封装但如果库内部默认开启keep-alive长连接设备端就一直维持TCP资源反而费电。更稳妥的方式是短连接请求结束就断开。ESP01S这类模块发送TCP消息到服务器或手机更多要关注模块指令的时序和回显不能把服务器当成无限带宽的公共接口。后端调第三方HTTP API也会遇到连接复用问题。一个很常见的坑对方接口其实很稳但客户端连接池里的空闲连接早已被服务端关闭下次请求就会报错。把客户端连接池的空闲时间设得比服务端keep-alive时间短一点问题通常自然消失。另外写代码时一定把完整URL、端口和异常类型都打到日志里只保留一个错误码能让你在联调时多花三倍时间。5.4 HTTP调试工具箱我平时调试HTTP基本就四样东西curl、nc、Wireshark和业务日志。curl -v看请求头和响应头curl -i直接输出响应头对症下药。nc可以发起一个最原始的TCP连接手动敲HTTP请求来测试服务端响应printf GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n | nc -v example.com 80这样做的好处是排除掉各种客户端库的干扰直接看服务端对裸HTTP请求的响应特别适合判断问题出在HTTP解析层还是业务代码层。Wireshark过滤http或tcp.port 8080观察实际发包往往能看到应用日志里根本没有的信息。排查顺序建议从客户端开始一层一层向外走而不是上来就抓包。6. HTTPS给HTTP加一层加密从握抓到明文捕获6.1 为什么需要HTTPS不只是“加个锁”HTTP是明文协议报文经过的每个路由器、每个WiFi热点理论上都能看到原始内容。登录密码、业务数据在明文中被截获后果不用多说。HTTPS全称是HTTP over TLS它在HTTP和TCP之间加入TLS安全层解决三个问题数据被加密、数据被篡改能发现、服务器身份可验证。TLS握手大致分五步客户端发ClientHello携带随机数和算法列表服务器回ServerHello附带证书客户端验证证书并生成预主密钥双方用非对称密钥交换协商出同一个“会话密钥”后续数据全部用会话密钥做对称加密。重点在于公钥加密只用在握手阶段真正通信还是对称加密所以现代HTTPS并没有慢到不可用的程度。6.2 http和https的区别不只是多了个s对比项HTTPHTTPS默认端口80443是否明文传输是否身份认证无证书验证数据完整性无TLS校验建连开销TCP握手TCP握手TLS握手首次访问更慢适用场景普通内容登录、支付、业务接口由于HTTPS比HTTP多出握手开销性能优化基本都围绕“复用”展开。服务端开启TLS会话恢复客户端连接池复用同一个TLS会话第二次访问的额外开销几乎可以忽略不计。如果你的服务首次访问很慢优先看证书链大小和TLS版本配置别急着认定是机房线路问题。6.3 HTTPS明文捕获在可控环境下做安全调试调试HTTPS报文比HTTP麻烦得多因为内容是加密的。但开发阶段又确实需要看明文排查登录接口为什么收不到验证码核对支付回调字段是否完整JMeter录制HTTPS脚本这些都需要解密。做法通常有两种。第一种是用抓包调试工具如Fiddler、Charles、mitmproxy。这类工具会安装一个本地根证书在浏览器和服务器之间完成“解密-明文展示-再加密”的转发。操作步骤启动工具导出并信任根证书到本机访问测试域名工具里就能看到明文请求。这里有一条必须守住的边界只可以在自己有权测试的环境和测试服务器上做绝不能跑到别人的网络或生产环境里安装根证书并抓取流量更不能下载来源不明的根证书。第二种是Wireshark配合密钥日志文件。很多浏览器和系统库支持通过环境变量把TLS会话密钥导出到文件Wireshark读取后就能解密HTTPSexport SSLKEYLOGFILE/tmp/tls.log然后启动Chrome或Firefox访问目标站点在Wireshark的TLS设置里指向这个文件重新抓包就能看到解密后的HTTP内容。JMeter录制HTTPS脚本本质上也类似需要把录制工具生成的证书装进本机信任库否则浏览器会一直提示证书告警。6.4 常见的HTTPS错误与证书排查日常遇到的“您的连接不是私密连接”多半是以下原因之一证书过期、证书与域名不匹配、证书链未完整下发、系统时间错误、自签名证书不被信任。查询服务器证书最方便的办法openssl s_client -connect example.com:443 -servername example.com输出里能看到证书有效期、签发者和验证链结果。如果出现verify error再结合状态码和客户端本地时间去判断。我处理过一个很奇怪的案例服务器证书链本身没问题但中间网关设备缓存了一个过期证书导致一批用户全报错。这种问题只看服务器是看不出来的要在用户访问路径上能“看到TLS证书”的节点去排查。还有一点容易被忽略HTTPS的“安全”依赖证书私钥的保护。私钥泄露或者客户端没有正确校验证书传输再加密也白搭。自签名证书只建议用于开发和内部测试不要把生产环境也改成自签。线上服务要用受信任CA签发的证书并做好到期提醒——很多大事故其实就是“证书过期”这种最土的原因引起的。这几类协议串在一起看真正想分享的经验就一句话排障先分“层”。拿到网络问题先确定它发生在哪个协议层再展开对应的判断逻辑。ping不同先看链路与网络层ping通但端口连不上把注意力放到TCP连接状态和防火墙端口通但业务报错仔细查应用层状态码和请求报文遇到加密流量异常再检查TLS握手与证书。这套流程用多了会发现百分之八十的网络故障都不神秘只是协议之间互相咬合的细节没串起来。抓包永远比猜快日志要保留完整URL、IP、端口和异常类型把这些基础打扎实比收藏一堆“救火命令”有用得多。