天天看點

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

目錄

  • 簡介
  • netty服務的基本建構方式
    • EventLoopGroup
    • channel
  • 多種建構方式
  • 其他的channel
  • 總結

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

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

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

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方法。

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.

預設情況下,我們使用的是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。

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

channel和selector是netty的基礎,在這基礎之上,netty可以擴充适配所有基于tcp和udp的協定,可以說非常的強大。

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

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

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