TCP is said to be connection-oriented because before one application process can
begin to send data to another, the two processes must first “handshake” with each
other…this connection-establishment procedure is often referred to as a three-way handshake.——《Computer networking : a top-down approach》
在任何一本计算机网络的教科书上,我们都会学到:应用程序在使用 TCP 通信之前,先要建立连接,而这个创建连接的过程被称为三次握手。(假如你已经忘了的话,我在文末写了段WireShark抓包实战:TCP连接的建立)接着,教材便会开始介绍三次握手的流程以及TCP报文段结构(TCP Segment Structure)。和喜欢追根溯源的历史故事不同,书本很少会涉及到一个问题:为什么在TCP协议中建立连接的握手需要三次?二次行不行?四次可以吗?“三”这个数字是怎么来的?与道家所讲的“一生二,二生三,三生万物”有关联吗?
引子:红蓝军问题(Two Generals’ Problem)
让我们回溯一下历史,回到冷兵器时代,那时候情报只能靠斥候骑马送达,而斥候在路上很可能被敌军截下。在这种情况下,两支军队之间如何进行通讯就引申出了一个很著名的思想实验——红蓝军问题(Two Generals’ Problem)。

Two armies, each led by a general, are preparing to attack a fortified city. A valley separates the two hills, and the only way for the two generals to communicate is by sending messengers through the valley. Unfortunately, the valley is occupied by the city’s defenders and there’s a chance that any given messenger sent through the valley will be captured.——Two Generals’ Problem from wikipedia
被蓝军隔断的两支红军要想攻下蓝军,就必须商定好统一的时间吹响集结号合力进攻。于是,左边红军的将军A派斥候告诉右边红军的将军B,“明早九点吹响集结号!”。幸运的是,斥候成功将情报送达,这时将军B虽然知道了时间,但担心将军A还不知道他(将军B)已经收到了情报,从而不敢贸然进攻,于是又派斥候把将军B已经记下时间点的这个情况告诉将军A。幸运第二次降临,斥候也成功地返回左边的红军报到。此时,将军A明早就敢吹响集结号吗?他不敢,因为他担心将军B不知道这次斥候成功送达了情报,在这种情况下,将军B在明早也不敢贸然进攻。于是将军A又派斥候去右边,告诉将军B他(将军A)已经知道了将军B知道明早九点吹响集结号的情报。幸运第三次降临,斥候成功地到右边红军报到。此时,将军B明早就敢发起进攻吗?他依然不敢,因为他担心将军A不知道这次斥候成功送达了情报,在这种情况下,将军A在明早也不敢吹响集结号……
聪明的读者已经猜到,这是一场永远达不成协议的进攻。无论情报送达了多少个来回,总是没有办法保证每个将军想对“最后一次情报送达”的“确认”生效。
Thus it quickly becomes evident that no matter how many rounds of confirmation are made, there is no way to guarantee the second requirement that each general be sure the other has agreed to the attack plan. Whichever general sends the final messenger will always be left wondering whether the messenger got through.——Two Generals’ Problem from wikipedia
这个思维实验说明了一个重要的通信道理:在不可信的信道上,不可能实现完全可信的通信。而实际信道绝对是有扰的,换句话说,世界上不存在完全可靠的通信协议。
历史的行程:无线电台
人生天地之间,若白驹之过隙,忽然而已。我们来到已经诞生了无线电台的时代。在这个时空,通讯不再依靠快马扬鞭的斥候,而是靠无线电台。那么斥候会不会被半路劫走就无须担心,取而代之的是,我们要确认通讯设备是可以正常工作的,换言之,发信机和收信机是好的。那么如果两个人要进行成功的通讯,他们相互之间需要联系几次,才能知道自己设备是好的呢?
第一次:将军A给将军B发送了尝试建立通讯的电报。此时,将军B收到了,那么将军B能确认自己的收信机,以及将军A的发信机是好的,而将军A什么都确认不了。
第二次:将军B给将军A发送了回应的电报。此时,将军A收到了,那么将军A能确认自己的发信机、收信机都是好的,以及将军B的发信机、收信机也是好的,而将军B依然不能确认自己的发信机,以及将军A的收信机是好的。所以将军A还不能讲正事。
第三次:将军A对将军B的回应进行了回应,再发送了一次电报。此时将军A知道双方的通讯设备是正常工作的,这一次回应只是为了消除将军B对自己的发信机,以及将军A的收信机的担心。现在,将军A不用等将军B的再次回应,就可以开始讲正事了。
以上共有几次握手呢?数一数,一共是三次握手。那握手三次后,后面的通讯就一定能一直正常工作吗?当然不能,握手成功后设备可以突然故障,信号可以突然丢失,还有许多可能可以导致通信失败。三次握手的意义是在于它排除了两个无线电台本身的收发信故障。如果再多握手几次,变成四次握手,五次握手等,也许会更加靠谱一些,但正如红蓝军问题所揭示的道理一样:世界上不存在完全可靠的通信协议。
有的读者想问了,电台通信一定要三次握手吗?当然不一定。在间谍电影里,我们经常可以看到间谍为了不暴露自己,可以只用收信机而不发信,传送情报时再采取其他手段,例如在冷咖啡的杯垫下留下密文。
众里寻他千百度
在这里,我们可以得出结论:握手次数在建立连接的可靠性和性能上进行取舍后,得出三次。
握手次数本来就是比较灵活的问题,但是作为协议必须确定。并不会适合所有的情况。现行的tcp协议每次握手的时候具体的通信协议细节,是为三次握手特别设定的。假如当时设定的是四次握手,它的第三次握手的内容就不会和现在三次握手的时候第三次的内容一样了。——Fluxay
所以在逻辑上是这样的,互联网诞生之初,在连接的可靠性和性能上平衡后,选择的方案是三次握手。在这之后,TCP协议的设计者按照三次握手的方案具体设计了每次握手的通信协议细节。
这个时候,让我们回到教材,看看课本对为什么选择三次握手方案的介绍:
It may seem that a two-message exchange would suffice to establish a connection.However, the three-way handshake is both necessary and sufficient for correct synchronization between the two ends of the connection given internet delivery semantics.To understand the reason that connection establishment is difficult, remember that TCP uses an unreliable packet delivery service. Therefore, messages can be lost, delayed,duplicated, or delivered out of order. To accommodate loss, TCP must retransmit requests. However, trouble can arise if excessive delay causes retransmission, which means both the original and retransmitted copy arrive while the connection is being established. Retransmitted requests can also be delayed until after a connection has been established, used, and terminated! The three-way handshake and rules that prevent restarting a connection after it has terminated are carefully designed to compensate for all possible situations.
——《Internetworking with TCP/IP》
相信聪明的读者读到这里,已是胸有成竹。
WireShark抓包实战:TCP连接的建立
知乎上流行一句话叫“先问是不是,再问为什么”。那么现在就让我用WireShark抓包看看,TCP是不是这样子建立连接的。先看一下课本是怎么介绍的:

To establish a connection, TCP uses a three-way handshake. That is, three messages are exchanged that allow each side to agree to form a connection and know that the other side has agreed. The first segment of a handshake can be identified because it has the SYN‡ bit set in the code field. The second message has both the SYN and ACK bits set to indicate that it acknowledges the first SYN segment and continues the handshake. The final handshake message is only an acknowledgement and is merely used to inform the destination that both sides agree that a connection has been established.
——《Internetworking with TCP/IP》
再看一眼TCP报文段结构(TCP Segment Structure):
——《TCP/IP Illustrated, Volume 1》
抓包实战开始:
①首先打开WireShark,选定要监听的网卡。
②在浏览器输入想访问的网址,比如我输入的是chenjuntong.me
③在过滤器输入HTTP,找到GET Method,右键菜单中选择追踪流-TCP流
④此时,通过截获的数据包,我们可以很清楚地看到,客户端和服务器先是在TCP协议下进行了三次握手,然后才进行了HTTP长连接。
⑤庖丁解牛,让我们来看看每一次握手的具体内容。
⑥第一次握手:
客户端发送建立连接请求,标志位SYN=1,序列号(Sequence number)seq=0 。
⑦第二次握手:
服务器发回确认包,标志位SYN=1,ACK=1,同时发送服务器的序列号seq=0,将自己的确认序号(Acknowledgement Number)ack设置为客户端的序列号seq加1,即0+1=1,ack=1。
⑧第三次握手:
客户端再次发送确认包,标志位ACK=1,SYN=0,并且将服务器发来的序列号seq+1,放在自己确定字段ack中发送给对方,同时把自己的序列号seq加1再发送出去,即seq=1。
至此TCP连接已经建立,客户端进入ESTABLISHED(已建立连接)状态,当服务端收到确认后,也进入 ESTABLISHED状态,它们之间便可以正式传输数据了。
参考资料:http://dwz.cn/3p5PaP
每篇文章除特殊声明外均以 CC BY-NC-SA 4.0 协议发布,非商业用途可以在保留署名和来源的情况下任意转载。