簡介
經典的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/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!