资讯专栏INFORMATION COLUMN

【自己读源码】Netty4.X系列(三) Channel Register

darkbug / 2527人阅读

摘要:我想这很好的解释了中,仅仅一个都这么复杂,在单线程或者说串行的程序中,编程往往是很简单的,说白了就是调用,调用,调用然后返回。

Netty源码分析(三) 前提概要

这次停更很久了,原因是中途迷茫了一段时间,不过最近调整过来了。不过有点要说下,前几天和业内某个大佬聊天,收获很多,所以这篇博文和之前也会不太一样,我们会先从如果是我自己去实现这个功能需要怎么做开始,然后去看netty源码,与自己的实现做对比。

Server端NIO复习

Netty有基于很多IO的实现(BIO/OIO/NIO...),而我们最常用的也就是NIO了,我们这次分析源码,也是基于NIO的实现,前提条件要先弄清楚NIO的流程,再分析Netty是怎么基于他开发出这个高性能网络框架的,这里我们先来简单的复习下,已经熟悉的同学可以跳过不看了。

四个步骤

抛开数据的读写,我们把NIO服务端监听分成四个步骤

channel初始化

注册 selector到 channel上

channel绑定端口

循环select 等待事件

其中第二步又分为几个小步骤

创建selector

调用channel的register

然后第四步也分为几个步骤

selector.select(或者几个带参的重写方法)

处理所有的selectedKey

然后再继续分解:[处理所有的selectedKey]

获取selectedKey的channel

根据selectedKey的状态处理channel

其中需要注意的是:如果key的状态(或者说是readyops)是accept的话,需要把channel转成ServerSocktChannel然后通过accept获取新的channel,再把这个selector注册到这个channel中去。如果是其他的读写状态的话,获取的channel要转为SocketChannel,然后进行操作。

Register的实现

通过NIO的复习,我们可以看到一个完整的NIO的创建流程。在之前的两篇博文中,我们实现了第一步初始化以及第三步绑定端口的流程,现在我们先来实现channel的register。

这里多提一句,网上很多人都是在bind端口之后,再channel register的,但是其实这个前后顺序并没有关系,Netty中则是在bind之前进行的。

先看下之前的代码,我们在doBind方法中,先实例化了一个channel,然后调用channel的bind方法。

 private  void doBind(Integer port){
        Channel channel = channelFactory.newChannel();
        channel.bind(new InetSocketAddress(port));
}

这样看,我们现在需要做的就是在这两行代码中插入一段channel register的代码就可以了,那是不是可以这样,Channel中新增一个register接口,然后在实现类中去实现他,最后在这里中间写一句channel.register,貌似看起来没问题啊,不管怎么样,先动手实现下。

/* in Channel Interface */
void register(Selector selector);

/* in AbstractNioChannel */
@Override
public void register(Selector selector)  throws ClosedChannelException {
    ch().register(selector,0,null);
}
/*in AbstractBootstrap*/
try {
    channel.register(Selector.open());
} catch (IOException e) {
    e.printStackTrace();
}

我们最后的doBind看起来就会是这样

 private  void doBind(Integer port){
        Channel channel = channelFactory.newChannel();
        try {
            channel.register(Selector.open());
        } catch (IOException e) {
            e.printStackTrace();
        }
        channel.bind(new InetSocketAddress(port));
    }

功能貌似是实现了。。。但是,看起来是不是有些问题?

没错,首先看起来很丑,try/catch一大块的很难看,但其次抛去美观问题不谈,这么写不符合设计思想,我这个类是一个抽象的启动类,但是这个register只是NIO的写法,那BIO,AIO...其他IO实现怎么办?

那我们先来做下最简单的改造,软件不就是在一次次的重构中,慢慢成长起来的嘛。我简单的分了下步骤。

Channel接口更改,取消入参和异常抛出

    void register();

抽象实现类中增加新的带参方法,供接口方法调用

@Override
public void register(){
    try {
            register(Selector.open());
    } catch (IOException e) {
            e.printStackTrace();
    }
}

