ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TCP三次握手原理详解:抓包验证与连接故障排查实战

TCP三次握手原理详解:抓包验证与连接故障排查实战 这次我们不只讲概念还把为什么需要三次握手、抓包怎么看、常见报错怎么排查一起拆开。TCP 三次握手是网络工程师、后端开发、运维同学绕不开的基础但很多人在面试时能背出 SYN、SYN-ACK、ACK一遇到真实故障就不知道从哪里下手。这篇文章会帮你把“书本上的三次握手”变成“自己动手能验证的三次握手”读完你可以在自己的电脑上抓包复现全过程。先说清楚这篇文章的范围三次握手的核心原理、TCP 报文关键字段、客户端与服务端的状态变化、Wireshark/tcpdump 抓包验证方法、Python 模拟连接实验、常见连接超时和连接重置排查思路。文中不涉及路由器、交换机配置也不会展开拥塞控制放心往下看。1. TCP 三次握手核心知识点速览知识点说明协议层传输层协议位于 IP 层之上可靠性基础通过序号、确认号、重传机制保证数据不丢不乱三次握手目的确认双方收发能力正常同步初始序号三次报文SYN、SYNACK、ACK客户端状态CLOSED - SYN_SENT - ESTABLISHED服务端状态LISTEN - SYN_RCVD - ESTABLISHED典型端口源端口随机目标端口固定如 80、443、3306失败现象connect timeout、connection reset by peer、bind: address already in use三次握手解决的核心问题只有一个在不可靠的信道上让通信双方确认“我能发送、你能接收、你能发送、我能接收”。少了任何一次确认都无法同时确认双方的收发能力。从实际开发角度看这部分知识直接关系到连接超时、端口被占用、代理连接断开等真实排障场景。热词列表里出现的tcp connect超时、curl: (35) tcp connection reset by peer、error: listen tcp 127.0.0.1:11434: bind全是三次握手与连接生命周期相关的报错后面会专门讲。2. 为什么是三次不是两次或四次很多人在面试时被问“为什么 TCP 建立连接要三次握手”标准回答是“防止失效的连接请求突然到达服务器”。这个答案没错但不够完整。需要理解的是TCP 要求连接双方都确认对方的收发能力同时还要同步双方的初始序列号。2.1 两次握手的问题假设只用两次握手客户端发送 SYN服务端收到后回复 SYNACK然后服务端就认为连接建立成功。问题在于如果客户端发送的 SYN 在网络中滞留了较长时间客户端已经超时放弃并发起新的连接而旧的 SYN 又突然到达服务端服务端会误以为这是一个新连接请求于是为这个失效请求分配连接资源浪费内存和端口。如果客户端收到服务端的 SYNACK 后不回复 ACK服务端就一直处于 SYN_RCVD 状态不知道客户端是否准备好。更准确地说两次握手无法让“发送方确认接收方收到了自己的确认”。2.2 三次握手的设计逻辑第一次握手客户端发送 SYN序号为 x。客户端此时确认自己的发送能力和服务端的接收能力未知但服务端能收到才能触发后续回复。第二次握手服务端收到 SYN回复 SYNACK服务端的序号为 y同时确认号 ackx1。服务端确认客户端的发送能力正常自己的接收能力也正常同时把自己的序号 y 告诉客户端但服务端还不知道客户端是否能接收。第三次握手客户端收到 SYNACK回复 ACK确认号 acky1。客户端确认服务端的发送能力正常自己的接收能力也正常。服务端收到这个 ACK 后确认客户端的接收能力正常。到这里双方都确认了对方的收发能力也完成了初始序号同步连接建立。这就是“三次”的必要性。2.3 四次握手是否可行理论上四次也能完成上述验证比如把第二次握手拆成 SYN 和 ACK 两次发送但这样会多一个 Round-Trip TimeRTT增加建连延迟而且第三次握手的 ACK 已经可以携带应用数据效率更高。TCP 选择三次握手是在可靠性和效率之间做了平衡。3. 三次握手过程详解报文与状态变化三次握手最关键的三个报文是 SYN、SYNACK、ACK。报文内容看起来复杂其实每个字段都有明确作用。3.1 第一次握手客户端向服务端发送 SYN 报文设置 SYN1初始序号 seqx。这个 x 是客户端随机生成的一个 32 位序号客户端进入 SYN_SENT 状态。客户端 - 服务端 TCP Flag: SYN Sequence Number: x Acknowledgment Number: 0第一次握手的作用是告诉服务端我要建立连接我的初始序号是 x。3.2 第二次握手服务端收到 SYN 报文后如果同意建立连接会回复 SYNACK 报文设置 SYN1ACK1服务端的序号为 y同时确认号 ackx1。服务端进入 SYN_RCVD 状态。服务端 - 客户端 TCP Flag: SYN, ACK Sequence Number: y Acknowledgment Number: x1第二次握手的作用是告诉客户端我收到你的 SYN同意建立连接我的初始序号是 y。ackx1 表示期望收到客户端序号为 x1 的报文。3.3 第三次握手客户端收到 SYNACK 报文后回复 ACK 报文设置 ACK1确认号 acky1。此时客户端进入 ESTABLISHED 状态。服务端收到 ACK 后也进入 ESTABLISHED 状态。客户端 - 服务端 TCP Flag: ACK Sequence Number: x1 Acknowledgment Number: y1第三次握手的作用是告诉服务端我收到你的 SYNACK连接建立成功。注意第三次握手的 seq 是 x1因为第一次握手的 seqx 已经被消耗。第三次握手时ACK 报文可以携带应用数据这也是 TCP 快速打开等优化技术的基础。3.4 状态变化总结阶段客户端状态服务端状态初始CLOSEDLISTEN发送 SYNSYN_SENTLISTEN收到 SYN回复 SYNACKSYN_SENTSYN_RCVD收到 SYNACK发送 ACKESTABLISHEDSYN_RCVD收到 ACKESTABLISHEDESTABLISHED服务端在 SYN_RCVD 状态如果一直收不到第三次 ACK会根据操作系统配置超时重传 SYNACK超过重传次数后关闭连接。客户端在 SYN_SENT 状态如果一直收不到 SYNACK也会超时重传 SYN。4. TCP 报文格式三次握手该看哪些字段三次握手抓包时不需要把整个 TCP 头都背下来但以下几个字段必须认识否则看抓包结果会一头雾水。4.1 关键字段说明字段长度作用Source Port16 位源端口通常随机Destination Port16 位目标端口如 80、443Sequence Number32 位本报文段第一个字节的序号Acknowledgment Number32 位期望收到对方下一个字节的序号Data Offset4 位TCP 头长度Flags9 位SYN、ACK、FIN、RST、PSH、URG 等Window Size16 位接收窗口大小用于流量控制4.2 Flags 标志位SYN: 同步序号建连时置 1 ACK: 确认号有效除第一次握手外的报文通常都置 1 FIN: 关闭连接发送方数据发送完毕 RST: 重置连接异常时使用三次握手时只用到了 SYN 和 ACK 两个标志位。看到 SYN1、ACK0 的报文就是第一次握手报文看到 SYN1、ACK1是第二次看到 SYN0、ACK1通常就是第三次或后续普通数据报文。4.3 抓包如何区分三次报文Wireshark 中可以直接看 Flags 列如果显示[SYN]、[SYN, ACK]、[ACK]就是三次握手的三个报文。除了看标志位还要对比 Sequence Number 和 Acknowledgment Number 的变化规律确认号等于对方序号加 1这是验证三次握手的关键逻辑。5. 实验环境准备与抓包验证光看理论不实操记不牢。这里准备一套可在自己电脑上运行的抓包验证流程。不需要服务器只需要一台能联网的 Linux 或 Windows 电脑。5.1 准备工具tcpdumpLinux或 WiresharkWindows/macOScurl 或 nc 命令用于发起真实连接一个已知开放端口比如www.baidu.com:80或本地局域网服务macOS 自带tcpdump和ncWindows 可以用 Wireshark 抓包或者装 Git Bash 后用 curl 发起连接。Linux 如果有 sudo 权限直接用 tcpdump 即可。5.2 tcpdump 抓包命令sudo tcpdump -i any -nn host www.baidu.com and port 80 -c 20解释参数-i any抓所有网卡-nn不做 DNS 反解直接显示 IP 和端口host www.baidu.com过滤该域名解析出的 IPport 80过滤目标端口为 80 的报文-c 20抓到 20 个报文后停止在另一个终端执行curl -v http://www.baidu.comcurl 发起 TCP 连接的过程就是三次握手的过程终端会输出Connecting to xxx (xxx.xxx.xxx.xxx) port 80此时去看 tcpdump 输出能看到三条关键报文Flags [S] seqxxxxx Flags [S.] seqyyyyy ackxxxxx1 Flags [.] seqxxxxx1 ackyyyyy1S表示 SYN.表示 ACK[S.]表示 SYNACK。看到这三条报文顺序正确三次握手就完成了。5.3 Wireshark 图形化操作Wireshark 操作更直观启动抓包后用 curl 访问一个网站然后停止在过滤器输入tcp.flags.syn 1 || tcp.flags.ack 1再按序号排序能直接看到三次握手报文。Wireshark 默认在 Info 列显示[SYN]、[SYN, ACK]、[ACK]非常清楚。5.4 本机局域网抓包验证如果没有外网访问权限可以自己搭一个最简单的 TCP 服务端验证。用 Python 起服务方便快速下面给出一段可运行示例。# server.py import socket server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((127.0.0.1, 12345)) server_socket.listen(5) print(server listening on 127.0.0.1:12345) conn, addr server_socket.accept() print(client connected:, addr) conn.close() server_socket.close()再写一个客户端# client.py import socket client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((127.0.0.1, 12345)) print(connected to server) client_socket.close()先运行python server.py再运行python client.py同时用 tcpdump 抓回环接口命令如下sudo tcpdump -i lo -nn port 12345 -S -c 10-S参数可以让 tcpdump 显示绝对序号更容易看出 seq、ack 的逻辑关系。6. 使用 nc 和 Python 验证三次握手抓包看到报文之后还可以通过 nc 和 Python 模拟一次完整的连接过程加深对状态的理解。6.1 用 nc 建立连接环境准备一台 Linux 服务器开放一个端口或者本机自己监听。nc -l 0.0.0.0 9999客户端执行nc 127.0.0.1 9999然后查看连接状态ss -tnp | grep 9999正常情况下能看到类似ESTAB的状态。如果连接没有建立成功会看到SYN-SENT、SYN-RECV等状态这些状态本身就是三次握手过程的直观体现。6.2 监听系统 socket 状态在 Linux 上ss或netstat是排查连接状态最常用的两个命令。ss -tn state syn-sent ss -tn state syn-recv ss -tn state establishedSYN-SENT客户端已经发 SYN等待服务端回复SYN-RECV服务端收到 SYN 并回复 SYNACK等待客户端 ACKESTAB三次握手完成看到大量 SYN-SENT 或 SYN-RECV 状态说明建连过程卡住了需要进一步排查网络连通性或服务端资源。6.3 模拟连接失败场景如果服务端监听端口不存在客户端 connect 会直接失败。用 Python 连接一个未监听的端口import socket client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: client_socket.connect((127.0.0.1, 9998)) except ConnectionRefusedError as e: print(connection refused:, e) finally: client_socket.close()运行后终端输出connection refused。这是因为目标端口没有服务在监听服务端直接回了 RST 报文连接被重置。这个现象就是热词列表里connection reset by peer的一种常见原因。7. 三次握手常见故障与排查方法实际开发运维中遇到最多的问题不是“三次握手怎么握”而是“连接建立不起来”。这里把热词列表里出现的常见报错和排查思路整理成表。7.1 常见报错排查表问题现象可能原因排查方式解决方案connect timed out网络不通、防火墙丢弃 SYN、目标 IP 不可达ping 测通telnet/nc 测试端口检查路由和防火墙策略connection reset by peer服务端进程崩溃、端口未监听、中间设备发 RST查看服务端日志抓包看 RST 来源检查服务进程确认端口监听bind: address already in use端口被占用ss -lnp查看占用进程换端口或复用 SO_REUSEADDRonly one usage of each socket address端口冲突服务启动失败查看端口占用换端口或杀掉占用进程大量 SYN_RECV半连接队列满或收不到客户端 ACK查看服务端内核参数抓包调整 tcp_max_syn_backlog、tcp_syncookies大量 SYN_SENT客户端发 SYN 无响应抓包看是否收到 SYNACK检查防火墙、路由、对端负载7.2 端口被占用问题热词中出现了error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address这是本地服务启动时的常见报错意思是端口 11434 已经被其他进程占用。排查方法ss -lnt | grep 11434或者lsof -i :11434找到占用进程后杀进程或改用其他端口即可。Python 服务端启动时也可以通过SO_REUSEADDR缓解 TIME_WAIT 状态导致的端口占用问题但解决不了“两个进程同时监听同一端口”的问题。7.3 connect 超时与会话管理tcp connect超时常见于跨地域网络或防火墙丢包。排查时先确认基础连通性ping 目标IP nc -vz 目标IP 目标端口nc -vz返回succeeded表示端口通返回timed out说明 SYN 被丢弃或不可达。如果 ping 通但端口超时优先检查防火墙策略、安全组、负载均衡后端状态。数据库连接超时还有一个常见原因应用层连接池已满或数据库max_connections达到上限。这时候 TCP 层面的三次握手其实可能已经完成但应用资源分配失败导致连接被关闭抓包看不到明显的 SYN 重传要注意区分。8. 从三次握手到四次挥手热词列表里大量出现“三次握手四次挥手”这里简要补一下四次挥手方便对照理解。TCP 关闭连接需要四次挥手原因是 TCP 连接是双工的每个方向的关闭都需要单独确认。四次挥手报文客户端 - 服务端: FIN 服务端 - 客户端: ACK 服务端 - 客户端: FIN 客户端 - 服务端: ACK第一次客户端发送 FIN表示客户端不再发送数据进入 FIN_WAIT_1第二次服务端回复 ACK表示收到关闭请求进入 CLOSE_WAIT但服务端可能还有数据要发第三次服务端发送 FIN表示服务端数据发送完毕进入 LAST_ACK第四次客户端回复 ACK进入 TIME_WAIT等待 2MSL 后关闭抓包时注意区分 FIN 和 RSTFIN 是正常关闭RST 是异常重置。排障时看到 RST 报文优先关注服务端是否崩溃、端口是否被防火墙拒绝、程序是否异常退出。8.1 TIME_WAIT 状态TIME_WAIT 出现在主动关闭连接的一方持续时长通常是 2MSLMaximum Segment Lifetime。大量 TIME_WAIT 是高并发短连接系统的常见现象本身是 TCP 的正常机制用于防止旧连接的报文干扰新连接。但如果系统负载上升且端口耗尽可能需要优化连接复用或调整内核参数不建议盲目调小 TIME_WAIT 时间否则可能引入数据混串风险。9. 端口、抓包和连接状态的实用命令清单排查 TCP 连接问题时下面这些命令值得保存。# 查看所有 TCP 连接状态 ss -tn state all # 查看 LISTEN 状态端口 ss -lnt # 查看 ESTABLISHED 连接 ss -tn state established # 查看指定端口占用 lsof -i :3306 # 抓取指定端口的完整 TCP 报文 sudo tcpdump -i any -nn port 3306 -S # 测试端口连通性 nc -vz 192.168.1.100 3306Windows 上可以用netstat -ano | findstr 3306 tasklist | findstr 进程PID这些命令覆盖了“端口监听、连接建立、连接状态、数据收发”的检查链路基本能定位大多数连接层问题。10. 面试知识点补充这些细节帮你加深理解三次握手是高频面试题除了前面的内容还有几个细节值得记一下。10.1 三次握手的初始序号客户端和服务端的初始序号 x 和 y 都不是固定的而是随机生成或根据时间函数计算得出。随机初始序号可以防止旧连接的报文被误认为新连接报文降低伪造 RST 攻击的风险。10.2 抓包中看到的相对序号Wireshark 默认会显示相对序号比如第一次握手 seq0第二次握手 seq0 ack1这样看起来更直观。但要注意这不是真实的序号只是 Wireshark 为了方便查看把首个序号归一化了。需要看真实序号时可以在 Wireshark 的 TCP 配置里关闭相对序号显示。10.3 SYN 重传机制客户端发送 SYN 后如果超时未收到 SYNACK会重传 SYN。Linux 下重传次数由net.ipv4.tcp_syn_retries控制默认可能重传 6 次。tcpdump 抓包时如果看到一个端口反复出现 SYN说明对端没有响应通常是防火墙丢包或服务端未启动。10.4 半连接队列与全连接队列服务端收到 SYN 后连接状态变成 SYN_RCVD此时连接存在半连接队列中三次握手完成后进入全连接队列。高并发下全连接队列满会导致连接被丢弃表现为客户端 connect 超时或服务端 accept 慢。排查时可以看ss -lt组合使用这些状态信息可以更准确地定位连接层问题。11. 90 秒记不住也没关系记住这套排查思路回到文章标题。90 秒记不住全部内容才是正常现象因为三次握手知识点密集涉及报文格式、状态变化、重传机制和大量真实报错。真正值得记住的是这套思路看到连接失败先分清是 TCP 层失败还是应用层失败用nc -vz和ss -tn快速确认。需要看细节就用 tcpdump 或 Wireshark 抓 SYN、SYNACK、ACK 三条报文确定卡在哪一次握手。卡在 SYN 无响应优先查网络和防火墙卡在 SYN_RCVD查服务端半连接队列和内核参数连接建立后秒断查应用层资源和 RST 来源。端口占用类报错优先用ss -lnt和lsof -i找到占用进程再决定是换端口还是清理进程。掌握了这套排查流程TCP 三次握手就不再只是面试题而是可以真正用来解决线上问题的技能。如果你在本地启动服务时遇到过bind: address already in use在调用远程接口时遇到过curl: (35) tcp connection reset by peer建议把这篇文章的命令保存下来下次遇到类似问题直接按表排查。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进