netty系列之:請netty再愛UDT一次

flydean發表於2022-01-21

簡介

UDT是一個非常優秀的協議,可以提供在UDP協議基礎上進行高速資料傳輸。但是可惜的是在netty 4.1.7中,UDT傳輸協議已經被標記為Deprecated了!

意味著在後面的netty版本中,你可能再也看不到UDT協議了.

優秀的協議怎麼能夠被埋沒,讓我們揭開UDT的面紗,展示其優秀的特性,讓netty再愛UDT一次吧。

netty對UDT的支援

netty對UDT的支援體現在有一個專門的UDT包來處理UDT相關事情:package io.netty.channel.udt。

這個包裡面主要定義了UDT的各種channel、channel配置、UDT訊息和提供ChannelFactory和SelectorProvider的工具類NioUdtProvider.

搭建一個支援UDT的netty服務

按照netty的標準流程,現在是需要建立一個netty服務的時候了。

netty建立server服務無非就是建立EventLoop、建立ServerBootstrap、繫結EventLoop、指定channel型別就完了,非常的簡單。

唯一不同的就是具體的childHandler,可能根據具體協議的不同使用不同的處理方式。

當然,如果不是NioSocketChannel,那麼對應的ChannelFactory和SelectorProvider也會有所變化。

沒關係,我們先看下如何建立支援UDT的netty服務:

 final ThreadFactory acceptFactory = new DefaultThreadFactory("accept");
        final ThreadFactory connectFactory = new DefaultThreadFactory("connect");
        final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(1, acceptFactory, NioUdtProvider.BYTE_PROVIDER);
        final NioEventLoopGroup connectGroup = new NioEventLoopGroup(1, connectFactory, NioUdtProvider.BYTE_PROVIDER);

 final ServerBootstrap boot = new ServerBootstrap();
            boot.group(acceptGroup, connectGroup)
                    .channelFactory(NioUdtProvider.BYTE_ACCEPTOR)
                    .option(ChannelOption.SO_BACKLOG, 10)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<UdtChannel>() {
                        @Override
                        public void initChannel(final UdtChannel ch) {
                            ch.pipeline().addLast(
                                    new LoggingHandler(LogLevel.INFO),
                                    new UDTEchoServerHandler());
                        }
                    });
            // 開啟服務
            final ChannelFuture future = boot.bind(PORT).sync();

可以看到,UDT和普通netty socket服務不同的地方在於它的selector和channelFactory都是由NioUdtProvider來提供了。

NioUdtProvider是netty核心包中的內容,他提供了對UDT的有用封裝,我們不需要要懂太多UDT內部的實現,就可以使用UDT協議,是不是很美妙。

異常來襲

如果有小夥伴興沖沖的拿上面這段程式碼去嘗試執行,那麼很可惜你會得到異常,異常大概類似下面的情況:

包com.barchart.udt找不到!

什麼?直接使用netty包中的類居然會報錯?是可忍孰不可忍!

我們來仔細分析一下,這裡只有一個新的類就是NioUdtProvider,開啟NioUdtProvider的原始碼,在import一欄,我們赫然發現居然引用了不屬於netty的包,就是這些包報錯了:

import com.barchart.udt.SocketUDT;
import com.barchart.udt.TypeUDT;
import com.barchart.udt.nio.ChannelUDT;
import com.barchart.udt.nio.KindUDT;
import com.barchart.udt.nio.RendezvousChannelUDT;
import com.barchart.udt.nio.SelectorProviderUDT;

雖然很詭異,但是我們要想程式跑起來還是需要找出這些依賴包,經過本人的跋山涉水、翻山越嶺終於功夫不負苦心人,下面的依賴包找到了:

<dependency>
            <groupId>com.barchart.udt</groupId>
            <artifactId>barchart-udt-core</artifactId>
            <version>2.3.0</version>
        </dependency>

        <dependency>
            <groupId>com.barchart.udt</groupId>
            <artifactId>barchart-udt-bundle</artifactId>
            <version>2.3.0</version>
        </dependency>

netty核心包居然要依賴與第三方庫,這可能就是netty準備刪除對UDT支援的原因吧。

TypeUDT和KindUDT

如果你去檢視barchart中類的具體資訊,就會發現這個包的作者有個癖好,喜歡把類後面帶上一個UDT。

當你看到滿屏的類都是以UDT結尾的時候,沒錯,它就是netty UDT依賴的包barchart本包了。

大牛們開發的包我們不能說他不好,只能說看起來有點累....

barchart包中有兩個比較核心的用來區分UDT type和kind的兩個類,分別叫做TypeUDT和KindUDT.

