写点什么

netty 系列之: 好马配好鞍, 为 channel 选择配套的 selector

作者:程序那些事
  • 2021 年 12 月 29 日
  • 本文字数:3337 字

    阅读完需:约 11 分钟

netty系列之:好马配好鞍,为channel选择配套的selector

简介

我们知道 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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 刚刚
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:好马配好鞍,为channel选择配套的selector