--本文采自本人公众号【猴哥别瞎说】
搞技术的都知道,TCP 的三次握手和四次挥手的过程。在这里,为了不那么无聊枯燥,笔者想用通俗的文字来描述一下 TCP 的三次握手,作为开头。
Story 模式
很久很久以前,有两个球球,一个叫 ‘小青蛙’,一个叫‘小乌龟’,分住在山的两段。这两个球球都有 一张嘴巴 和 两只耳朵,但都看不见。有一天,“小青蛙”去散步,偶然碰到了“小乌龟”,它们想要聊天,但是不确定对方是否听得到,也不确定对方是否会说话(因为只有双方都能够听得到并且会说话,才能够聊天啊)。在这里,能够说话表示的是发送能力,能够听得到表示的是接收能力。

终于,“小乌龟”开口了:“哈喽~ 你好”
"小青蛙"听到了这个问候,于是“小青蛙”知道“小乌龟”是会说话的。( round 1 :“小乌龟”具有发送信息的功能)

于是,“小青蛙”礼貌性的回复了一句:“哈喽,你好呀~”
“小乌龟”听到了“小青蛙”的回复。这时,“小乌龟”知道“小青蛙”不仅听得到,而且还会说话。(round 2 :“小青蛙”具有发送信息和接收信息的功能)

这个时候(看上图),“小青蛙”并不知道“小乌龟”是否听得到(接收能力)。所以“小乌龟”这时需要告诉“小青蛙”,它听得到“小青蛙”的回复,并且还可以告诉“小青蛙”更多的内容。(round 3 :“小乌龟”具有接收信息的功能)

于是,经过这三个回合之后,它们两个小家伙的快乐聊天就开始了。
这就是 TCP 握手的全部过程。
在这里,有一个很重要的概念:两颗球球不仅仅需要知道自己有某项能力(发送信息的能力、接收信息的能力),更重要的是,要让对方知道自己具有这项能力。套用当下流行的话语就是:我不仅仅要我觉得,我还要你觉得。
详解TCP的三次握手
在上面的这个 story 描述完之后,我们来看看正式的、枯燥的、标准的 TCP 三次握手的表示图:

TCP 通过三次握手建立可靠的(确保收到)的全双工通信。
客户端与服务器在交换应用数据之前,必须就起始分组序列号,以及其他一些连接相关的细节达成一致。出于安全考虑,序列号由两端随机生成。
- SYN
- 客户端选择一个随机序列号 x,并发送一个 SYN 分组,其中可能还包括其他 TCP 标志和选项。
- SYN ACK
- 服务器给 x 加 1,并选择自己的一个随机序列号 y,追加自己的标志和选项,然后返回响应。
- ACK
- 客户端给 x 和 y 分别加 1 并发送握手期间的最后一个 ACK分组。
非要三次握手么?
是的。因为我们想要创建的是可靠的(确保收到)的全双工通信,正如 Story 部分提到的,三次握手是让客户端和服务端都可以确定双方的接收和发送能力均正常。详细如下:
第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收能力,服务器自己的发送能力也正常。
至此,客户端和服务端可以确定双方的接收和发送能力均正常。
没有第三次握手会怎样咧?
这主要是为了防止已失效的连接请求报文段突然又传送到了服务器端,从而减少服务端的开销。
如果只有两次握手就建立连接会出现这种情况:客户端发出的连接请求报文段在某些网络节点长时间滞留了,以致延误到连接释放以后的某个时间才能到达服务端。本来这是一个早已失效的报文段,但服务端收到此失效的连接请求报文段后,就误认为客户端又发出了一次新的连接请求。于是向客户端发出确认报文段,同意建立连接。
由于现在客户端并没有发出建立连接的请求,因此不会处理服务端的确认,也不会向服务端发送数据。但服务端却以为新的连接已经建立了,并一直等待客户端发来数据。 服务端会因此浪费很多连接。
关于TCP握手阶段的优化
上面的讲解过程已经说了 TCP 三次握手的必要性。即便在无法改变握手次数的大前提下,我们的 Web 性能优化依然发现这里有很多的优化空间。
是否每一次连接都需要经过三次握手?
从原则上来讲,每一次连接都需要经过三次握手。但是在实际应用的时候,就会发现大部分的情况是:同一个客户端的多个请求,去连接同一个服务端的服务。这个时候,每一次连接都需要握手就显得没有必要了。完全可以握手的过程只操作一次,然后让其他连接直接与服务端的服务进行数据交互。
怎么做呢?通过 TFO 技术。
TFO
TFO(TCP Fast Open)是一种能够在 TCP 连接建立阶段传输数据的机制。使用这种机制可以将数据交互提前。
-
我们考虑在三次握手的首次握手过程中,客户端的 SYN 包增加 Fast Open Cookie 请求的 TCP 选项。这样,服务端就知道,这一类请求是 TFO 请求,在返回的第二次握手信息中,包含一些额外的信息。
-
服务端生成一个 cookie,这个 cookie 是通过使用密钥加密客户端的IP地址生成的,可以看做是服务端提供给到客户端的握手凭证。服务端给客户端发送SYN|ACK响应,在响应包的选项中包含了这个 cookie 。
-
客户端在第二次握手的信息中,拿到了这个 cookie 凭证之后,就保存了下来。
-
在接下来的连接请求中,只需要客户端的握手请求中携带了这个 cookie 凭证,服务端就会检查这个凭证是否有效(对应的 IP 地址是否之前产生过握手行为,且在有效期内)。
-
如果有效,那么两次握手之后,就可以进行数据通信了。而不需要三次。
根据测试数据,TFO 可以减少 15% 的 HTTP 传输延迟,全页面的下载时间平均节省 10%,最高可达 40%。
哇,TFO 这个技术这么好,那大家都用起来啊?
TFO的使用限制
TFO 技术是一个比较新的技术。它的使用,需要服务端与客户端的同时支持。
对于服务端而言,只在 Linux 3.7 以及更新的 Linux 内核版本才支持。在对应版本的 Linux 机器上,设置 /proc/sys/net/ipv4/tcp_fastopen 为3,即可开启 TFO。
对于浏览器端而言,需要浏览器的内核支持才行。TFO 的客户端支持情况如下:
windows默认的Edge浏览器14352以后的版本。
Chrome浏览器在Linux、 Android上的版本。在windows上版本不支持。
Firefox浏览器默认关闭,可以手动开启。
总结
TCP 的三次握手过程,是非常固定成熟的话题了。如何写它是一个挑战。本文通过 story + 画图的方式来展开,希望这样的方式会有趣一点。
前端性能优化系列: