大家好,我是岛主小风哥,今天来聊聊为什么TCP需要3次握手而不是2次。
假设有一条河,河的上下游有两个人,这两个人只能借助这条河交流信息:

图片

岸边有很多叶子,因此它们把信息写到岸边的叶子上:

图片

并假设上游的叶子会飘向下游,下游的叶子会飘向上游。

图片

但由于水流的作用,同一个方向叶子先出发的有可能后到(乱序),也有可能沉到水里(丢失)。
也就是说假如a发了四片叶子,每片叶子上写两个字,记录的是“码农的荒岛求生”,a依次把【码农】【的荒】【岛求】【生】放到河里,这四片叶子到达b后可能变成了【岛求】【码农】 【生】 【的荒】(乱序),也有可能丢了一片叶子,变成了【的荒】 【码农】 【生】(乱序+数据丢失):
图片
那么在这种情况下该怎么让b知道a想说的其实是“码农的荒岛求生”呢?
很简单,只要两种机制:编号以及确认
编号针对与乱序问题,确认机制针对的是丢失问题。
a放到河里的任意一片叶子都带上一个编号,这些编号依次递增,b收到叶子后根据编号重组起来,这样即使叶子到达的顺序是乱的b也能根据编号恢复信息:

图片

乱序问题解决了,叶子丢失问题依靠确认机制:b收到每一片叶子后会回复一个收到,并附带接收叶子的编号+1,也就是期待a发出的下一片叶子的编号:

图片

这样a在接收到【3 收到】后就确信b已经收到了【2 的荒】,如果a在一定时间内没有收到【3 收到】那么就会重新拿起一片叶子再次发送【2 的荒】。

图片

可以看到利用这种重传机制确保即使叶子可能沉到水里也能把信息发给b。
有了编号和确认机制,即使在河流这种不可靠的介质中a也可以把信息可靠的传递给b。
当然b也可以利用这种机制把消息可靠的发送给a。
由于需要对每片叶子进行编号,因此a向b发送消息之前必须把叶子的初始编号告诉b,又因为b也可以向a发送消息,因此双方在通信之前必须知道对方叶子的初始编号,这样才能正确的进行叶子收到后的确认以及对叶子根据编号进行重排。

图片

于是ab双方协定,聊天发起方先把编号告诉聊天接收方,聊天接收方收到发起方编号后也把自己的编号告诉发起方。
依然假设a先发起通信,a先发送了一片叶子,写着“SYN X”,SYN表示这是一片告诉你我的初始编号的叶子(在TCP中SYN是synchronization的简写,表示同步,但作用和这里一样),X表示自己的初始编号。

图片

根据之前提到的确认机制,b在收到需要对这片叶子进行确认,于是b发送一片叶子:“ACK X+1”,表示“我确认已经接收到了你的叶子,期待接收X+1号叶子”(在TCP中ACK是acknowledge的简写,表示确认,作用和这里一样)。

图片

不要忘了,b也要把自己的编号告诉a,于是b紧接着又发送了一片叶子“SYN Y”,表示“这是一片告诉你我的初始编号的叶子,我的初始编号是Y”:

图片

同样的a收到后也要进行确认,于是a发送一片叶子:“ACK Y+1”,表示“我确认已经接收到了你的叶子,期待接收你Y+1号叶子”。

图片

就这样经过最少四片叶子,a和b就能知道对方的初始编号是多少,注意看这里:

图片

可以看到这两片叶子都是b发向a的,因此这两片叶子的信息可以合并在一起,这样就可以少发送一片叶子:

图片

可以看到,双方至少需要发送3片叶子才能知道对方的初始编号,而如果只发送两片叶子没办法保证这一点。
作者简介

  • 畅销书《计算机底层的秘密》作者。
  • 公众号“码农的荒岛求生”作者。