Search K
Appearance
Appearance
前面几篇文章讲了三次握手的过程,可能你会有觉得好麻烦呀,要发数据先得有三次包交互建连。三次握手带来的延迟使得创建一个新 TCP 连接代价非常大,所有有了各种连接重用的技术。
但是连接并不是想重用就重用的,在不重用连接的情况下,如何减少新建连接代理的性能损失呢?
于是人们提出了 TCP 快速打开(TCP Fast Open,TFO),尽可能降低握手对网络延迟的影响。今天我们就讲讲这其中的原理。
最开始知道 TCP Fast Open 是在玩 shadowsocks 时在它的 wiki 上无意中逛到的。专门有一页介绍可以启用 TFO 来减低延迟。原文摘录如下:
If both of your server and client are deployed on Linux 3.7.1 or higher, you can turn on fast_open for lower latency.
First set fast_open to true in your config.json.
Then turn on fast open on your OS temporarily:
echo 3 > /proc/sys/net/ipv4/tcp_fastopenTFO 是在原来 TCP 协议上的扩展协议,它的主要原理就在发送第一个 SYN 包的时候就开始传数据了,不过它要求当前客户端之前已经完成过「正常」的三次握手。快速打开分两个阶段:请求 Fast Open Cookie 和 真正开始 TCP Fast Open
请求 Fast Open Cookie 的过程如下:

第一次过后,客户端就有了缓存在本地的 cookie 值,后面的握手和数据传输过程如下:

上面说的都是理论分析,下面我们用实际的抓包来看快速打开的过程。
因为在 Linux 上快速打开是默认关闭的,需要先开启 TFO,如前面 shadowsocks 的文档所示
echo 3 > /proc/sys/net/ipv4/tcp_fastopen接下来用 nginx 来充当服务器,在服务器 c2 上安装 nginx,修改 nginx 配置 listen 80 fastopen=256;,使之支持 TFO
server {
listen 80 fastopen=256;
server_name test.ya.me;
access_log /var/log/nginx/host.test.ya.me main;
location /{
default_type text/html;
return 200 '<html>Hello, Nginx</html>';
}
}下面来调整客户端的配置,用另外一台 Centos7 的机器充当客户端(记为 c1),在我的 Centos7.4 系统上 curl 的版本比较旧,是 7.29 版本
curl -V
curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.36 zlib/1.2.7 libidn/1.28 libssh2/1.4.3这个版本的 curl 还不支持 TFO 选项,需要先升级到最新版本。升级的过程也比较简单,就分三步
// 1. 增加 city-fan 源
rpm -Uvh http://www.city-fan.org/ftp/contrib/yum-repo/city-fan.org-release-2-1.rhel7.noarch.rpm
// 2. 修改 city-fan.org.repo,把 enable=0 改为 enable=1
vim /etc/yum.repos.d/city-fan.org.repo
// 2. 升级 curl
yum update curl
// 验证是不是最新版本
curl -V
curl 7.64.1 (x86_64-redhat-linux-gnu) libcurl/7.64.1 NSS/3.36 zlib/1.2.7 libpsl/0.7.0 (+libicu/50.1.2) libssh2/1.8.2 nghttp2/1.31.1下面就可以来演示快速打开的过程了。
第一次:请求 Fast Open Cookie
在客户端 c1 上用 curl 发起第一次请求,curl --tcp-fastopen http://test.ya.me,抓包如下图

逐个包分析一下
第 1 个 SYN 包:wireshark 有标记 TFO=R,看下这个包的 TCP 首部

这个首部包含了 TCP Fast Open 选项,但是 Cookie 为空,表示向服务器请求新的 Cookie。
第 2 个包是 SYN + ACK 包,wireshark 标记为 TFO=C,这个包的首部如下图所示

这时,服务器 c2 已经生产了一个值为 "16fba4d72be34e8c" 的 Cookie,放在首部的 TCP fast open 选项里
第 3 个包是客户端 c1 对服务器的 SYN 包的确认包。到此三次握手完成,这个过程跟无 TFO 三次握手唯一的不同点就在于 Cookie 的请求和返回
后面的几个包就是正常的数据传输和四次挥手断开连接了,跟正常无异,不再详细介绍。
第二次:真正的快速打开
在客户端 c1 上再次请求一次 curl --tcp-fastopen http://test.ya.me,抓包如下图

逐个包分析一下
第 1 个包就很亮瞎眼,wireshark 把这个包识别为了 HTTP 包,展开头部看一下

这个包本质是一个 SYN 包,只是数据跟随 SYN 包一起发送,在 TCP 首部里也包含了第一次请求的 Cookie
第 2 个包是服务端收到了 Cookie 进行合法性校验通过以后返回的 SYN + ACK 包
第 3、4 个包分别是客户端回复给服务器的 ACK 确认包和服务器返回的 HTTP 响应包。因为我是在局域网内演示,延迟太小,ACK 回的太快了,所以看到的是先收到 ACK 再发送响应数据包,在实际情况中这两个包的顺序可能是不确定的。
一个最显著的优点是可以利用握手去除一个往返 RTT,如下图所示

在开启 TCP Fast Open 以后,从第二次请求开始,就可以在一个 RTT 时间拿到响应的数据。
还有一些其它的优点,比如可以防止 SYN-Flood 攻击之类的
用 strace 命令来看一下 curl 的过程
加上 –tcp-fastopen 选项以后的 strace 输出 sudo strace curl --tcp-fastopen http://test.ya.me 可以看到客户端没有使用 connect 建连,而是直接调用了 sendto 函数,加上了 MSG_FASTOPEN flag 连接服务端同时发送数据。

没有加上 –tcp-fastopen 选项的情况下的 strace 输出如下 sudo strace curl http://test.ya.me

在没有启用 Fast Open 的情况下,会先调用 connect 进行握手
这篇文章主要用 curl 命令演示了 TCP 快速打开的详细过程和原理
可以看到历代大牛在降低网络延迟方面的鬼斧神工般的努力,现在主流操作系统和浏览器都支持这个选项了。