您的位置:首页 > 其它

Netty源码研究笔记(3)——Channel系列

2022-05-21 11:56 696 查看

1. Netty源码研究笔记(3)——Channel系列

依旧是通过先纵向再横向的研究方法,在开篇中,我们发现不管是Sever还是Client,最终的启动是通过调用channel的对应方法来完成的,而这个动作实际在channel绑定的eventLoop中执行。

接下来,我们继续EchoSever、EchoClient的调用链来揭开channel、eventloop的面纱。

1.1. 调用链分析

1.1.1. initAndRegister调用链

AbstractBootstrap::initAndRegister
的调用event loop group的register方法处打个断点,并进入。

我们忽略在EventLoopGroup、EventLoop部分的方法调用,发现最终进入了

AbstractUnsafe::register
方法。

前面是些校验逻辑:判断是否已经注册过了、判断channel是否和eventloop兼不兼容。

主要的逻辑是说,在eventloop中去执行真正的注册操作:register0,注意,目前是在主线程中。

doRegister0
打个线程断点,进入到EventLoop线程中:

这个ChannelPromise指示register的成功与否。

首先确保这个ChannelPromise不能被取消,和Channel是open的(没有close)。

doRegister()方法是AbstractChannel的protect空方法,留给子类实现(AbstractNioChannel、AbstractEpollChannel、AbstractKqueueChannel、LocalChannel、LocalServerChannel、EmbeddedChannel)。

AbstractNioChannel::deRegister
的实现仅仅是将netty channel中的java channel注册到
NioEventLoop
中的java selector上,没有指定任何interest ops。

deRegister完成后,就处理pipeline中的pending task(回调ChannelInitializer,它负责添加需要的channel handler,在两个Bootstrap的init方法中被添加到channel pipeline中),然后就向pipeline发射channel register事件。

如果channel已经激活(对于NioSocketChannel来说,其持有的java channel已经open并且connected;对于NioServerSocketChannel来说,其持有的java channel已经open,并且已经bound),这时:

  • 如果channel是第一次注册,那么向channel pipeline发送channelActive事件

  • 如果channel之前已经注册过、取消注册过并且,channel被设置成autoRead,那么进行beginRead,这个方法由AbstractChannel的子类实现。对于NioChannel来说,它把自己注册到EventLoop上时得到的java selection key给关注上读事件。

1.1.2. Server的doBind0调用链

AbstractBootstrap::doBind0
处打个断点,注意,此时的线程是EventLoop对应的线程,非主线程。

结果进入到

AbstractChannel::bind
方法中,

忽略channel pipeline中的执行流程,我们发现最终进入到

AbstractChannel
的内部类
AbstractUnsafe::bind
中:

bind的时候对SO_BROADCAST这个ChannelOption(表示广播)进行检查:如设置了这个option为true,并且不是windows平台(当前是unix、linix等平台),并且绑定的本地地址不为0.0.0.0(对于服务器来说这个地址表示自己所有的ip地址,当服务绑定这个地址后,那么使用服务器的所有ip地址都可以访问到这个服务),并且当前程序不是以super user的身份运行,那么就warning:非root用户如果没有绑定到0.0.0.0的话不能收到broadcast packet,但程序还是按用户请求绑定到指定的非0.0.0.0的localaddress。

获取channel当前的是否active(对于NioServerSocketChannel来说如果它的ServerSocketChannel没有close并且绑定到local address,那么就算active)。

然后调用channel的doBind方法,它一个抽象方法,对于NioServerSocketChannel来说具体的操作是将自己的java channel绑定到local address。

然后再检查channel的active状态。

如果此前没有active而doBind后active了那么就让channel pipeline来fireChannelActive(这个动作被invokeLater了),对于NioServerSocketChannel来说,如果是第一次bind那么会fireChannelActive。

最后将用户持有的指示bind过程的ChannelPromise设置为success。

1.1.3. Client的doConnect调用链

Bootstrap::doConnect
处打上断点,然后进入,发现进入了
AbstractChannel::connect
方法中。

AbstractChannel::connect
方法委托给其持有的channel pipeline执行,我们忽略channel pipeline中的执行逻辑,发现,最终调用到
AbstractNioChannel
的内部类中的方法
AbstractUioUnsafe::connect

AbstractUioUnsafe::connect
看起来很长,但实际做的事情不复杂:

首先设置指示该操作的promise为不能取消,并且检查channel是否open,这两个操作都必须成功。

然后进行实际的doConnect,这是一个抽象方法,对于NioSocketChannel来说,它调用自己持有的java channel的connect方法,如果没有马上就连上,那么就设置selection key关注OP_CONNECT事件。

如果doConnect马上就连上了,那么就进行收尾工作:fulfillConnectPromise,告知其他线程持有的指示connect操作的promise句柄:操作已成功,并且,如果之前channel没有激活,而当前channel激活的话,还要向channel pipeline发送channelActive事件。

如果doConnect没有马上成功,即当前还在连接中,那么起一个超时检测的延时任务,当这个任务给触发的时候来检查connect promise来检查操作是否完成,如果没有完成,那么就给promise设置成操作失败。这个过程同时也是双向的:又给connect promise添加了一个监听器,使得,如果在连接中,超时前,如果其他线程将该connect promise取消的话,即取消连接操作,那么就取消这个超时检查的延时任务。

备注:doConnect没有马上成功的话,最终,如果eventloop中轮询到selector的OP_CONNECT事件后,会调用

AbstractNioUnsafe::finishConnect
方法来完成连接操作。

1.2. Channel系列

Channel是Netty中的核心类。它位于EventLoop、ChannelPipeline之间。

Channel的实现种类有很多,而常用的一般为两种:

  • 端到端的TCP:NioServerSocketChannel、NioSocketChannel
  • 无连接的UDP:NioiDatagramChannel。

1.2.1. 继承关系

Channel 接口继承自 ChannelOutboundInvoker,AttributeMap,Comparable。接口内部包含 一个UnSafe 接口。

  • ChannelOutboundInvoker:唤起Channel向外的操作。(注:从eventloop到ChannelPipeline是Inbound,从ChannelPipeline到eventloop是outbound)这些操作都是异步操作,返回的是ChannelFuture,这些操作包括:

    bind,connect,close,disconnect,deregister(register是eventloop group提供的),read(将数据从channel中读取到inbound buffer),write,flush,writeAndFlush

  • 此外还有newPromise(ChannelPromise),newProgressivePromise(ChannelProgressivePromise),voidPromise(ChannelPromise)

  • 以及newSucceededFuture,newFailedFuture(ChannelFuture)

  • Channel接口的功能:

      获取关联信息:id(ChannelId),eventloop,parent channel,channel config(ChannelConfig),meta data (ChannelMetadata),local address,remote address(SocketAddress),close future,ByteBufAllocator,channel pipeline,Unsafe

    • 获取状态信息:isOpen,isRegistered,isActive,isWritable,bytesBeforewritable,bytesBeforeUnWritable

    • 动作:read,flush方法,二者重定义自ChannelOutboundInvoker,将返回值设置为自身,以支持链式调用操作

  • ServerChannel:是一个标记性质的空接口。

  • ServerSocketChannel:重定义了remote address,local address 的返回类型(InetSocketAddress),channel config的类型(ServerSocketChannelConfig)

  • DuplexChannel:表示双向信道,output 、input两个方向可以独立关闭、并且提供了对这两个方法的传输是否关闭进行判断的方法。

  • SocketChannel:重写了Channel返回的parent channel、channel config、local address、remote address,这四者的返回类型(是原来返回类型的子类型)。

  • DatagramChannel:重写了Channel返回的channel config、local addresss、remote address的返回类型;新增了isConnected、joinGroup、leaveGroup、block这几种方法。

  • 1.2.2. AbstractChannel

    AbstractChannel实现了Channel接口中的绝大多数功能:

    • 对于AttributeMap接口中的功能,它是通过继承自DefaultAttributeMap来实现的

    • 对于ChannelOutboundInvoker接口中的功能,是通过委托给自己持有的channel pipeline来实现的(channel pipeline也实现了该接口)

    • 对于Channel接口中新增的功能:

      一部分是委托给自己持有的channel outbound buffer来实现的:isWritable、bytesBeforeWritable、bytesBeforeUnwritable

    • 剩下的一些状态获取、组件获取的操作,直接返回自己持有的状态字段、组件字段,比如:

      isRegistered()对应registered字段

    • closeFuture()对应closeFuture字段

    • eventLoop()对应eventLoop字段

    • localAddress() remoteAddress() 对应 localAddress、remoteAddress字段,等等

  • 注意,Channel接口中的一些方法AbstractChannel没有实现,而是留给子类取实现比如:config()、metaData()、isOpen()、isActive()。

  • 然后还定义了一些protected的抽象方法,留给子类实现,这些方法由Unsafe使用。

      doRegister、doBind、doDisconnect、doClose、doShutdownOutput、doDeregister、doBeginRead、doWrite

    注意,AbstractChannel是其他所有channel的公共祖先,这些其他的channel可以是nio channel、oio channel、local (server) channel、embedd channel、failed channel、epoll channel、kqueue channel。

    1.2.3. AbstractNioChannel

    AbstractNioChannel是所有nio channel的祖先,它在AbstractChannel的基础上加上了这个channel是NIO的语义。 因此,它持有了:java的selectable channel、selection key、interest op。

    AbstractNioChannel下面又分为byte channel、message channel两种(一个基于字节、一个基于消息),因此AbstractNioChannel类中还实现了,具有NIO语义的并且跟byte、message没有区别的一些操作:

    • doRegister:不管是byte channel还是message channel,就register操作而言,都是将自己持有的java selectable channel给注册到eventloop中的java selector中,并拿到selection key

    • doBeginRead:不管都是byte channel还是message channel,就begin read来讲,都是将selection key给关联上interest op。

    • isCompatible:不管都是byte channel还是message channel,它们都必须和NioEventLoop来配合。

    • 由于NIO是非阻塞的(NioEventLoop非阻塞)所以connect流程必须弄成异步,这个异步流程由自己持有的NioUnsafe完成。

    1.2.4. AbstractNioByteChannel、AbstractNioMessageChannel

    byte channel、message channel的区别主要是read、write的策略。

    而实际的read、write策略非常复杂,目前的纵向流程只到了bind、connect,因此暂不分析。

    1.3. Unsafe系列

    由于Netty网络通信过程异常复杂,大致可分为两部分的操作:

    • 第一部分是对java channel的操作的封装,比如connect、close等。
    • 另一部分是和netty体系相关,比如异步流程、读策略、写策略等。这些需要和netty的其他组件打交道,比如event loop、channel pipeline、channel outbound buffer等,这些操作异常复杂,精细,正如Unsafe字面的语义。

    因此,为了将这两部分的操作进行隔离,netty引入了Unsafe这个内部接口,它从属于Channel,并且它的不同实现类散落在不同的Channel实现类中。将后者操作放入unsafe中实现,将前者操作放在netty的channel中实现,这样可以保证channel的纯净。

    1.3.1. 继承关系

    由于我们目前只关心三种常用的channel:NioSocketChannel、NioServerSocketChannel、NioDatagramChannel,因此它们涉及Unsafe的继承关系如下:

    1.3.2. Unsafe

    Unsafe operations that should never be called from user-code. These methods are only provided to implement the actual transport, and must be invoked from an I/O thread except for the following methods:

    • localAddress()
    • remoteAddress()
    • closeForcibly()
    • register(EventLoop, ChannelPromise)
    • deregister(ChannelPromise)
    • voidPromise()

    由于Unsafe是对Channel的复杂流程操作的封装,从属于Channel,所以其方法和Channel很相近:

    • 一类是信息的获取操作:RecvByteBufAllocator.Handle、local address、remote address、voidPromise、channelOutBoundbuffer
    • 另一类是动作类型的操作:bind、register、deregister、connect、disconnect、close、closeFocibly、beginRead、write、flush
  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: