尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HTTPS请求中的TCP与TLS握手全解析:从建立到关闭

HTTPS请求中的TCP与TLS握手全解析:从建立到关闭 握手在计算机网络里并不是凑热闹而是通信双方在正式收发数据之前先确认“你愿意通信吗”“你能听懂我说话吗”“我应该按什么规则加密”这一系列问题。如果把一次 HTTPS 请求拆开看你会发现它并不只是客户端发一个 HTTP 请求那么简单底层先有 TCP 三次握手建立可靠连接中间可能还有 TLS 握手协商加密参数代理和网关层也有各自的长连接维持逻辑最后连接关闭时还要处理一次 FIN 包的挥手过程。这篇博客会从“过来握手”这个玩梗标题切入带你把一次完整请求从建立到关闭需要经历的所有握手阶段拆开看一遍包括抓包观察方法、每个报文的关键字段、常见故障现象和排查命令。适合需要排查网络连接问题、阅读抓包文件、给服务端配置访问链路的开发者和运维人员阅读看完之后你能独立分析“连接为什么超时”“这个 HTTPS 请求为什么握手失败”“服务端为什么出现大量 TIME_WAIT”这类问题。1. 先搞清楚一次 HTTPS 请求里到底要完成几次握手1.1 从“过来握手”理解协议设计意图“过来握手”这句话背后的技术隐喻很直接两个节点刚开始通信时互相之间没有任何信任基础也不知道对方的报文处理能力。如果一方直接发送大量业务数据另一方可能根本没准备好接收也可能无法理解数据格式甚至无法确认数据是否被第三方截获。握手就是在业务数据开始传输之前双方先交换一组控制报文完成三项基本确认对方在线且可达、双方协议版本兼容、通信规则已达成一致。在 TCP/IP 网络模型里不同层承担不同任务。链路层解决数据在局域网内如何识别设备。IP 层解决报文如何跨越网络找到目标主机。TCP 层负责把字节流可靠地传给对端应用解决丢包、乱序、重复等问题。HTTP/HTTPS 属于应用层解决客户端和服务端业务语义。因此一次浏览器访问https://example.com的请求在物理链路上可能已经发生过设备间的网络协商但通常我们只需要关心 TCP 和 TLS 两层握手。TCP 握手回答“连接是否可用”TLS 握手回答“通信内容如何保密、对端身份是否可信”。1.2 TCP、TLS、HTTP 三个层次的握手不是同一件事很多刚接触抓包的开发者会混淆一个问题三次握手不是说 HTTP 请求要发三次而是传输层建立一个可靠连接需要三个 TCP 报文TLS 握手也不是重新建立 TCP 连接而是在已建立的 TCP 连接之上再做一层加密参数协商。可以把整个流程理解为一次会面过程。阶段类比技术协议核心回答的问题TCP 三次握手先敲门并确认门能打开TCP双方是否可达、初始序号是否同步TLS 握手核对身份并约定保险箱密码TLS对方身份是否可信、加密密钥如何生成HTTP 请求响应开始谈正事HTTP/HTTPS业务数据如何传输TCP 四次挥手寒暄结束关门前确认没有遗留TCP干净关闭连接、回收资源TCP 握手能直接通过netstat或ss观察状态变化TLS 握手通常要靠抓包或者openssl s_client才能看到详细参数。后面两章会分别用真实报文说明。1.3 为什么不能省略握手直接传数据如果客户端连服务端是否可达都不确认就直接发送大量数据服务端可能正忙、端口未监听、防火墙丢包。虽然 TCP 也会重传数据但没有同步序号之前接收方无法判断一段数据在整个字节流中的位置无法处理乱序和重复报文。TLS 加密更需要握手如果客户端和服务端没有协商出会话密钥后面传的业务内容就只能用明文暴露在网络中间节点上。从设计角度看握手本身是有成本的。TCP 三次握手要多出 1 个 RTTTLS 1.2 握手通常需要 2 个 RTTTLS 1.3 优化后也需要 1 个 RTT。这也是为什么 HTTP 长连接和连接复用那么重要。如果每次请求都重新握手再叠加公网延迟页面打开时间会明显变慢。理解握手的成本才能明白全链路加密、连接复用、TLS 会话恢复这些优化措施到底在解决什么。注意抓包分析时不要把 TCP 握手漏看成一个不可见的前置过程。连接建立失败的多数问题恰恰发生在 TCP SYN 发出之后比如 SYN 被防火墙丢弃、目标端口没有服务监听、半连接队列满了。2. 准备最小观察环境用 curl、tcpdump 和 Wireshark 抓真实握手包2.1 工具选型和环境准备抓包分析并不需要太复杂的平台。常见组合是客户端发起请求在客户端本机用 tcpdump 抓包再用 Wireshark 打开 pcap 文件分析。tcpdump 适合命令行抓包Wireshark 适合可视化过滤和字段解析。如果环境不允许安装图形界面也可以只用 tcpdump 输出关键过滤规则。以 Ubuntu 或 Debian 系的 Linux 系统为例安装方式如下sudo apt update sudo apt install -y tcpdump wireshark-qt如果使用 macOS可以通过 Homebrew 安装brew install wireshark这里要注意 tcpdump 抓包需要 root 权限或使用具有抓包能力的用户组。Wireshark 安装后可能需要单独把当前用户加入wireshark组否则无法在非 root 模式下抓包sudo usermod -aG wireshark $USER修改用户组后需要重新登录会话才能生效。2.2 最小复现访问一个 HTTPS 服务为了观察完整的 TCP 三次握手和 TLS 握手可以找一个测试域名发起 HTTPS 请求。实际项目中把域名替换成自己服务所在的地址即可。先启动抓包sudo tcpdump -i any -nn -s 0 host your-service.example.com and port 443 -w handshake.pcap参数含义-i any抓取所有网卡上的流量。多网卡主机用全接口抓包比指定单一接口更省心。-nn不解析主机名和服务名避免 DNS 解析干扰输出。-s 0抓取完整数据包默认可能只抓包头部。host your-service.example.com and port 443只关注目标服务的 443 端口流量。-w handshake.pcap把原始数据包写入文件方便后续用 Wireshark 分析。抓包进程保持运行后另开一个终端发起请求curl -v https://your-service.example.com/-v参数会把 TLS 连接过程的关键信息打印出来。正常请求完成后回到抓包终端按CtrlC停止 tcpdump然后检查刚才生成的 pcap 文件ls -lh handshake.pcap这个文件里就包含了这一次完整请求的 TCP 连接建立、TLS 密钥协商、HTTP 数据传输和最后连接关闭过程。2.3 Wireshark 里如何过滤握手报文用 Wireshark 打开handshake.pcap后流量可能包含很多无关数据。用过滤条件把关注内容缩小。tcp.port 443只显示 443 端口的 TCP 报文。需要注意这不等于只显示 HTTPS 流量TCP 层面所有 443 端口报文都会出现。观察 TCP 三次握手时可以过滤 SYN 报文tcp.flags.syn 1 and tcp.flags.ack 0上面的表达式过滤纯 SYN 包也就是三次握手的第一步。要过滤 SYN-ACK 报文则使用tcp.flags.syn 1 and tcp.flags.ack 1观察 TLS 握手时报文类型有多种Wireshark 的 TLS 协议解析器会识别握手层。例如只看 ClientHellotls.handshake.type 1如果 tcpdump 抓到的是原始加密报文Wireshark 在没有解密密钥时仍然能解析 TLS 握手过程的明文部分因为证书、ClientHello、ServerHello 这些控制参数本身就不加密。真正加密的是握手完成后的应用数据。注意如果想要解密后续的应用数据需要在 Wireshark 的 TLS 设置里配置(Pre)-Master-Secret日志文件。Chrome 和 Firefox 可以通过设置环境变量SSLKEYLOGFILE导出密钥日志但生产环境一般不建议开启这会降低安全性仅适合本地开发调试。3. TCP 三次握手抓包分析SYN、SYN-ACK、ACK 和状态机3.1 三次握手的报文序列和关键字段TCP 是面向连接的协议客户端和服务器各自维护一个连接状态机。建立连接的三次报文分别是客户端向服务端发送SYN随机生成一个初始序列号client_isn并进入SYN-SENT状态。服务端收到后如果端口正在监听且可以建立新连接则回复SYN-ACK携带服务端自己的初始序列号server_isn并确认收到客户端的client_isn之后进入SYN-RECV状态。客户端收到SYN-ACK后回复一个ACK确认服务端序列号随后客户端进入ESTABLISHED状态。服务端收到这个ACK后也进入ESTABLISHED状态。在 Wireshark 里看到的前三个报文大概类似1 0.000000 192.168.1.10 203.0.113.5 TCP 66 52341 - 443 [SYN] Seq0 Win64240 2 0.045123 203.0.113.5 192.168.1.10 TCP 66 443 - 52341 [SYN, ACK] Seq0 Ack1 Win65535 3 0.045208 192.168.1.10 203.0.113.5 TCP 54 52341 - 443 [ACK] Seq1 Ack1 Win64240注意Wireshark 默认显示的是相对序列号所以从0开始。实际报文头里的 Seq 是客户端的初始序列号。相对序列号用起来更直观分析时不用关注绝对值。为什么需要三次而不是两次核心原因是双方需要确认彼此的接收和发送能力都正常。第一次 SYN 只能证明客户端能发、服务端能收第二次 SYN-ACK 能证明服务端能发、客户端能收但客户端此时还不能确认自己发送能力正常与否只有第三次 ACK 让服务端确认客户端能收同时客户端也知道自己的 SYN 和 ACK 都成功到达。如果只有两次握手服务端无法确认客户端是否收到了自己的 SYN-ACK后续出现半开连接时很难快速发现。3.2 三次握手失败SYN_SENT、SYN_RECV 状态怎么查当连接建立不起来时首先观察客户端连接状态和可能出现的端口。在 Linux 上可以用ss查看ss -nt state syn-sent ss -nt state syn-recv ss -nt state established | head -20如果客户端长期处于SYN_SENT说明 SYN 发出后没有收到服务端响应。可能原因有网络路径不通、服务端防火墙丢弃入站 SYN、目标端口没有服务监听、回包路由错误。如果服务端出现大量SYN_RECV说明服务端已经回复SYN-ACK但迟迟没有收到客户端的最终 ACK。这种状态堆积常见原因是服务端半连接队列满了或者客户端收到 SYN-ACK 后无法回复例如客户端防火墙丢弃回包。排查时先用ping判断基本连通性再用telnet或nc测试端口是否监听nc -vz your-service.example.com 443如果端口通抓包看服务端有没有发送 SYN-ACK。如果服务端没有回包优先检查监听状态和防火墙规则。如果服务端回包了但客户端没收到则需要检查中间链路和客户端防火墙。3.3 半连接队列与 SYN Flood 防护服务端在收到 SYN 后、完成第三次握手前会为该连接分配一个半连接队列。Linux 内核里这个队列是否可用的一个重要参数是tcp_max_syn_backlog和应用程序 listen backlog 的较小值。常见项目中如果服务端瞬时收到大量 SYN半连接队列被打满正常的客户 SYN 也会被丢弃表现为客户端连接超时。可以查看当前内核参数sysctl net.ipv4.tcp_max_syn_backlog sysctl net.ipv4.tcp_syncookies对于 SYN Flood 类问题启用tcp_syncookies是常见缓解手段sudo sysctl -w net.ipv4.tcp_syncookies1在配置半连接队列时应用层 listen 的 backlog 不是越大越好。队列过大会让服务端在资源不足时积压大量半连接反而增加内存占用和超时等待。生产环境需要结合服务类型、连接速率、服务端资源综合调整。如果服务经常处于高并发短连接场景优先考虑加入负载均衡层或开启 SYN Cookie而不是无限调大队列。4. TLS 握手抓包分析证书、密钥协商和 SNI4.1 TLS 1.2 和 TLS 1.3 的握手差异TCP 三次握手完成后客户端接着发起 TLS 握手用于协商加密算法和验证服务端身份。以常见的 TLS 1.2 握手为例流程大致如下客户端发送ClientHello携带支持的 TLS 版本、加密套件列表、客户端随机数以及可选的 SNI 扩展。服务端返回ServerHello选定协议版本、加密套件和服务端随机数。服务端下发Certificate证书链客户端验证证书。服务端发送ServerKeyExchange和ServerHelloDone。客户端发送ClientKeyExchange、ChangeCipherSpec、Finished。服务端确认并发送ChangeCipherSpec、Finished。在 Wireshark 中如果服务端使用 TLS 1.3握手交互会明显更短。TLS 1.3 通过减少 RTT把部分协商参数合并进了早期报文通常只需要 1 个 RTT 就能完成握手配合 0-RTT 可以更早发送数据。但 0-RTT 有重放风险生产环境需要确认业务是否允许幂等请求。可以用openssl s_client直接观察某一台 HTTPS 服务的 TLS 握手参数openssl s_client -connect your-service.example.com:443 -servername your-service.example.com -showcerts如果服务端要求 TLS 1.2 或以上版本可以用约束协议版本的方式测试openssl s_client -connect your-service.example.com:443 -tls1_3分析这类输出时重点关注 Certificate 段是否包含完整证书链以及协议版本和加密套件是否满足客户端要求。4.2 ClientHello 里的 SNI 为什么如此关键SNIServer Name Indication是 TLS 的扩展字段让客户端在握手阶段就告诉服务端自己访问的是哪个域名。服务端在还没有看到 HTTP 请求 Host 头之前就能根据 SNI 选择对应的证书和虚拟主机配置。抓包时如果你在 TLS 握手报文里找不到 SNI常见原因有三个客户端用的是 IP 直接访问客户端代码没有正确配置主机名中间层做了 TLS 终结并重新封装。SNI 缺失最常见的后果是服务端返回了默认证书导致浏览器提示证书域名不匹配。排查这类问题时可以用openssl s_client分别测试带与不带-servername的输出差异openssl s_client -connect your-service.example.com:443 openssl s_client -connect your-service.example.com:443 -servername your-service.example.com如果带 SNI 时证书正常不带 SNI 时证书不对基本可以判断是多域名证书或虚拟主机配置问题。4.3 证书校验失败过期、域名不匹配、证书链不完整HTTPS 握手最常见的报错来自证书链验证。抓包或客户端日志看到的现象大致有错误现象常见原因检查方式处理建议certificate has expired服务端证书已过期查看证书有效期重新申请并部署证书SSL: certificate subject name mismatch证书域名与访问域名不一致openssl x509 -noout -subject检查证书 SAN 是否包含目标域名unable to get local issuer certificate缺少中间证书或 CA 链openssl s_client -showcerts在服务端完整配置证书链self-signed certificate客户端不信任自签名证书检查客户端信任库内部环境可导入根证书生产用正规 CA如果只有证书过期直接换新证书即可。如果是证书链不完整需要把服务端证书和中间证书合并。Nginx 场景里常见写法是把服务端证书与中间证书写在一个文件server { listen 443 ssl; server_name your-service.example.com; ssl_certificate /etc/nginx/certs/your-service.example.com.pem; ssl_certificate_key /etc/nginx/certs/your-service.example.com.key; }部署完成后验证证书链openssl s_client -connect your-service.example.com:443 -servername your-service.example.com -showcerts /dev/null注意观察输出中 Certificate chain 的每一节。如果第二节开始缺少中间证书浏览器在 Windows、Android 等客户端上可能会报错但部分桌面浏览器因为有缓存或本地中间证书而表现正常这会让问题很难被发现。4.4 实际项目里最容易踩的 TLS 坑第一客户端和服务端的 TLS 版本不匹配。服务端只开 TLS 1.3而客户端仍然是旧系统内置的 TLS 1.0握手阶段就会报协议版本错误。排查这类问题时不能在客户端侧光看 HTTP 状态码要看握手失败的具体原因。第二证书私钥与证书不匹配。部署新手经常把 A 证书配到 B 私钥上服务端启动时不一定报错但客户端握手会失败。可以用下面的命令验证证书内部公钥和私钥是否配对openssl x509 -noout -pubkey -in cert.pem | openssl sha256 openssl pkey -pubout -in key.pem | openssl sha256如果两个哈希值不一致说明证书和私钥不匹配。第三客户端时钟不一致。这个坑容易被忽略。TLS 证书校验依赖系统时间如果客户端服务器时间差了半年刚刚申请的证书也会被判定为过期或未生效。出现不可解释的证书错误时先检查客户端和服务端系统时间。5. 连接结束后还有一次挥手FIN、TIME_WAIT 和连接复用5.1 TCP 四次挥手过程与状态变化一次连接不可能无限期使用。当 HTTP 请求处理完成双方不再需要这个连接时连接进入关闭阶段。TCP 关闭通常用四次挥手完成。主动关闭方发送FIN进入FIN_WAIT_1状态。被动关闭方回复ACK进入CLOSE_WAIT状态主动关闭方进入FIN_WAIT_2。被动关闭方处理完数据后发送自己的FIN。主动关闭方回复最终ACK进入TIME_WAIT状态。如果抓包时看到 QUIC 之类协议关闭流程会有差异但经典 TCP 服务仍以这个模型为主。正常关闭之后主动关闭方不会立刻回收连接资源而是停留在TIME_WAIT状态默认等待时间为 2 个 MSL。这主要是为了保证最后一个 ACK 能到达对方同时让网络上可能残存的旧报文自然消失。MSL 通常取决于操作系统实现Linux 相关参数可以通过sysctl net.ipv4.tcp_fin_timeout间接观察。5.2 TIME_WAIT 大量出现不一定都要优化高并发的短连接服务中主动关闭方通常会出现大量TIME_WAIT连接。这不是系统故障而是正常状态的表现。不过数量过多会占用本地端口和连接表资源可能影响新连接的建立。查询当前连接状态ss -ant | awk {print $1} | sort | uniq -c如果TIME_WAIT数量很大优先思考业务层能否复用连接。HTTP/1.1 默认支持keep-alive客户端和服务端应该尽量保持连接而不是每个请求都新建 TCP 连接。如果请求量极大但连接复用比例很低需要检查客户端连接池配置、HTTP 版本是否升级到 HTTP/2以及服务端是否主动关闭了空闲连接。Linux 上可以调整端口范围和 TIME_WAIT 复用参数但调整之前要理解影响。例如设置tcp_tw_reuse时通常还需要配合时间戳选项。生产环境不要一看到 TIME_WAIT 多就立刻改内核参数先确认连接是否本来就应该被复用。5.3 CLOSE_WAIT 堆积才是代码问题如果服务器端出现大量CLOSE_WAIT问题往往出在应用代码没有正确关闭连接。被动关闭方收到对方 FIN 后进入CLOSE_WAIT如果应用代码没有调用 close这个连接会一直停留在该状态。排查方式ss -ant state close-wait看到大量 CLOSE_WAIT 时重点检查HTTP 服务框架是否配置了请求超时和空闲连接释放。是否有连接池没有归还连接。业务代码是否在某些异常分支漏掉了关闭操作。读取请求体或响应体时是否没有完整消费数据。CLOSE_WAIT堆积通常意味着文件描述符被占用最终可能导致too many open files。这比 TIME_WAIT 更需要立即处理。6. 握手故障排查清单与生产环境建议6.1 从客户端到服务端的完整检查链路拿到一个握手失败或超时的问题不建议直接翻配置先按链路顺序缩小范围。顺序检查内容常用命令或工具判断目标1客户端到服务端是否可达ping、traceroute基本网络连通性2端口是否监听ss -lntp、netstat服务进程是否正常监听目标端口3TCP 握手是否完成ss -nt、tcpdump客户端是否进入 ESTABLISHED4TLS 证书是否正确openssl s_client证书链、有效期、域名5TLS 版本和加密套件是否匹配openssl s_client -tls1_3客户端与服务端是否协商一致6应用层是否返回正常响应curl -vHTTP 状态码和响应头7连接关闭是否异常ss -ant 状态统计是否存在大量 CLOSE_WAIT 或异常状态这个顺序能解决大多数连接层问题。前面任何一层失败都不需要急着分析后面的业务代码。6.2 抓包问题定位过滤规则速查想要观察的内容tcpdump/Wireshark 过滤规则某个主机的全部流量host your-service.example.com443 端口流量tcp.port 443只看到 TCP 三次握手 SYNtcp.flags.syn 1 and tcp.flags.ack 0看到 SYN-ACKtcp.flags.syn 1 and tcp.flags.ack 1只看到 TLS ClientHellotls.handshake.type 1只看到 TLS 证书报文tls.handshake.type 11只看到 FIN 报文tcp.flags.fin 1查看连接状态分布ss -ant6.3 生产环境握手参数与安全建议如果服务需要对外开放 HTTPSTLS 配置不是把证书放上去就能结束。以下几点建议直接可落地协议版本至少启用 TLS 1.2有条件时优先 TLS 1.3。TLS 1.0 和 1.1 已经不再适合生产环境多数现代客户端不再支持或强制禁用。证书自动续期要纳入部署流程。很多事故的根因不是证书申请不下来而是证书到期后无人处理。可以用 certbot 等工具做定时续期并在续期后自动 reload 网关或 Web 服务。证书私钥要限制文件权限。常见做法是把私钥文件属主设为运行用户权限设为 600 或 400。HTTP 层要开启 HSTS让浏览器强制使用 HTTPS 访问减少明文请求穿过网络的可能性。应用代码和服务端框架要配置合理的连接空闲超时、读写超时和连接池最大数量避免连接长期占用或泄漏。对于内部服务之间的调用如果链路不经过公网也不能想当然地认为不需要加密。内网里的中间设备、日志系统、抓包工具都可能看到明文流量。只要服务间传的是敏感数据就建议启用 mTLS 或至少使用 HTTPS。不过 mTLS 会引入证书分发和轮换成本规模较小时可以用服务网格或专用网关统一管理而不是在每个业务代码里自行维护证书逻辑。6.4 性能优化和扩展方向理解握手之后再谈性能优化就不会只停留在“加缓存”这一步。针对网络握手的主要优化方向有三类。第一类是减少握手次数。HTTP/1.1 开启 keep-alive 能避免每个请求都重新经历 TCP 三次握手。HTTP/2 的多路复用把多个请求放到同一条连接里减少并发连接数。TLS 会话恢复机制允许服务端缓存会话 Ticket不必每次重新做完整 TLS 握手。第二类是减少握手路径上的额外跳转。客户端如果每次都先访问 HTTP 再被 301 跳转到 HTTPS就会额外增加一次请求往返。对外服务尽可能直接监听 HTTPS或让负载均衡器统一处理 80 到 443 的跳转避免业务层多次重定向。第三类是减少握手过程中的等待。处理高并发短连接的 WebSocket 服务时可以评估 WebSocket 握手之后如何维持连接。处理 TCP 层时可在了解网络环境的前提下谨慎评估 TCP 快速打开等机制但公网场景要综合考虑兼容性和丢包重传行为。对新手来说最值得做的练习是在本机起一个 Nginx配置自签名证书然后分别用 curl、openssl、tcpdump 完成一次从 TCP SYN 到 TLS 握手再到 HTTP 请求的完整观察。跑通这个最小实验后生产环境里的超时和证书错误基本都能在大脑里映出对应的报文片段排查速度会明显提高。
返回列表