private void register(Selector selector) throws ClosedChannelException {
    ch().register(selector,0,null);
}

doBind内register调用

private  void doBind(Integer port){
    Channel channel = channelFactory.newChannel();
    channel.register();
    channel.bind(new InetSocketAddress(port));
}

这样看,代码是不是美观很多?最关键的是,此时的我们的抽象启动类里,真正做到了抽象,不管什么IO实现,都会去调用自身Channel实现的register方法。

目前来看还挺不错的,接下来看下Netty是怎么实现的吧,自己写的思路对的,但是肯定有很多地方没考虑到的。

Netty中的Register How to do

Netty中,doBind方法里是包装了一个initAndRegister方法去完成初始化和注册地功能,这里我们直接看下initAndRegister这个方法体,看下Netty怎么做的

是不是和我们写的差不多呢,先实例化一个channel,然后再进行register的,不过有两点不同的地方,其是register之前先要调用一个init方法,玩过Netty的应该都清楚,主要是处理我们自定义的一些配置的,这里我们先不提,等后面再说。先来说下第二个不同,也就是register方法。

我们的代码里,是直接通过扩展channel的接口,直接调用channel的register的方法的,但是Netty这里则是通过group(包含了Netty很重要的一个角色EventLoop,我们后面会详细说他),传入channel对象,然后再去调用channel的register方法,不信,我们看下他的调用链。

撇去Netty复杂的继承关系,我们最终定位到方法的最终调用的地方,SingleThreadEventLoop里

我们可以看到,register方法里,先是包装了一个promise对象(实现promise接口的对象,这里维护了channel对象和这个eventloop对象,题外话:promise接口其实是future接口的一个超集),然后调用了promise里的channel的unsafe对象的register(ps:好绕口的感觉),这个unsafe就是channel的一个内部类,感觉越来越接近了,我们就到unsafe里面看下register方法吧。

这下应该很清晰了吧,unsafe的register里又调用了doRegister,然后就和我们的方法差不多了,这里的javaChannel其实就是JAVA NIO的channel对象,唯一不同的就是这里的selector不是open出来的,而是早在eventloop初始化的时候就存下来了,可能有人会问:这里eventloop是怎么来的呢?答案就在上面,在调用unsafe的register的时候,我们传入了两个对象,第一个this就是指向的eventloop(别忘了我们是在eventloop里调用的register)。然后就是把this(AbstractNioChannel)作为register的第三个参数(后面可以通过selectedKey.attachment获取到)

How to say

Netty中怎么做的我们也看过了,相信你和我一样,有很多疑问(大神们请自动无视)。这里我举出我觉得比较重要的两个。

为什么一个注册这么复杂?明明用原生NIO只需要一行就搞定的,又是promise又是unsafe,在channel.register之间抽象出来两层。

为什么最后的doRegister里,要用一个无限循环包起来呢?

答案还是要从源码里获得,先看下eventloop的register的接口注释

简单来说,就是把eventloop对象注册到channel里,并且要能知道注册的结果,并且返回。这句话,可能有些同学不太明白,这是Java中的异步调用,我们传统开发的Java web程序,我们所处理的基本都是单线程的模式,首先这符合人的大脑的习惯嘛(人的大脑本质是串行处理事情的),其次是多线程的部分,框架和容器已经帮我做好了,所以可能会不太理解异步这个概念。

然后再看下unsafe这个register接口的注释

看到了吧,执行promise的channel的注册并在完成的时候通知返回,这是靠一个叫观察者模式来完成的,具体的内容在下一章会详细讲解。

我想这很好的解释了Netty中,仅仅一个register都这么复杂,在单线程或者说串行的程序中,编程往往是很简单的,说白了就是调用,调用,调用然后返回。但Netty是个充满并行和异步的程序,所以光从设计上就会比较复杂,这让我想起了沈神说的一句话:简单的容易理解的模型性能都差,性能好的都很复杂。虽然他指的是数据库的设计,但是我觉得道理是相通的。

还有一点就是这些设计也是为了同时兼容服务端和客户端,软件开发的思想里,很重要的一点就是复用。这也是为什么会有第二个疑问,我们还是要看下注释。


