netty系列之:好馬配好鞍,為channel選擇配套的selector

flydean發表於2022-01-19

簡介

我們知道netty的基礎是channel和在channel之上的selector,當然作為一個nio框架,channel和selector不僅僅是netty的基礎,也是所有nio實現的基礎。

同樣的,我們知道netty很多種不同的協議,這些協議都是在channel上進行通訊的,那麼對於不同的協議來說,使用的channel和selector會有所不同嗎?

帶著這個疑問,我們一起來深入探究一下吧。

netty服務的基本構建方式

netty可以分為客戶端和伺服器端,實際上客戶端和伺服器端的構造方式差別不大,這裡為了簡單起見,以netty中伺服器端的構建為例子進行研究。

回顧一下我們最開始搭建的netty伺服器,其對應的程式碼如下:

 //建立兩個EventloopGroup用來處理連線和訊息
        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 FirstServerHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 繫結埠並開始接收連線
            ChannelFuture f = b.bind(port).sync();

我們要注意的是兩個地方,一個是ServerBootstrap的group方法,一個是它的channel方法。

EventLoopGroup

group有兩種實現方式,可以帶一個引數,也可以帶兩個引數。引數都是EventLoopGroup,EventLoopGroup主要用來註冊channel, 供後續的Selector進行選擇。

如果使用一個引數的形式,則一個EventLoopGroup同時處理acceptor和client的事件,如果使用兩個引數,則會將兩者分開。

當然,這都不是今天要講的重點,今天要講的是EventLoopGroup的構建在不同的協議中有什麼不同。

EventLoopGroup本身是一個介面,他有很多種實現,但是本質上還是兩種EventLoop:SingleThreadEventLoop和MultithreadEventLoopGroup.

也就是用單執行緒進行EventLoop處理和多執行緒進行EventLoop處理。

比如上面我們常用的NioEventLoopGroup,就是一個單執行緒的EventLoop。

NioEventLoopGroup通常我們使用的是無參的建構函式,實際上NioEventLoopGroup可以傳入ThreadFactory,thread的個數,SelectorProvider和SelectStrategyFactory.

netty只提供了一個SelectStrategyFactory的實現:DefaultSelectStrategyFactory。

而對應SelectorProvider來說,預設的實現是SelectorProvider.provider(), 我們看下這個方法的具體實現:

    public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

可以看到預設情況下,SelectorProvider有三種建立方式。

第一種就是從系統屬性中查詢:java.nio.channels.spi.SelectorProvider:

String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
Class<?> c = Class.forName(cn, true,
                                       ClassLoader.getSystemClassLoader());
            provider = (SelectorProvider)c.newInstance();

如果有定義,則建立一個例項返回。

如果沒有的話,則會從"META-INF/services/"中載入service Loader :

    private static boolean loadProviderAsService() {

        ServiceLoader<SelectorProvider> sl =
            ServiceLoader.load(SelectorProvider.class,
                               ClassLoader.getSystemClassLoader());
        Iterator<SelectorProvider> i = sl.iterator();

如果servie也沒有找到的話,則會使用最後預設的sun.nio.ch.DefaultSelectorProvider.

channel

預設情況下,我們使用的是NioServerSocketChannel。他實際是從上面提到的預設的SelectorProvider來建立的。

private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
return DEFAULT_SELECTOR_PROVIDER.openServerSocketChannel();

所以使用的channel需要跟selector相匹配。

我們可以直接使用channel,也可以使用ChannelFactory,通過這些Factory來生成channel。

如果要使用ChannelFactory,則可以呼叫ServerBootstrap的channelFactory方法。

多種構建方式

上面提到了最基本的netty server構建方式。對應的是socket協議。

如果是要進行UDP連線,對應的channel應該換成NioDatagramChannel,如下:

EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioDatagramChannel.class)
             .option(ChannelOption.SO_BROADCAST, true)
             .handler(new UDPServerHandler());

            b.bind(PORT).sync().channel().closeFuture().await();

EventLoopGroup可以保持不變。

因為netty底層是基於Socket進行通訊的,socket底層又是基於TCP或者UDP協議,所以在netty中實現的http或者http2或者SOCKS協議都是在socket連線基礎上進行的。

所以對http或者http2來說,channel還是NioServerSocketChannel。

可以看到只有UDP協議有所不同。同樣的基於UDP協議之上的UDT協議也是不同的,其使用如下:

 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());
                        }
                    });

UDT使用的是NioUdtProvider中提供的BYTE_PROVIDER和BYTE_ACCEPTOR分別作為selector和channelFactory。

其他的channel

除了NioSocketChannel之外,還有EpollChannel、KQueueChannel、SctpChannel,這些channel都是針對不同協議來使用的。我們會在後續的文章中詳細進行介紹。

總結

channel和selector是netty的基礎,在這基礎之上,netty可以擴充套件適配所有基於tcp和udp的協議,可以說非常的強大。

本文已收錄於 http://www.flydean.com/39-netty-selecto…r-channelfactory/

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

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

相關文章