TCP 快速打开
TCP 快速打开,简称 TFO,是对 TCP 协议的扩展,允许在建立 TCP 连接时与初始 SYN 数据包一起发送少量数据。在某些情况下,这可以节省往返时间并减少响应时间。
但是,TFO 并不总是可用,因为它改变了 TCP 的行为方式:接收端可能会由于 SYN 数据包的重传而看到这些数据被重复。因此,TFO 只能在初始数据包中的数据将被幂等处理时使用。这是否属实取决于协议和应用程序。
幂等性得到保证的一个重要用例是 TLS 客户端 Hello 消息。这是客户端发送给服务器以启动 TLS 握手的第一条消息。这在建立 TLS 连接时节省了一次往返。自版本 4.1.61.Final
以来,Netty 中的 SslHandler
将在可用时自动利用 TFO。具体而言,仅在 epoll 传输上支持服务器端和客户端端 TFO,并且需要在 Linux 内核中启用支持。自版本 4.1.67.Final
以来,TCP FastOpen 也将在 kqueue
传输上的客户端连接中得到支持。
首先,需要在操作系统中支持并启用 TFO。在 MacOS 上,默认启用此功能。在 Linux 上,使用 /proc/sys/net/ipv4/tcp_fastopen
文件控制此功能。该文件可能包含以下值之一
- 未启用 TFO。
- 为传出连接(客户端)启用 TFO。
- 为传入连接(服务器)启用 TFO。
- 为客户端和服务器启用 TFO。
可通过以 root
用户身份将派生的配置值写入文件来更改设置。
第二步是在 Netty 中启用 TFO。服务器和客户端的启用方式不同。
对于服务器,将 ChannelOption.TCP_FASTOPEN
设置为 ServerBootstrap
上的一个选项
ServerBootstrap sb = ...;
sb.option(ChannelOption.TCP_FASTOPEN, maxPendingFastOpen);
仅此而已。该选项指定在任何时候可以在套接字上挂起的快速打开请求数。这限制了可用于尚未建立连接的快速打开有效负载的系统资源。请参阅 RFC 7413 的被动打开附录 以供参考。
对于客户端,情况稍显复杂。首先在 Bootstrap
上将 ChannelOption.TCP_FASTOPEN_CONNECT
选项设置为 true
Bootstrap cb = ...;
cb.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
然后,你还必须决定允许哪些数据与 SYN 数据包一起发送,同时考虑到它可能会因重传而多次到达。然后需要将此数据放在通道出站缓冲区中在通道连接之前。若要获取连接之前的通道,我们调用 register
。以下是如何实现此功能的示例
Bootstrap cb = new Bootstrap();
cb.option(ChannelOption.TCP_FASTOPEN_CONNECT, true);
// ...set handler, etc...
Channel channel = cb.register().sync().channel(); // Get unconnected channel.
ByteBuf fastOpenData = ...;
ByteBuf normalData = ...;
channel.write(fastOpenData); // Write TFO data.
channel.connect(remoteAddress).sync(); // Establish connection (flushes TFO data).
channel.write(normalData); // TCP connection works like normal now.
关于上述内容的一个重要方面:我们对由 register()
生成的通道调用 connect()
。我们无法使用 Bootstrap.connect()
方法,因为这将创建另一个具有自己出站缓冲区的新通道。