jdk的register的注释说明,如果这个channel已经被注册过了,并且再次注册的过程中连接断了,则会抛出这个CanceledKeyException异常,那么这里捕获了异常并且调用select.selectNow,理论上会从selector中移除这个无效的channel,但是事实上并没有,所以下面也写到了,可能是个jdk bug,所以要显示的往外继续抛出异常。

仔细想想,如果仅仅是服务端的channel register,怎么可能发生上述的情况呢,所以这里的doRegister同时也是客户端channel复用的注册方法。

总结&&预告

这一章差不多就结束了,我们做了哪些事情呢?

复习Java NIO 流程

动手实现channel register

读Netty源码,理解Netty是如何实现register以及为什么这样实现的

软件开发的思想

然后画个简单的图来总结下把。

接下来在实现循环select监听和处理事件之前,我们先实现一下Netty中一个很重要的EventLoop,并且了解下运用到的异步框架(Future and Promise),希望我的拖延症得到解决,应该在下周会写出来。。。

希望同学们看到后会有收获,如果我有理解不对的地方,欢迎来指正。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68364.html

相关文章

  • Netty4.x 源码实战系列(二):服务端bind流程详解

    摘要:对于,目前大家只知道是个线程组,其内部到底如何实现的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,这里不作详细介绍,后面会有文章作专门详解。 在上一篇《ServerBootstrap 与 Bootstrap 初探》中,我们已经初步的了解了ServerBootstrap是netty进行服务端开发的引导类。 且在上一篇的服务端示例中,我们也看到了,在使用netty进行网络编程时,我...

    laoLiueizo 评论0 收藏0
  • 自己源码Netty4.X系列(二) 启动类成员Channel

    摘要:下面无耻的贴点源码。启动类我们也学,把启动类抽象成两层,方便以后写客户端。别着急,我们慢慢来,下一篇我们会了解以及他的成员,然后,完善我们的程序,增加其接收数据的能力。文章的源码我会同步更新到我的上,欢迎大家,哈哈。 废话两句 这次更新拖了很长时间,第一是自己生病了,第二是因为最开始这篇想写的很大,然后构思了很久,发现不太合适把很多东西写在一起,所以做了点拆分,准备国庆前完成这篇博客。...

    waterc 评论0 收藏0
  • 自己源码Netty4.X系列(一) 启动类概览

    摘要:一些想法这个系列想开很久了,自己使用也有一段时间了,利用也编写了一个简单的框架,并运用到工作中了,感觉还不错,趁着这段时间工作不是很忙,来分析一波源码,提升下技术硬实力。 一些想法 这个系列想开很久了,自己使用netty也有一段时间了,利用netty也编写了一个简单的框架,并运用到工作中了,感觉还不错,趁着这段时间工作不是很忙,来分析一波源码,提升下技术硬实力。 结构 这里先看下net...

    qingshanli1988 评论0 收藏0
  • Netty4.x 源码实战系列):NioServerSocketChannel全剖析

    摘要:本篇将通过实例化过程,来深入剖析。及初始化完成后,它们会相互连接。我们在回到的构造方法父类构造方法调用完成后,还要初始化一下自己的配置对象是的内部类而又是继承自,通过代码分析,此对象就是就会对底层一些配置设置行为的封装。 根据上一篇《Netty4.x 源码实战系列(二):服务端bind流程详解》所述,在进行服务端开发时,必须通过ServerBootstrap引导类的channel方法来...

    Flink_China 评论0 收藏0
  • Netty4.x 源码实战系列(一):ServerBootstrap 与 Bootstrap 初探

    摘要:而用于主线程池的属性都定义在中本篇只是简单介绍了一下引导类的配置属性,下一篇我将详细介绍服务端引导类的过程分析。 从Java1.4开始, Java引入了non-blocking IO,简称NIO。NIO与传统socket最大的不同就是引入了Channel和多路复用selector的概念。传统的socket是基于stream的,它是单向的,有InputStream表示read和Outpu...

    BakerJ 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<