netty系列之:讓TCP連線快一點,再快一點

flydean發表於2022-02-15

簡介

經典的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/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章