簡介
經典的TCP三次握手大家應該很熟悉了,三次握手按道理說應該是最優的方案了,當然這是對於通用的情況來說的。那么在某些特殊的情況下是不是可以提升TCP建立連接的速度呢?
答案是肯定的,這就是今天我們要講的TCP fast open和netty。
TCP fast open
什么是TCP fast open呢?
TCP fast open也可以簡寫為TFO,它是TCP協議的一種擴展。為什么是fast open呢?這是因為TFO可以在初始化建立連接的時候就帶上部分數據,這樣在TCP連接建立之后,可以減少和服務器交互的次數,從而在特定的情況下減少響應的時間。
既然TFO這么好,為什么我們很少見到使用TFO協議的呢?
這是因為TFO是有缺陷的,因為TFO會在sync包中帶上一些數據信息,那么當sync包重發的時候,就會造成接收方接受到重復的數據。
所以,如果是用TFO,那么接收方則需要具有能夠處理重復數據的能力。
在程序界,防止數據重復提交有一個好聽的名字叫做冪等性,只有具有冪等性的服務器才能夠使用TFO。
開啟TFO
既然TFO這么優秀,怎么才能開啟TFO呢?
TFO的開啟首先需要操作系統的支持,如果你是mac系統,恭喜你,mac默認情況下已經支持TFO了,你不需要進行任何操作。
如果你是Linux系統,那么需要查看/proc/sys/net/ipv4/tcp_fastopen這個文件。
tcp_fastopen可以有四種值,如下所示:
0 -- 表示TFO未開啟
1 -- 表示TFO開啟了,但是只對客戶端有效
2 -- 表示TFO開啟了,但是只對服務器端有效
3 -- 表示TFO開啟了,同時對客戶端和服務器端有效
通過上面的設置,我們就在操作系統層開啟了TFO的支持。
接下來,我們看一下如何在netty中使用TFO。
netty對TFO的支持
首先我們看下如何在netty的服務器端開啟TFO支持。
在這之前,我們先回顧一下如何建議一個普通的netty服務器:
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TFOServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口並開始接收連接
ChannelFuture f = b.bind(port).sync();
上面的代碼中,我們看到ServerBootstrap可以設置option參數,ChannelOption中包含了所有可以設置的channel的參數,對應的TFO的參數是ChannelOption.TCP_FASTOPEN, 所以我們只需要添加到ServerBootstrap中即可:
sb.option(ChannelOption.TCP_FASTOPEN, 50)
ChannelOption.TCP_FASTOPEN的值表示的是socket連接中可以處於等待狀態的fast-open請求的個數。
對於客戶端來說,同樣需要進行一些改動,先來看看傳統的client端是怎么工作的:
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new TFOClientHandler());
}
});
// 連接服務器
ChannelFuture f = b.connect(HOST, PORT).sync();
client要支持TFO,需要添加這樣的操作:
b.option(ChannelOption.TCP_FASTOPEN_CONNECT, true)
還記得TFO是做什么的嗎?TFO就是在sync包中發送了一些數據。所以我們需要在client端對發送的數據進行處理,也就是說在client和server端建立連接之前就需要向channel中發送消息。
要獲得非建立連接的channel,則可以調用Bootstrap的register方法來獲取channel:
Channel channel = b.register().sync().channel();
然后向該channel中寫入byteBuf:
ByteBuf fastOpenData = directBuffer();
fastOpenData.writeBytes("TFO message".getBytes(StandardCharsets.UTF_8));
channel.write(fastOpenData);
最后再和服務器建立連接:
// 連接服務器
SocketAddress serverAddress = SocketUtils.socketAddress("127.0.0.1", 8000);
ChannelFuture f = channel.connect(serverAddress).sync();
總結
這樣一個一個支持TFO的客戶端和服務器就完成了。盡情使用吧。
本文的例子可以參考:learn-netty4
本文已收錄於 http://www.flydean.com/44-netty-tcp-fast-open/
最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!