Type和kind翻譯成中文好像沒太大區別。但是兩者在UDT中還是有很大不同的。

TypeUDT表示的是UDT socket的模式。它有兩個值,分別是stream和datagram:

    STREAM(1),
    DATAGRAM(2),

表示資料傳輸是以位元組流的形式還是以資料包文訊息的格式來進行傳輸。

KindUDT則用來區分是伺服器端還是客戶端,它有三種模式:

ACCEPTOR,
CONNECTOR,
RENDEZVOUS

server模式對應的是ACCEPTOR,用來監聽和接收連線.它的代表是ServerSocketChannelUDT,通過呼叫accept()方法返回一個CONNECTOR.

CONNECTOR模式可以同時在客戶端和伺服器端使用,它的代表類是SocketChannelUDT.

如果是在server端,則是通過呼叫server端的accept方法生成的。如果是在客戶端,則表示的是客戶端和伺服器端之間的連線。

還有一種模式是RENDEZVOUS模式。這種模式表示的是連線的每一側都有對稱對等的channel。也就是一個雙向的模式,它的代表類是RendezvousChannelUDT。

構建ChannelFactory

上面提到的兩種Type和三種Kind都是用來定義channel的,所以如果將其混合,會生成六種不同的channelFactory,分別是:

public static final ChannelFactory<UdtServerChannel> BYTE_ACCEPTOR = new NioUdtProvider<UdtServerChannel>(
            TypeUDT.STREAM, KindUDT.ACCEPTOR);

public static final ChannelFactory<UdtChannel> BYTE_CONNECTOR = new NioUdtProvider<UdtChannel>(
            TypeUDT.STREAM, KindUDT.CONNECTOR);

public static final ChannelFactory<UdtChannel> BYTE_RENDEZVOUS = new NioUdtProvider<UdtChannel>(
            TypeUDT.STREAM, KindUDT.RENDEZVOUS);

public static final ChannelFactory<UdtServerChannel> MESSAGE_ACCEPTOR = new NioUdtProvider<UdtServerChannel>(
            TypeUDT.DATAGRAM, KindUDT.ACCEPTOR);

public static final ChannelFactory<UdtChannel> MESSAGE_CONNECTOR = new NioUdtProvider<UdtChannel>(
            TypeUDT.DATAGRAM, KindUDT.CONNECTOR);

public static final ChannelFactory<UdtChannel> MESSAGE_RENDEZVOUS = new NioUdtProvider<UdtChannel>(
            TypeUDT.DATAGRAM, KindUDT.RENDEZVOUS);

這些channelFactory通過呼叫newChannel()方法來生成新的channel。

但是歸根節點,這些channel最後是呼叫SelectorProviderUDT的from方法來生成channel的。

SelectorProviderUDT

SelectorProviderUDT根據TypeUDT的不同有兩種,分別是:

public static final SelectorProviderUDT DATAGRAM = 
    new SelectorProviderUDT(TypeUDT.DATAGRAM);

    public static final SelectorProviderUDT STREAM = 
    new SelectorProviderUDT(TypeUDT.STREAM);

可以通過呼叫他的三個方法來生成對應的channel:

    public RendezvousChannelUDT openRendezvousChannel() throws IOException {
        final SocketUDT socketUDT = new SocketUDT(type);
        return new RendezvousChannelUDT(this, socketUDT);
    }

        public ServerSocketChannelUDT openServerSocketChannel() throws IOException {
        final SocketUDT serverSocketUDT = new SocketUDT(type);
        return new ServerSocketChannelUDT(this, serverSocketUDT);
    }

        public SocketChannelUDT openSocketChannel() throws IOException {
        final SocketUDT socketUDT = new SocketUDT(type);
        return new SocketChannelUDT(this, socketUDT);
    }

使用UDT

搭建好了netty伺服器,剩下就是編寫Handler對資料進行處理了。

這裡我們簡單的將客戶端寫入的資料再回寫。客戶端先建立一個message:

message = Unpooled.buffer(UDTEchoClient.SIZE);
 message.writeBytes("www.flydean.com".getBytes(StandardCharsets.UTF_8));

再寫入到server端:

    public void channelActive(final ChannelHandlerContext ctx) {
        log.info("channel active " + NioUdtProvider.socketUDT(ctx.channel()).toStringOptions());
        ctx.writeAndFlush(message);
    }

伺服器端通過channelRead方法來接收:

public void channelRead(final ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

總結

以上就是netty中使用UDT的原理和一個簡單的例子。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/40-netty-udt-support/

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

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

相關文章