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


簡介

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM