尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

深入解析TCP三次握手与四次挥手:从原理到实战排查

深入解析TCP三次握手与四次挥手:从原理到实战排查 1. 项目概述为什么我们需要“握手”与“挥手”如果你写过网络程序或者排查过服务器连接问题大概率见过Connection refused、connect timeout或者TIME_WAIT状态过多这类错误。这些问题的根源十有八九都指向了TCP连接建立与关闭的机制。今天我们不绕弯子直接拆解这个被问了无数遍的经典面试题和运维难题TCP的三次握手和四次挥手。我的目标很简单让你看完这篇下次再遇到相关报错时心里能立刻浮现出连接正处于哪个阶段问题可能出在哪一环。TCP传输控制协议是互联网的基石它负责在不可靠的IP网络之上提供可靠的、面向连接的、字节流式的数据传输服务。所谓“面向连接”就好比两个人打电话不是拿起话筒就喊而是要先拨号、对方接听、互相确认“喂听得到吗”然后才开始正式通话。挂电话时也不会直接掐断通常会说“好那就这样再见”等对方也回应“再见”后才按下挂断键。TCP的“三次握手”就是建立连接时的确认流程“四次挥手”则是终止连接时的告别流程。理解这个过程是理解一切网络通信异常的基础。2. TCP连接的生命周期与状态机全景在深入细节之前我们需要建立一个宏观视角。一个TCP连接从无到有再到消亡会经历一系列明确的状态变迁。这些状态在你的操作系统如Linux中可以通过netstat或ss命令查看。理解状态机是诊断连接问题的地图。一个完整的TCP连接生命周期通常包括以下几个阶段CLOSED初始状态表示没有连接活动或连接已完全关闭。连接建立阶段通过三次握手从 CLOSED 状态变迁到 ESTABLISHED 状态。数据传输阶段 (ESTABLISHED)连接已建立双方可以双向传输数据。这是连接存续期间的主要状态。连接终止阶段通过四次挥手从 ESTABLISHED 状态变迁回 CLOSED 状态。其中握手和挥手过程涉及的状态转换最为复杂也最容易出问题。比如你常听到的SYN_SENT、SYN_RCVD、FIN_WAIT_1、CLOSE_WAIT、LAST_ACK、TIME_WAIT等都是在这个过程中出现的临时状态。后续我们会把每个状态对应到握手和挥手的每一步中让你知其然更知其所以然。3. 三次握手深度解析如何可靠地“搭上线”三次握手是TCP连接建立的唯一标准方式。它的核心目的是同步双方的初始序列号ISN并交换一些必要的参数如窗口缩放因子、是否支持SACK等。序列号是TCP实现可靠传输、顺序交付和去重的关键。3.1 握手过程步步拆解假设客户端Client主动向服务器Server发起连接。第一步SYN客户端发送一个TCP报文段。这个报文有几个关键标志将SYN标志位设置为1表示这是一个连接请求。随机生成一个初始序列号ISN假设为client_isn J并放在序列号字段中。此时客户端进入SYN_SENT状态。这个报文不携带任何应用层数据。你可以把它理解为客户端对服务器说“嗨我想和你建立连接我这边的起始号码是J。”第二步SYN-ACK服务器收到SYN报文后如果同意建立连接则会回复一个报文段将SYN和ACK标志位都设置为1。服务器也随机生成自己的初始序列号假设为server_isn K放在序列号字段。确认号字段设置为client_isn 1即J 1。这表示“我已经收到了你序列号为J的SYN报文我期望你下一个报文序列号是J1”。此时服务器进入SYN_RCVD状态。这个报文可以理解为服务器的回应“收到你的连接请求了ACK你的J我同意连接我这边起始号码是KSYN我的K。”第三步ACK客户端收到服务器的SYN-ACK报文后需要向服务器发送最后一个确认报文将ACK标志位设置为1。序列号字段设置为client_isn 1即J 1。因为第一步的SYN消耗了一个序列号。确认号字段设置为server_isn 1即K 1。表示“我收到了你序列号为K的SYN报文我期望你下一个报文序列号是K1”。此报文可以携带应用层数据例如HTTP请求。发送后客户端进入ESTABLISHED状态。服务器收到这个ACK后也进入ESTABLISHED状态。至此连接建立成功双方可以开始全双工数据传输。为什么是三次不是两次或四次这是一个经典问题。三次是理论上保证双方互相确认彼此收发能力的最小次数。两次不够如果只有两次客户端SYN - 服务器SYN-ACK客户端知道它能发能收服务器能收能发。但服务器无法确认客户端接收能力是否正常客户端的ACK可能丢失。在不可靠的网络中这会导致服务器在认为连接已建立的状态下白等浪费资源。四次多余客户端的ACK已经足以确认双方的收发通道。再多一次确认只是冗余降低效率。因此三次是效率和可靠性的完美平衡。3.2 核心参数与选项交换握手不仅仅是交换SYN和ACK。在SYN和SYN-ACK报文中TCP头部还包含一个“选项”字段用于协商一些高级参数这对性能至关重要最大报文段长度 (MSS)告知对方自己愿意接收的最大TCP报文段大小。这通常基于底层网络接口的MTU计算得出目的是避免IP分片。窗口缩放因子 (WS)TCP头部的窗口字段只有16位最大只能表示65535字节的接收窗口。在现代高速网络中这远远不够。通过窗口缩放选项可以将实际窗口大小左移若干位缩放实现上G字节的窗口。选择性确认 (SACK)允许接收方告知发送方哪些不连续的数据块已经收到这样发送方只需重传真正丢失的包而非从第一个丢失包开始全部重传极大提升重传效率。时间戳 (TS)用于更精确的计算往返时间RTT和防止序列号回绕PAWS。这些选项都在握手阶段协商确定并在整个连接生命周期内生效。这也是为什么有时候抓包看握手报文长度会超过40字节标准TCP头20字节IP头20字节的原因。3.3 实操中的问题与排查1. 连接失败Connection refused这通常发生在第一次握手。客户端发送SYN后服务器目标端口没有进程在监听。服务器的TCP栈会直接回复一个RST(复位) 报文。客户端收到RST后报出此错误。常见原因服务进程未启动、配置了错误的监听端口、防火墙规则阻断了连接。2. 连接超时connect timeout客户端发送SYN后迟迟收不到服务器的SYN-ACK。可能原因服务器的SYN-ACK报文在网络上丢失。服务器过于繁忙来不及处理新的SYN请求SYN队列满。中间网络设备如防火墙丢弃了SYN或SYN-ACK报文。 操作系统内核会有重试机制例如Linux默认重试5次间隔为1s, 2s, 4s, 8s, 16s全部失败后返回超时错误。3. SYN Flood攻击与防御攻击者伪造大量虚假IP地址向服务器疯狂发送SYN报文但不完成第三次握手。服务器会为每一个SYN分配资源进入SYN_RCVD状态并等待一段时间SYN_RECV超时时间。大量半连接会耗尽服务器的资源如tcp_max_syn_backlog队列导致无法为正常用户服务。 防御手段启用syn cookies在收到SYN时不立即分配资源而是根据SYN报文计算一个cookie值作为初始序列号放在SYN-ACK中。只有收到携带正确cookie的ACK即完成三次握手时才分配连接资源。Linux系统可通过net.ipv4.tcp_syncookies 1开启。调整内核参数如减小tcp_synack_retries降低重试次数和等待时间增大tcp_max_syn_backlog和somaxconn增加队列容量。4. 数据传输与可靠性保障机制连接建立后就进入了ESTABLISHED状态。此时的数据传输并非简单地“发送-接收”而是依靠一套精密的机制来保证可靠性。4.1 序列号与确认机制每个传输的字节都被分配一个序列号。接收方通过回复ACK报文来确认已成功接收到的数据ACK报文中的确认号表示“期望收到的下一个字节的序列号”。例如发送方发送了序列号1001-2000的数据接收方成功接收后会回复一个ACK确认号为2001。累积确认TCP通常采用累积确认。ACK 2001意味着序列号2000及之前的所有数据都已收到。这简化了设计但有个缺点如果2001-3000的数据段丢失而3001-4000的数据段先到了接收方仍然只能回复ACK 2001无法告知3001-4000已收到。这就是引入SACK的原因。超时重传与快速重传超时重传 (RTO)发送方每发送一个数据段都会启动一个重传定时器。如果在定时器超时前未收到该数据段的ACK就会重传。超时时间RTO是根据动态计算的RTT往返时间自适应调整的。快速重传如果接收方收到一个失序的数据段例如收到了序列号3001-4000但2001-3000没收到它会立即重复发送之前最后一个按序到达的数据段的ACK即重复ACK 2001。当发送方连续收到3个相同的重复ACK时它就推断该数据段可能丢失并立即重传丢失的数据段序列号2001-3000而不必等待超时。这大大提高了丢包恢复速度。4.2 流量控制与滑动窗口为了防止发送方发送数据过快导致接收方缓冲区溢出TCP使用滑动窗口进行流量控制。接收方在每次发送ACK时都会通过TCP头部的“窗口”字段告知发送方自己当前还有多少可用的接收缓冲区空间接收窗口rwnd。发送方维护一个发送窗口其大小等于min(拥塞窗口, 接收窗口)。只有落在发送窗口内的数据才能被发送。随着接收方ACK的返回发送窗口向右“滑动”。如果接收方缓冲区满了它会通告一个零窗口。发送方会通过持续探测发送零窗口探测报文来等待窗口重新打开。4.3 拥塞控制这是TCP最精妙的部分之一目的是避免网络过载。它通过一个“拥塞窗口cwnd”来动态调整发送速率。主要算法包括慢启动连接开始时或检测到拥塞后cwnd从一个很小的值如1个MSS开始每收到一个ACKcwnd就增加一个MSS。这是指数级增长旨在快速探测网络可用带宽。拥塞避免当cwnd增长到慢启动阈值ssthresh后进入线性增长阶段每经过一个RTTcwnd增加一个MSS。快速恢复在快速重传触发后执行的一种优化避免像超时重传那样将cwnd直接降为1而是降为新的ssthresh值并进入拥塞避免阶段。现代Linux内核默认使用CUBIC算法它对高带宽、高延迟的网络如长肥网络有更好的表现。5. 四次挥手深度解析如何优雅地“说再见”数据传输完毕任何一方都可以发起关闭连接的请求。由于TCP连接是全双工的每个方向必须单独关闭。因此终止连接通常需要四个报文段称为四次挥手。5.1 挥手过程与状态变迁假设客户端先发起关闭。第一步FIN客户端应用程序调用close()或shutdown(SHUT_WR)TCP会发送一个FIN报文。将FIN标志位设置为1。序列号设为当前已发送数据的最后一个字节序列号1假设为Seq M。客户端进入FIN_WAIT_1状态。这意味着客户端已没有数据要发送但可能还能接收数据。第二步ACK服务器收到FIN后内核会立即回复一个ACK报文。ACK标志位设置为1。确认号字段设置为M 1。服务器进入CLOSE_WAIT状态。此时从客户端到服务器的连接方向关闭了但服务器可能还有数据要发送给客户端即连接处于“半关闭”状态。服务器的应用程序会收到一个“文件结束符”EOF告知它客户端已结束发送。第三步FIN当服务器应用程序也处理完数据并调用close()时服务器TCP会发送自己的FIN报文。FIN标志位设置为1通常和ACK一起发送即FIN-ACK。序列号设为服务器这边最后一个字节序列号1假设为Seq N。服务器进入LAST_ACK状态等待客户端的最终确认。第四步ACK客户端收到服务器的FIN后必须发送ACK进行确认。ACK标志位设置为1。确认号字段设置为N 1。客户端随后进入TIME_WAIT状态。等待一段时间2MSL下文详解后客户端才进入CLOSED状态。 服务器收到这个ACK后立即进入CLOSED状态。至此连接完全关闭。5.2 为什么需要四次挥手因为TCP连接是全双工可以看作两个独立的方向。一次FIN只关闭一个方向的数据流。因此主动关闭方发送FIN关我这边被动关闭方ACK这个FIN知道你关了然后被动关闭方处理完自己的数据后再发送自己的FIN关我这边主动关闭方再ACK知道你关了。所以最少需要四次交互。有没有可能变成三次有可能。如果被动关闭方在收到第一个FIN时已经没有任何数据要发送它可以将自己的FIN和对客户端FIN的ACK合并成一个报文发送这就变成了三次交互。这在抓包中经常能看到。但TCP协议标准仍然以四次挥手为基本模型。5.3 关键状态TIME_WAIT 与 CLOSE_WAIT这两个状态是线上问题的高发区。1. CLOSE_WAIT 状态过多这是被动关闭方的状态。如果服务器上出现大量CLOSE_WAIT状态的连接几乎可以断定是服务器应用程序的问题。它收到了客户端的FIN对方已关闭也回复了ACK但自己的应用程序没有及时调用close()来发送FIN导致连接一直卡在这个状态。原因排查应用程序代码有Bug没有正确关闭Socket。应用程序处理逻辑太慢或阻塞来不及关闭连接。线程池或连接池资源耗尽无法处理关闭事件。解决方案检查服务器应用程序代码确保在所有执行路径上包括异常路径都正确关闭了Socket。设置合理的Socket超时时间。2. TIME_WAIT 状态过多这是主动关闭方的状态。客户端或作为主动关闭方的服务器在发送最后一个ACK后会进入TIME_WAIT并持续2MSLMaximum Segment Lifetime报文最大生存时间Linux默认是60秒。TIME_WAIT存在的两个核心原因可靠地终止连接确保最后一个ACK能到达对端。如果这个ACK丢失被动关闭方处于LAST_ACK会超时重传FIN。主动关闭方在TIME_WAIT状态下收到这个重传的FIN可以重发ACK从而保证连接能正常关闭。防止旧连接的数据包干扰新连接等待2MSL时间足以让本次连接产生的所有报文都在网络中消失。这样一个迟到的、属于旧连接的报文就不会被误认为是新连接的报文因为序列号可能复用。TIME_WAIT过多的影响每个TIME_WAIT连接都占用着一个本地端口、内存等资源。在高并发短连接的场景下如压测、爬虫作为客户端的机器可能会快速耗尽可用端口net.ipv4.ip_local_port_range范围内的端口导致无法发起新连接错误表现为Cannot assign requested address。优化方案启用端口复用net.ipv4.tcp_tw_reuse 1。允许将处于TIME_WAIT状态的端口用于新的OUTBOUND连接即作为客户端。前提是安全时间戳选项net.ipv4.tcp_timestamps必须开启默认为1。这能有效缓解客户端端口耗尽问题。调整tcp_max_tw_buckets限制系统中TIME_WAIT连接的总数超出后系统会直接回收并打印警告。这是一个“兜底”方案治标不治本。优化应用架构将短连接改为长连接使用连接池。这是最根本的解决办法。6. 常见网络问题与抓包实战分析理论说再多不如一次实战抓包。我们使用tcpdump或 Wireshark 工具结合具体错误来分析。场景一分析“Connection refused”在客户端执行telnet server_ip 9999假设9999端口无服务监听同时抓包。# 客户端发送 IP client.port server.9999: Flags [S], seq ... # 服务器回复 IP server.9999 client.port: Flags [R.], seq 0, ack ..., win 0你会清晰地看到服务器回复了一个RST报文Flags中含有R这就是“拒绝”的根源。场景二分析“TIME_WAIT”堆积在频繁创建短连接的客户端机器上执行ss -tan | grep TIME-WAIT会看到大量连接处于此状态。抓包观察挥手过程你会看到完整的四次报文交换并注意到客户端在发送最后一个ACK后该连接在本地状态中停留了约1分钟2MSL。场景三连接卡在“CLOSE_WAIT”在服务器上发现大量CLOSE_WAIT。抓包过滤该连接你会看到客户端 - 服务器: FIN 服务器 - 客户端: ACK (至此服务器进入CLOSE_WAIT) ... (此后长时间没有报文) ...抓包证明服务器收到了FIN并ACK了但后续没有发出自己的FIN。问题锁定在服务器应用程序。场景四握手失败服务器无响应客户端发送SYN后无任何回复。抓包可能只看到出去的SYN没有回来的SYN-ACK或RST。这需要分段排查检查客户端SYN是否到达服务器网卡 (tcpdump -i eth0 host server_ip and port server_port在服务器上抓包)。如果服务器收到了SYN检查是否有进程监听 (netstat -tlnp | grep :port)。检查服务器防火墙规则 (iptables -L -n -v) 或安全组策略是否丢弃了SYN或SYN-ACK。检查中间网络设备负载均衡、代理的配置和日志。7. 内核参数调优与生产环境建议对于高并发服务默认的TCP内核参数可能不够用。以下是一些关键参数及其调整思路以Linux为例文件位于/etc/sysctl.conf连接建立相关net.ipv4.tcp_syn_retries 2客户端SYN重试次数默认5次约180秒内网环境可降低至2-3次加快失败感知。net.ipv4.tcp_synack_retries 2服务器SYN-ACK重试次数用于防御SYN Flood可适当降低。net.core.somaxconn 65535调整全连接队列accept队列的最大长度需要配合应用程序的listen(backlog)参数一起调整。net.ipv4.tcp_max_syn_backlog 65535调整半连接队列SYN队列的最大长度。连接断开与回收相关net.ipv4.tcp_tw_reuse 1如前所述允许复用TIME_WAIT端口用于新连接出向。net.ipv4.tcp_fin_timeout 30调整FIN_WAIT_2状态的超时时间秒如果对端一直不关闭连接在此状态停留的时间。默认60秒可酌情减小。net.ipv4.tcp_keepalive_time 600启用TCP保活机制探测空闲连接是否存活。默认2小时太长可设为10-30分钟。性能与缓冲区相关net.ipv4.tcp_window_scaling 1启用窗口缩放必须开启。net.ipv4.tcp_sack 1启用选择性确认必须开启。net.ipv4.tcp_timestamps 1启用时间戳用于RTT测量和PAWS也是tcp_tw_reuse的前提必须开启。net.core.rmem_max / wmem_max调整TCP套接字接收/发送缓冲区的最大值。net.ipv4.tcp_rmem / tcp_wmem定义TCP接收/发送缓冲区的自动调整范围min, default, max。调整后的生效执行sysctl -p使修改生效。请注意任何参数调整都需要结合实际的业务流量、硬件资源和网络状况进行测试切勿盲目照搬生产环境。理解TCP三次握手和四次挥手不仅仅是背下几个包和状态的名字。它是一把钥匙帮你打开网络问题排查的黑盒。下次再看到TIME_WAIT过多导致端口耗尽或者服务端CLOSE_WAIT堆积导致资源泄漏你就能立刻定位到问题发生的具体阶段并从应用程序或系统配置层面找到优化方向。网络编程和运维本质上就是和这些状态与报文打交道摸清了它们的脉络很多问题都会变得清晰起来。
返回列表