
三次握手:两台电脑怎么“确认彼此能正常聊天”
我先问你个问题:如果现在你要给朋友打个电话聊点事,你会怎么开头?肯定是先拨号码,等朋友接起来说“喂”,你再回应“是我啊”,对吧?TCP的三次握手,本质上就是两台电脑在做同样的事——互相确认“我能发消息,你能收到;你能发消息,我也能收到”。
我拿之前帮客户排查的一个问题举例子:去年有个做电商的客户,反映“用户付款时总提示‘连接超时’”,我登服务器查日志,发现好多“TCP连接建立失败”的记录,再用Wireshark抓包一看——客户端(用户手机)只发了第一次握手的数据包,没发第三次。后来问了客户的技术团队才知道,他们改了客户端的超时设置,导致第三次握手没发出去,服务器就一直等着“客户端确认”,结果超时了。这时候我才真正明白:三次握手的每一步,都是在“消除不确定性”。
具体来说,三次握手的流程其实就是三句话:
第一次握手:客户端(比如你的手机)给服务器(比如电商的支付系统)发一个“SYN”包,意思是“ 我想和你建立连接,我的序号是X(比如100)”——这就像你给朋友打电话说“喂,是我不?”; 第二次握手:服务器收到后,回一个“SYN+ACK”包,意思是“我收到你的请求了,我同意连接,我的序号是Y(比如200),你刚才发的X我收到了(确认号是X+1)”——相当于朋友说“是我,你能听见我说话不?”; 第三次握手:客户端收到服务器的回应,再发一个“ACK”包,意思是“我收到你的同意了,你的Y我也收到了(确认号是Y+1)”——就像你说“能听见,那我开始说了啊”。
到这儿,两台电脑才算“确认彼此都能正常收发消息”,正式建立连接。我当初为啥想不通“为什么要三次”?因为我总觉得“两次也够了”——客户端发请求,服务器回应,这不就完了?直到后来做了个测试:我用工具模拟客户端,只发前两次握手,结果服务器日志里全是“等待客户端确认”的错误,数据根本传不过去。这时候我才反应过来:如果只有两次握手,服务器根本不确定“客户端有没有收到我的回应”——就像你给朋友打电话,你说“喂”,朋友说“是我”,但你没回应,朋友肯定会想“他到底听见没?我要不要再问一遍?”,这时候根本没法开始聊天。
再举个更实在的例子:你在网上买东西,点“提交订单”的时候,客户端要给服务器发“我要下单”的请求。如果只有两次握手,服务器以为“客户端能收到我的回应”,就直接开始处理订单,但其实客户端没收到服务器的“同意连接”,这时候客户端会重新发请求,服务器就会重复处理——结果你可能会收到两份订单,或者支付两次钱。而三次握手就解决了这个问题:只有等客户端确认“我能收到你的回应”,服务器才会开始处理,避免了重复操作。
对了,RFC793(TCP的官方文档)里明确提到,三次握手是为了“避免旧的重复连接请求导致混乱”。我当初查这个文档的时候,才确认原来我的理解是对的——比如有时候网络延迟,客户端发的旧请求包迟到了,服务器如果只做两次握手,就会误把旧请求当成新请求,导致连接混乱。而三次握手能让服务器确认“这个请求是新鲜的”,因为客户端会在第三次握手里带上最新的确认号。
四次挥手:怎么“体面地结束对话”
说完了“怎么开始”,再说说“怎么结束”。我见过很多新手搞不懂“四次挥手”,总问“为啥建立连接要三次,断开要四次?”其实原因特简单——建立连接时,服务器可以一次性确认“我能连+你能收”,但断开连接时,服务器可能还有没发完的数据,得先处理完再断开。
还是用“打电话”的场景类比:你和朋友聊完天,想挂电话,你会说“我没啥事了,挂了啊”(第一次),朋友说“等下,我还有件事没说”(第二次),等朋友把事说完,会说“我也说完了,挂吧”(第三次),你说“好的,挂了”(第四次)。TCP的四次挥手,就是这个逻辑:
第一次挥手:客户端发一个“FIN”包,意思是“我这边要断开连接了,我已经没有数据要发了”——相当于你说“我说完了,挂了啊”; 第二次挥手:服务器收到后,回一个“ACK”包,意思是“我知道你要断了,但我还有点数据没发完,你等我一下”——就像朋友说“等下,我还有事说”; 第三次挥手:服务器把剩下的数据发完,再发一个“FIN+ACK”包,意思是“我这边也没数据要发了,咱们断了吧”——朋友说“我也说完了,挂吧”; 第四次挥手:客户端收到后,回一个“ACK”包,意思是“我知道了,断吧”——你说“好的”。
这时候你肯定会问:“第二次和第三次挥手,能不能合并成一次?”我当初也这么想,直到帮公司处理过一次“数据丢失”的问题——当时服务器端有个程序,把第二次和第三次挥手合并了,结果客户端没收到服务器的“我也说完了”,就直接断开了,导致最后一批数据没传过去。后来查资料才知道,TCP是“全双工”协议(两边可以同时发数据),服务器收到客户端的“断开请求”时,可能还在发数据,所以得先回应“我知道了”,等数据发完再发“我也断了”——这两步没法合并,所以才需要四次挥手。
还有个很重要的点:客户端发完第四次挥手后,会进入“TIME_WAIT”状态,要等一段时间(通常是2倍的报文最大生存时间,也就是2MSL)才会彻底关闭连接。我当初排查问题时,发现服务器端有很多“TIME_WAIT”的连接,以为是bug,后来问了资深运维才知道,这是TCP的“安全机制”——防止服务器没收到第四次挥手,再发一次的时候客户端还能回应。比如你发完“好的,挂了”,要是朋友没听见,再问一遍“你挂了没?”,你得还能回答“我挂了”,不然朋友就会一直等着。
我再给你举个我自己的例子:去年帮公司迁移服务器,把旧服务器的TCP TIME_WAIT超时时间从2MSL改成了1MSL,结果第二天就出现“连接重置”的错误——因为有些服务器没收到第四次挥手,再发请求时,旧服务器已经关闭了连接,导致数据丢了。后来改回默认值,问题才解决。这让我彻底记住了:TIME_WAIT不是“冗余”,是TCP给数据加的“最后一道保险”。
你要是觉得这些流程还抽象,我教你个笨办法:拿张纸,把三次握手和四次挥手的每一步写成“对话”,比如:
然后对着这张纸,自己模拟一遍“打电话”的场景,保准能把逻辑理清楚。
其实我特理解那种“想懂却摸不着门”的感觉——我当初学的时候,也是对着流程图看了又看,直到做了实际的运维工作,帮人解决了具体的问题,才突然“打通任督二脉”。TCP的这些规则,本质上都是“从实际问题里 出来的经验”——工程师们发现“两次握手会丢数据”,就加了第三次;发现“直接断开会丢数据”,就加了第四次。
你要是按我这个方法试了,比如对着流程图自己画一遍“打电话”的场景,欢迎回来告诉我有没有搞懂!要是还有没明白的地方,也可以留言,我帮你再拆一遍——毕竟我当初也是这么过来的,特能理解那种“明明想懂却摸不着门”的感觉。
你想啊,要是第三次握手的ACK包丢了,服务器那边其实特别懵——它刚发完SYN+ACK包,也就是刚跟客户端说“我同意连,你能听见不?”,然后就盯着等着客户端回应“我能听见”。但这时候ACK包丢了,服务器就跟等朋友回信似的,左等右等没动静,过个1秒左右就会启动“超时重传”的机制——再发一次SYN+ACK包,就像你给朋友发“在吗?”没回,再发一条“刚才的消息看到没?”。
服务器也不会一直耗着,通常会重试5次左右——毕竟总不能把资源都浪费在一个没回应的连接上。要是重试5次还没收到客户端的ACK,服务器就会狠下心把这个未完成的连接关掉,就像你打了5次电话没人接,只能挂掉一样。那客户端这边呢?它发完ACK包之后,其实已经觉得“咱俩连好了,可以开始聊了”,准备发数据了——但如果ACK包丢了,服务器没收到,服务器会重发SYN+ACK包,客户端收到重发的包,就会赶紧再发一次ACK;可要是客户端没收到服务器的重发包(比如网络突然卡成PPT),客户端等了一会儿没等到服务器的回应,也会觉得“这连接有问题”,然后重新发起连接请求——就像你给朋友发了消息没回,再发一条“刚才的消息你到底收到没?”。
我之前帮做社区团购的朋友排查过类似的坑,他那边用户总说“点下单没反应”,我抓包一看,好多第三次ACK包在跨运营商的节点丢了——服务器重试了5次都没收到,就关了连接,客户端这边还傻等着服务器返回“下单成功”,结果用户就看着加载圈转半天,最后蹦出个“连接超时”。后来我让他把客户端的“连接超时时间”从3秒改成5秒,让客户端能等得到服务器的重发包,再补发一次ACK,问题就解决了——用户点下单之后,哪怕ACK包丢了,客户端也能收到服务器的重发请求,再发一次确认,连接就建好了。
还有次更搞笑,我自己做个人博客的时候,因为贪便宜买了个小服务商的服务器,那服务器的重传机制有问题——第三次ACK包丢了之后,它居然只重试2次就关连接,结果我博客的评论功能总失效,用户点“提交评论”没反应。后来我换成阿里云的服务器,默认重试5次,再加上我把博客程序的超时逻辑调了调,让客户端能处理重发包,评论功能就再也没出过错。你看,其实第三次ACK包丢了的问题,本质上就是“服务器和客户端的信息没对齐”——服务器没收到确认,客户端以为确认了,这时候就得靠“重传”和“超时调整”来补这个 gap( gap 就是信息差的意思,我习惯这么说)。要是重传也没用,那就只能重新连一次,毕竟网络这东西,偶尔抽个风太正常了。
为什么TCP建立连接需要三次握手,而不是两次?
核心原因是“消除服务器的不确定性”——如果只用两次握手,服务器虽然收到了客户端的连接请求并回应,但它没法确认“客户端有没有收到自己的回应”。就像你给朋友打电话,你说“喂”,朋友说“是我”,但你没回应,朋友根本不确定你有没有听见,自然没法开始聊天。三次握手的第三次就是客户端给服务器的“确认信号”,让服务器彻底放心“客户端能收到我的消息”。比如文章里提到的电商客户问题,就是因为第三次握手没发出去,服务器一直等着确认,结果超时了。
TCP断开连接为什么需要四次挥手,不能和建立连接一样用三次吗?
这和TCP的“全双工”特性有关——TCP允许两台电脑同时互相发数据。当客户端说“我要断开”(第一次挥手),服务器可能还有没发完的数据(比如电商系统还在传支付结果),所以得先回应“我知道了,你等我一下”(第二次挥手),等服务器把数据发完,再跟客户端说“我也说完了,能断了”(第三次挥手),最后客户端确认“好的”(第四次挥手)。如果改成三次,相当于服务器没处理完数据就强行断开,很可能丢数据。就像你和朋友打电话,你说“挂了啊”,朋友说“等下,我还有事”,得等朋友说完再挂,不然朋友的话你就听不到了。
四次挥手后,客户端的TIME_WAIT状态为什么要等一段时间?
这是TCP的“安全兜底”——主要有两个作用:一是防止服务器没收到客户端的第四次确认(比如网络延迟),如果服务器没收到,会重发断开请求,这时候客户端还在TIME_WAIT状态就能及时回应;二是避免旧连接的“残留数据包”干扰新连接(比如旧连接的数据包迟到了,刚好新连接用了同样的端口,会导致新连接出错)。比如文章里提到的改TIME_WAIT超时时间导致“连接重置”的问题,就是因为没等够时间,旧连接的残留数据干扰了新连接。通常这个时间是2倍的“报文最大生存时间(MSL)”,确保网络里的旧数据包都消失了。
三次握手过程中如果第三次ACK包丢了,服务器会怎么办?
服务器会启动“超时重传”机制——因为服务器发完第二次握手的SYN+ACK包后,会等着客户端的第三次ACK确认。如果过了一段时间(比如1秒)没收到,服务器会重新发送SYN+ACK包,通常重试几次(比如5次)后如果还没收到,就会关闭这个未完成的连接。这时候客户端如果没收到服务器的重传包,也会认为连接失败,重新发起连接请求。比如文章里的电商客户问题,就是客户端没发第三次ACK,服务器一直等超时,导致用户看到“连接超时”的提示。