搞网络排查这些年,我见过最多的一种情况是:项目一卡顿,第一反应先怀疑“TCP连接断了”,结果抓包一看,地址都没配通,是IP层的锅;还有一种反过来的,盯着IP地址冲突折腾半天,其实服务端口根本没起来。这个现象背后其实是个老问题——很多人对TCP和IP这两个协议的理解,始终停留在“概念上认识、实战上混乱”。TCP/IP协议族是互联网的基石,但TCP和IP在里面各自扮演什么角色、哪儿相同哪儿不同、出问题时怎么区分,才是真正决定你能不能快速定位问题的能力。这篇我就结合这些年的实操经验,把TCP和IP协议的关系掰开揉碎讲清楚,目标是让做开发、做运维、甚至刚入门的朋友,读完都能在网络上“找对方向”。
1. 为什么从来只有TCP/IP,没有IP/TCP
1.1 TCP/IP不是两个协议,是一整套协议族
先纠正一个最常见的误解:TCP/IP不是“TCP”和“IP”两个协议的合称,而是一整套协议族的名字,标准说法叫Internet Protocol Suite。TCP和IP只是这套体系里最有名的两个核心协议,就像一家公司的两位明星高管——公司叫“TCP/IP有限公司”,但公司里还坐着UDP、ICMP、ARP、HTTP、DNS等一大堆员工。
这套协议族从1970年代美国国防部高级研究计划局的ARPANET项目里长出来,当时Vint Cerf和Bob Kahn发表了那篇著名的《A Protocol for Packet Network Intercommunication》,最早设计出来的传输层协议叫NCP,后来拆成TCP和IP两个层次,整个协议集合被统称为“TCP/IP”,这个名字一直沿用到现在。所以你会发现一个有趣的事实:为什么常说TCP/IP,而不是IP/TCP?因为TCP的“资历”更老、更出名,网上讨论网络优化时谈的也是“TCP调优”居多,IP这个底座反而常常被一笔带过。
理解TCP/IP是一整套协议族很重要,因为它决定了我们讨论“异同”时的坐标系:TCP和IP不是孤零零的两个东西,而是同一套网络体系里上下相邻的两层。要比较它们,得先看清它们各自站在哪一层、管什么事。
1.2 分层不是摆设:每一层都有各自的盘算
网络通信为什么非要分层?我用一个很直白的理由来解释:分层是为了让技术可以独立演进。底层铜缆换成光纤,上层应用不需要跟着改;上层加一个新的应用协议,底层设备也不用动。这就是“封装”和“解耦”的价值。
TCP/IP通常被画成四层模型,经常会拿来和OSI七层模型对照:
- 链路层(对应OSI的数据链路层+物理层):处理网卡、以太网帧、Wi-Fi等,MAC地址在这里。
- 网络层(对应OSI的网络层):核心就是IP协议,负责寻址和路由。
- 传输层(对应OSI的传输层):核心是TCP和UDP,负责端到端的通信管理。
- 应用层(对应OSI的高三层合并):HTTP、FTP、DNS、SSH等都在这里。
熟悉OSI的朋友会看到,TCP/IP的四层模型是OSI七层的精简版。实际工程里几乎没人严格背着七层跑,大家口里说的“网络分层”默认就是TCP/IP四层模型。
用寄快递来类比最贴切:应用层是你写的信,内容随便你想表达什么;TCP是快递公司的客服,负责打电话确认对方“收到了没”“缺不缺件”,缺了安排补寄;IP是物流中心的地址分拣系统,负责看门牌号(IP地址)、规划线路、把包裹一站站转运;链路层则是具体的那辆货车和你家门口的路。
有了这个坐标系,再来看TCP和IP各自管什么,就很清楚了。
2. IP负责找人,TCP负责说清事情
2.1 IP:无连接、尽力而为的“寻路系统”
IP位于网络层,它的活儿就两件:寻址和转发。给每一台接入网络的主机分配一个逻辑地址(IPv4的32位地址或IPv6的128位地址),然后根据目的IP地址,决定数据包下一个该丢给谁,一站一站传到目的地。
IP层的一个关键设计是“无连接”。什么意思?就是IP协议本身不维护任何“会话状态”。每个IP数据包都是独立的个体,发出去就发出去了,路由器只需要看包头的目的地址,查一下路由表,决定下一跳,根本不需要记住这个包属于谁的连接。这带来一个巨大的工程优势:路由器不用保存海量的会话状态,硬件可以高速转发,这让互联网能撑起今天这个体量。
另一个关键设计是“尽力而为”。IP不保证数据包一定能到达,也不保证到达顺序和发送顺序一致,更不保证不重复、不丢包。路由拥塞了,路由器会直接丢弃数据包;路径变化了,同一个会话的包可能走不同路线,到达时顺序颠倒。这些“不靠谱”在IP层看来都是正常的。IP层做的最多就是:包坏了(头部校验不过)就丢弃,然后用ICMP回一个差错消息告诉源主机“我丢了你的包”,仅此而已。
IP超然于“连接”之外,它只认“IP地址”这一个门牌号。两台机器之间能不能通信,IP层只关心地址对不对、路由通不通,至于对方主机上的哪个程序在收,IP不知道也不想知道。
2.2 TCP:有连接、有状态的“端到端管家”
TCP位于传输层,管的是两台主机上“具体进程与进程之间”的通信。它引入的最重要的概念是端口号——IP地址定位到某台主机,端口号定位到主机上的某个服务(流水号、进程入口)。所以一条TCP连接用四元组唯一标识:源IP、源端口、目的IP、目的端口。
TCP和IP最大的区别就在“状态”二字。TCP是面向连接的,通信双方要先建立一个“逻辑连接”,然后才能传数据。注意这个连接是“虚”的——它不像电话线路那样物理占有一条通道,本质上只是通信两端各自记录一组状态参数(序列号、确认号、窗口等),彼此约定好按这套参数收发。所谓“三次握手”,就是双方把这套初始状态敲定下来的过程。
建立连接之后,TCP用一整套机制保证“可靠交付”:
- 编号:每个字节都有序列号,接收方按序列号拼装数据。
- 确认:收到数据就回ACK确认,没收到就重传。
- 重传:超时没收到ACK就重发;收到三个重复ACK可以快速重传。
- 校验:TCP头和数据有校验和,发现损坏直接丢弃并要求重传。
- 流量控制:通过窗口字段告诉对端“我的缓冲区还能收多少”,避免撑爆接收方。
- 拥塞控制:慢启动、拥塞避免、快重传、快恢复,探测网络当前能承受多少数据,避免把网络堵死。
这些机制是IP层完全不具备的。所以你可以这么理解:IP把一个包裹扔进一个大物流网,说“能送到最好,但我无法保证”;TCP则是那个拿着电话守在电话旁的客服,“我用序列号记着发了多少个箱子,每收到一个回执就核对一下,缺了马上让寄件方补发,最后全部到齐了才告诉你:齐了,收货吧。”
2.3 一张表看清异同
很多朋友喜欢逼我总结一张对比表,那我直接给:
| 对比维度 | IP协议 | TCP协议 |
|---|---|---|
| 所属层次 | 网络层 | 传输层 |
| 核心目标 | 寻址与路由,把数据包送到目的主机 | 端到端可靠传输,把数据完整交给目标进程 |
| 寻址方式 | IP地址 | 四元组(源IP+源端口+目的IP+目的端口) |
| 是否面向连接 | 无连接 | 面向连接(虚连接) |
| 是否维护状态 | 无状态 | 有状态(序列号、确认号、窗口状态等) |
| 可靠性保证 | 尽力而为,不保证送达 | 可靠交付,重传、排序、确认 |
| 头部最小开销 | IPv4头部20字节 | TCP头部20字节 |
| 发送单位 | IP数据包 | TCP段 |
| 典型故障表现 | 网络不可达、路由黑洞、地址冲突 | 握手超时、连接被重置、端口不通 |
| 代表性机制 | 路由选择、分片、ICMP差错报告 | 三次握手、滑动窗口、拥塞控制、四次挥手 |
被说成“异同”,我们当然不能只讲“异”。TCP和IP至少有四个地方是相通的:
- 同属一个协议族,都定义在RFC标准里,头部都讲究字节对齐。
- 都运行在“网络不可靠”这个大前提下,只是应对策略不同——IP选择接受不可靠,TCP选择在不可靠之上构建可靠。
- 都基于“包交换”,不是电路交换,数据和路径不绑定。
- 实际通信中必须配合使用:IP负责把包送到主机,TCP负责把数据流完整交给应用,缺一不可。
3. 一次网页访问,看清TCP和IP各干了什么
3.1 从输入URL到TCP三次握手
理论讲完了,用一个最日常的场景串起来:你在浏览器输入一个网址,回车,网页打开。这中间TCP和IP是如何各司其职的?
第一步是DNS解析。浏览器要先知道网址对应的IP地址——这是IP层的首次出场:你得先拿到对方的“门牌号”。DNS请求本身用的是UDP(特殊情况会用TCP),但无论如何,目标主机的IP地址是整个通信的前提。
拿到IP后,浏览器开始发起TCP连接。这就是著名的三次握手:
- 客户端发送SYN报文,携带一个初始序列号x(这个数是随机生成的,用于防止伪造连接)。
- 服务端收到SYN后,回复SYN+ACK报文,携带自己的初始序列号y,同时确认号ack=x+1,表示“我收到你的编号了,期待你下一个字节的编号是x+1”。
- 客户端再回一个ACK报文,确认号ack=y+1,握手完成,连接进入ESTABLISHED状态。
为什么必须是三次而不是两次?关键原因是要防止历史失效SYN报文造成“死锁”。想象一个场景:客户端发出SYN,被网络卡了很久才到达服务端,此时客户端已经放弃。如果只要两次握手,服务端收到这个迟到的SYN后会以为自己建立了连接,一直傻等数据,浪费资源。三次握手中,客户端收到服务端SYN+ACK后,会根据自己的状态判断:如果自己确实在等待连接,就回ACK;如果这压根是历史残留连接,就回RST拒绝。服务端只有收到ACK才能确认双方状态一致。
三次握手不是没有代价,它至少消耗一个RTT(往返时间)。所以HTTP协议设计出Keep-Alive长连接复用同一个TCP连接处理多个请求,TLS 1.3更是引入了0-RTT恢复机制,这些都是为了省掉握手开销。
3.2 数据封装:TCP分段与IP分片是两回事
握手完成,浏览器把HTTP请求丢给TCP。这里TCP做的第一件事是“分段”,也就是按MSS把应用数据切成一块块合适大小的TCP段。MSS怎么算?以太网MTU是1500字节,IP头20字节、TCP头20字节,所以MSS=1460字节。超过1460字节的数据就要分成多个TCP段,每个段加上TCP头后塞进一个IP包。
这里有一个特别容易混淆的点:TCP的分段和IP的分片是完全两回事。
TCP分段发生在源主机,是端到端的主动行为:源端知道对方MSS,主动把数据切成适合的大小。IP分片发生在路径上的路由器:当中间某个链路的MTU比源端认为的更小(比如PPPoE拨号网络MTU只有1492字节),IP包太大塞不下,路由器就会把IP包切成碎片。IP分片是“被动”的、中间设备的行为,而且碎片一旦丢失会导致整个原始包重传,效率很低,所以现代主机一般都会设置DF位(不分片标志),通过PMTUD(路径MTU发现)机制提前探测整条路径的最小MTU。
你可以这样理解:TCP分段是“发货前按箱子规格打包”,IP分片是“运输途中遇到窄门被迫把箱子锯开”。前者可控,后者不可控——这就是TCP/IP设计中,能不做的事尽量在端到端做、中间设备尽量少干活的思想。
接着看封装顺序:HTTP数据交给TCP,加上TCP头变成TCP段;TCP段交给IP,加上IP头变成IP包;IP包交给链路层,加上以太网帧头和帧尾变成帧,最后变成电信号发出去。每一层只关心自己头部里面那点事儿,这就是分层思想的实际落地。
3.3 一路转发、四次挥手与连接消亡
IP包在网络上奔波时,沿途的每个路由器都只干一件事:读取IP包头里的目的地址,对照路由表,决定下一跳给谁。路由器根本不会去看TCP头的端口号,也不知道这个包属于哪条HTTP请求——对IP层来说,它的任务就是“把这个包丢给下一站”。
等数据到了目标主机,链路层帧被剥掉,IP包被交给IP模块,IP模块发现“协议号是6(TCP)”,就把载荷交给TCP模块,TCP模块校验完整性、按序列号拼装,交给应用层的HTTP服务。整个过程里,IP只做了“送达”,TCP做了“管到底”。
数据传完后还要断开连接,这就是传说中的四次挥手:
- 主动关闭方(比如客户端)发送FIN,表示“我的数据发完了”。
- 被动关闭方回ACK,表示“我知道了,但我可能还有数据要发”。
- 被动关闭方把剩余数据发完后,也发FIN,表示“我这边也发完了”。
- 主动关闭方回ACK,连接关闭。
为什么是四次而不是三次?因为TCP连接是全双工的,两个方向的通道是独立的,关闭也要独立关。步骤2和3不能合并,是因为被动方收到FIN时可能还没发完数据,必须等自己发完才能FIN。之所以很多人看到三次就结束,是因为被动方发完数据后,它的ACK和FIN经常被合并成同一个报文发送,抓包时就会看到三条消息。
挥手结束前还有最后一个值得记住的东西:TIME_WAIT状态。主动关闭方发送最后一个ACK后不会立刻关闭,而是进入TIME_WAIT,等待2MSL(报文最大生存时间两倍,通常60秒左右)。这是为了让可能迟到的数据包在网络上自然消亡,避免影响新连接。生产中你拿netstat看到大量TIME_WAIT不要慌,这是TCP在“打扫战场”,属于正常生理现象。
一次网页访问,TCP和IP就是这样你方唱罢我登场:IP负责找到人、把包裹一站站运过去;TCP负责管好“说了没、收到没、完整没、速率别把人堵死”这些精细活。
4. 别用谁强谁弱来看它们
4.1 可靠是TCP的优势,也是代价
我做性能排查时经常碰到一句话:“TCP比IP高级,可靠嘛。”这种说法其实站不住脚。可靠是有代价的,而且代价不小。
TCP的可靠靠什么换来的?首先是头部开销:TCP头最小20字节,加IP头20字节,总开销40字节起步,在语音这类小包场景里占比相当可观。其次是确认机制:每个包都要ACK,高频往返会吃掉不少吞吐。最让人头疼的是队头阻塞:TCP要保序,如果一个包的ACK迟迟不来,后面即便已经收到的包也不能交付给应用层,因为顺序乱了应用没法拼。一个丢包就能让整条连接卡顿。
TCP的拥塞控制同样不是免费的。经典的慢启动算法要求连接建立后从很小的拥塞窗口开始,逐步加倍探测带宽,这在长肥管道(高带宽高延迟链路)上要花很多个RTT才能把带宽“跑满”。我们判断一条TCP连接性能好不好,经常看四个指标:重传率、乱序率、RTT和拥塞窗口。这就暴露了TCP的“本性”:它把可靠性管得越严,网络利用率可能反而越低,因为它在小心翼翼地试探这个网络到底能承受多少。
所以我处理高并发系统时,通常花大量时间调TCP参数:增大初始拥塞窗口、开启选择性确认SACK、调整接收窗口自动调优、配置合适的keepalive时间。调这些本质上就是在“用配置换可靠性和速度的平衡”。这就是为什么做网络优化的人不会简单说“TCP就是好”,而是会说“TCP是一个可调性极强的协议”。
4.2 IP的尽力而为反而成就了扩展性
再说IP。很多人觉得“尽力而为”是IP不够强,但实际上这恰恰是它能撑起全球互联网的原因。
IP层不维护状态,意味着转发设备内部只有一张路由表,没有几十万条并发会话记录,不用跟踪“这个包属于哪个用户、下一步应该发送什么”。这种“傻白甜”的设计让路由器可以做到几十上百Gbps的线速转发,也是运营商敢把骨干设备铺到全球的原因。你看底层设备越忙,越需要简单的行为模型。
因为IP“不管连接”,各种扩展协议才有生存空间:ICMP在网络层探测路径和报告差错,IGMP支持组播,NAT在边缘设备上做地址转换,ACL、QoS都在IP层实施策略。这些功能的共同点就是只摸IP包头,不需要关心上层是不是TCP、是不是HTTP。你可以把IP层理解成一个极宽松的平台——只要按它的格式封装好,谁都能在上面跑,这也让它成为整个互联网事实上的“通用底座”。换个角度看,如果IP也像TCP一样维护海量状态,那路由器早就被压垮了。
4.3 UDP的存在提醒我们:不是所有传输都需要TCP
聊到TCP的“可靠代价”和IP的“扩展红利”,有个非常好的参照系是UDP。UDP也在传输层,和TCP共用IP这个底座,但它比TCP激进得多:没有握手,没有确认,没有重传,没有状态,头部只有8字节。UDP靠IP层提供的“尽力而为”直接干活,把可靠性完全交给应用自己决定。
什么时候你会主动抛弃TCP、投奔UDP?直播场景,一帧画面迟到比丢更糟糕,重传只会让流卡得更久,所以宁可丢掉也不要等;语音通话,实时性大于准确性;DNS查询,请求响应一次完事,根本不需要连接;还有HTTP/3,直接跑在UDP之上,把可靠传输搬到了应用层和QUIC,从而绕开了TCP的队头阻塞——因为在多路复用场景里,TCP对每个独立资源流的队头阻塞是没法接受的。
这才是看待TCP和IP真正该有的姿态:它们之间没有优劣之分,只有舍取和分工。IP用简单换来了互联网的规模,TCP用复杂换来了应用的安心。UDP则证明“可靠”不是传输层的必然义务,而是一种可选择的“服务等级”。
5. 排障实战——先分锅,再动手
5.1 通用定位法:连通性决定先查哪层
我把这些年排查网络问题的经验浓缩成一个心法:TCP/IP是分层的,排查也必须分层,别一上来就钻到细节里。
最简单的定位口诀是:先ping,后端口,再业务。这个顺序决定了你从哪一层开始查:
| 检测手段 | 检测目标 | 通过说明什么 | 不通过说明什么 |
|---|---|---|---|
| ping | IP层连通性 | 网络层基本通(不排除丢包) | IP层问题,或被防火墙策略屏蔽ICMP |
| tracert/traceroute | IP层路径 | 看到每跳延迟和丢包率 | 中间某跳路由黑洞或策略丢弃 |
| telnet/nc 测试端口 | TCP层连通性 | TCP握手能成功 | 服务未监听、防火墙丢SYN、连接队列满 |
| ss/netstat 查看状态 | TCP层本地状态 | 看到LISTEN、ESTABLISHED、TIME_WAIT等 | 端口没监听、队列溢出、握手失败 |
| 业务请求测试 | 应用层 | 业务逻辑正常 | TSL/HTTP/应用自身问题 |
有个细节要特别提醒:ping不通不等于IP层一定断了。很多系统为了安全会直接丢弃ICMP报文,这是主机防火墙或路由策略干的活,属于ICMP被禁,不代表TCP数据也过不去。判定IP层的时候要学会用多种手段交叉验证:能ping通附近的网关、能访问同网段服务器、traceroute能看到第一跳,这些都可以佐证链路状态。
5.2 IP层问题的几个常见案例
IP层故障通常有非常明确的症状:要么完全不可达,要么时通时断,要么地址根本分不到。
第一种是IP地址冲突。典型场景是你往局域网里接了一个设备,或者虚拟机没配置好,然后整片网络开始出现“某些机器通、某些机器不通”的灵异现象。排查手法是抓包看ARP:正常的ARP应答是MAC和IP的映射,如果收到多个不同的MAC回应同一个IP的ARP请求,那就是地址冲突了。还有一种简单的物理判断法:拔掉可疑设备的网线,网络立刻恢复。新建局域网规划时,最好把DHCP地址池和静态IP段分开,避免冲突。
第二种是DHCP获取不到地址。手机开热点后“IP配置失败”、虚拟机反复拿不到IP,多半是DHCP服务器故障、租约池耗尽、或者跨网段中继配置有误。注意排查时先看网卡有没有拿到169.254开头的APIPA地址,那是Windows拿不到地址时的“自嘲地址”,看到了直接锁定DHCP问题。
第三种是路由黑洞。你ping目标不通,但ping同网段网关是通的,traceroute一下,发现某个中间跳有来无回。可能原因是对端防火墙丢弃、路由宣告错误、或者中间设备的负载均衡策略异常。这类问题的特点是“看上去通了一部分,实际上是半死状态”,必须用traceroute逐跳定位。
5.3 TCP层问题的几个常见案例
IP层通了,但TCP连不上,那就是TCP的锅。这类问题最典型的症状是“ping通但业务连不上”。
案例一:Docker发布端口失败。报错类似“ports are not available: exposing port tcp 0.0.0.0:xxx”。这是典型的TCP端口已被占用。处理很简单:用ss -lntp或者netstat -ano找到那个进程,确认是不是该占、要不要杀掉或者换端口。我遇到过不少同事在容器编排里写了固定的host port,两台机器同一个端口都绑,docker起容器时必然有一台失败。好的习惯是让宿主机端口随机映射,或者用服务网格统一管理端口规划。
案例二:Harbor推送镜像时报“dial tcp 192.168.209.133:443: connect: connection refused”。这个报错信息的核心是“dial tcp”失败——本机发起的TCP连接根本没能建立。先不要急着折腾registry配置,按顺序查:目标IP能不能ping通(IP层)、443端口通不通(TCP层)、Harbor容器有没有监听(服务层)、是不是有防火墙把443拦了(策略层)。我实际处理过类似问题,最后发现是目标主机上Harbor服务因为磁盘满了起不来,ping通、端口却不监听,属于纯TCP层“无人在线”。
案例三:SYN收不到回复,连接一直挂着。抓包看客户端重传SYN,服务端不回应。原因可能是服务端的连接队列满了。Linux下有两个队列:半连接队列(syn queue)和全连接队列(accept queue)。半连接队列溢出时,内核直接丢弃SYN包,表现为“客户端SYN发出石沉大海”;全连接队列溢出时,TCP虽然完成握手但仍拒绝应用accept,表现为“握手能看到但业务无法建立”。排查命令很简单:ss -lnt查看Send-Q(表示队列最大长度)和Recv-Q(当前积压数量),netstat -s看“SYNs to LISTEN sockets dropped”计数。调优方向一般是调大小、调net.ipv4.tcp_syncookies,以及加快应用accept。
案例四:Windows下TCP时间戳调试。很多人搜过netsh int tcp set global timestamps=enabled这条命令,它用于开启TCP时间戳选项。TCP时间戳在高带宽高延迟链路上能更精确地计算往返时间,有助于RTT估算,也能配合防序列号回绕。Windows默认策略是部分场景关闭,改它的前提是两端都开启才有效。我一般只在做TCP性能对比实验时才动它,生产环境不会盲目全局开启,因为时间戳选项每个包多占12字节,而且对绝大多数内网场景没感知收益。
5.4 学习环境里怎么练排查
理论说再多,不如拿真包练一次。我特别推荐用GNS3自己搭一个模拟网络:两个路由器分别连接两台主机,然后在主机上ping对方,同时抓包,你会亲眼看到完整的过程——首先是ARP请求找对端MAC,接着ICMP Echo Request被封装成IP包走路由器转发,路由器逐跳修改TTL和校验和,最后送到目的地。这个实验几乎把IP层的工作机制完整演了一遍。
如果条件有限,用一个虚拟机也能练:修改虚拟机的IP地址后,如果出现“找不到对方设备”,很可能是ARP缓存没刷新。在Windows上执行arp -d清空缓存,在Linux上执行ip neigh flush all,网络立刻恢复。这类小实验做上几次,你对IP和TCP各自负责什么的直觉就会完全建立起来。
6. 从抓包里直观读懂TCP和IP
6.1 抓包前需要准备什么
纸上谈兵够多了,最后一节直接上干货——怎样用Wireshark或tcpdump“看见”TCP和IP各自干活的样子。
学习阶段我在GNS3里抓包,生产环境则用tcpdump在服务器上抓。几个最常用的过滤条件先记下来:
# 按主机过滤,只抓和某台机器交互的流量 tcpdump -i eth0 host 192.168.1.10 # 按端口过滤,只看TCP 80端口 tcpdump -i eth0 tcp port 80 # 同时过滤方向和协议,抓某个IP发出的TCP SYN包 tcpdump -i eth0 src host 192.168.1.10 and tcp[tcpflags] & tcp-syn != 0在Windows上可以用Wireshark直接基于图形界面抓包,Win10以上还支持用netsh trace抓回环流量。抓包尽量在自己控制的设备上做,不要在别人的网络里随便抓——这既是合规问题,也是职业操守。
6.2 报文解读:IP头与TCP头逐个字段过
抓到一个TCP SYN包,展开它的IP层和TCP层,你会看到下面这样的结构:
IP Header: Version: 4 Header Length: 20 bytes Total Length: 40 bytes Identification: 0x1234 Flags: 0x02 (Don't Fragment) Time to Live: 64 Protocol: 6 (TCP) Header Checksum: 0xabcd Source Address: 192.168.1.10 Destination Address: 192.168.1.20 TCP Header: Source Port: 54321 Destination Port: 443 Sequence Number: 2415780182 Acknowledgment Number: 0 Header Length: 20 bytes Flags: 0x002 (SYN) Window Size Value: 64240 Checksum: 0xef12看IP头的关键字段:Protocol=6说明上层是TCP;TTL=64说明这个包从源主机发出后没被转发过几次,每经过一个路由器TTL减1;Total Length=40正好是IP头20字节加TCP头20字节,说明这是一个没有载荷的纯握手包。
看TCP头的关键字段:Source Port是客户端临时端口,Destination Port是HTTPS的443;Sequence Number是客户端初始序列号,此时还没收到服务端任何数据,所以Acknowledgment Number是0;Flags显示SYN,说明这是三次握手的第一步。
有个深度细节值得了解:TCP校验和的计算不只是基于TCP头和负载,它还有一个“伪首部”包含源IP、目的IP、协议号和TCP长度。有线抓包工具算TCP校验和时,必须知道IP层的地址信息。这从侧面说明TCP和IP虽然是两层,但校验机制是联动的——TCP的可靠性建立在IP地址正确的基础上。
6.3 TCP重传和乱序的判读思路
抓包不只为了“看懂”,更多是为了判断问题性质。我排障时最关注下面三种现象:
第一是TCP Retransmission(超时重传)。原始包发出去,等待了很久没等到ACK,于是又发一遍。出现大量重传,说明网络丢包率很高,优先查链路质量和拥塞。需要说明的是抓包软件显示的“Retransmission”是它根据序列号自己推断的,实际网络里重传可能有很多层原因。
第二是Dup ACK(重复确认)。接收方收到乱序包时,会重复发送上一次正确接收到的序号,让发送方知道“我缺哪个片段”。连续收到3个Dup ACK就触发快速重传,不用等超时。如果抓包看到大量Dup ACK但重传不多,说明网络乱序严重而不只是简单丢包。
第三是Out-of-Order(乱序到达)。序列号出现跳跃,TCP层的拼装机制在起作用。这种情况在路径变化频繁的网络里很常见,也不一定代表“故障”,但如果伴随业务延迟明显,你就得看看是不是有多径负载不均的问题。
我处理过一台服务器上的偶发延迟问题,抓包三个小时才逮到一个丢包,确认是物理链路误码率过高。这时TCP的重传机制一直在帮我们“擦屁股”,业务虽然没断但延迟上去了。如果只看业务层指标,很容易误判成应用性能问题,抓包的价值就在于此——它把TCP和IP各自的行为赤裸裸地摆在桌面上,让你能精确判断问题的层次归属。
最后再分享一个小技巧:排障时不要把抓包工具看作“最后的武器”,而是从第一步就用起来。最简单的ping包开始抓,一路抓到TCP握手、业务数据,你会看到IP、TCP、应用三个维度在同一张时间轴上如何交织。看得多了,以后别人问你“TCP和IP到底什么关系”,你可以淡定地回答:一个送件,一个管收发;一个无连接的做底座,一个有状态的做保障——分工不同,使命相同。