netty 源码分析之(三)ChannelSink

一、ChannelSink用于绑定地址端口

这个东西应该是netty里面最难理解的,或者最关键的组件了,这个我会慢慢的进行分析。在Pipeline传送完后,都必须都通ChannelSink进行处理。Sink默认处理了琐碎的操作,例如连接、读写等等。

          ChannelSink这个组件是来处理downstream请求和产生upstream时间的一个组件, 是所有io操作的执行者。也就是传输的逻辑层吧。当channel创建的时候就有一个ChannelSink和它想绑定。

        传输层的代码实现一般来说都是比较麻烦的,相比来说客户端的实现一般来说比服务端的实现要简单一些,服务端一般要处理状态变换和数据交换等,我们一点一点来看ChannelSink。

public interface ChannelSink {

    /**
     * Invoked by {@link ChannelPipeline} when a downstream {@link ChannelEvent}
     * has reached its terminal (the head of the pipeline).
     */
    void eventSunk(ChannelPipeline pipeline, ChannelEvent e) throws Exception;

    /**
     * Invoked by {@link ChannelPipeline} when an exception was raised while
     * one of its {@link ChannelHandler}s process a {@link ChannelEvent}.
     */
    void exceptionCaught(ChannelPipeline pipeline, ChannelEvent e, ChannelPipelineException cause) throws Exception;
	}

          接口里面只定义了两个操作,一个是eventSunk,一个是exceptionCaught,eventSunk 我不知道怎么翻译,姑且理解为让event处理吧,一般来说应该有一个abstract骨架程序实现,我们就来看吧。

        AbstractChannelSink里面只实现了exceptionCaught,来看一眼:

        

public void exceptionCaught(ChannelPipeline pipeline,
            ChannelEvent event, ChannelPipelineException cause) throws Exception {
        Throwable actualCause = cause.getCause();
        if (actualCause == null) {
            actualCause = cause;
        }

        fireExceptionCaught(event.getChannel(), actualCause);
    }

        这个实际上是向上层报告一个DefaultExceptionEvent的upstream事件。

      下来我们就直奔主题来看NioClientSocketPipelineSink这个类吧。

扫描二维码关注公众号,回复: 632854 查看本文章

       首先来看局部变量

private static final AtomicInteger nextId = new AtomicInteger();

    final int id = nextId.incrementAndGet();
    final Executor bossExecutor;

    private final Boss[] bosses;
    private final NioWorker[] workers;

    private final AtomicInteger bossIndex = new AtomicInteger();
    private final AtomicInteger workerIndex = new AtomicInteger();

       上面是sink的id,这个地方用到了AtomicInteger。还有Boss和NioWorker,boss的个数现在 默认是1,NioWorker的个数是处理器的个数的2倍。后面两个是bossIndex和workerIndex。

     在构造函数里面初始化了Boss和NioWorker

NioClientSocketPipelineSink(
            Executor bossExecutor, Executor workerExecutor,
            int bossCount, int workerCount) {
        this.bossExecutor = bossExecutor;
        
        bosses = new Boss[bossCount];
        for (int i = 0; i < bosses.length; i ++) {
            bosses[i] = new Boss(i + 1);
        }
        
        workers = new NioWorker[workerCount];
        for (int i = 0; i < workers.length; i ++) {
            workers[i] = new NioWorker(id, i + 1, workerExecutor);
        }
    }

如果是一个网络会话最末端的事件,比如messageRecieve,那么可能在某个handler里面就直接结束整个会话,并把数据交给上层应用,但是如果是网络会话的中途事件,比如connect事件,那么当触发connect事件时,经过pipeline流转,最终会到达挂载pipeline最底下的ChannelSink实例中,这类实例主要作用就是发送请求和接收请求,以及数据的读写操作。

NIO方式ChannelSink一般会有1个BOSS实例(implements Runnable),以及若干个worker实例(不设置默认为cpu cores*2个worker),这在前面已经提起过,BOSS线程在客户端类型的ChannelSink和服务器端类型的ChannelSink触发条件不一样,客户端类型的BOSS线程是在发生connect事件时启动,主要监听connect是否成功,如果成功,将启动一个worker线程,将connected的channel交给这个线程继续下面的工作,而服务器端的BOSS线程是发生在bind事件时启动,它的工作也相对比较简单,对于channel.socket().accept()进来的请求向Nioworker进行工作分配即可。这里需要提到的是,Server端ChannelSink实现比较特别,无论是NioServerSocketPipelineSink 还是OioServerSocketPipelineSink的eventSunk方法实现都将channel分为 ServerSocketChannel和SocketChannel分开处理。这主要原因是Boss线程accept()一个新的连接生成一个 SocketChannel交给Worker进行数据接收。

  public void eventSunk(
           ChannelPipeline pipeline, ChannelEvent e) throws Exception {
       Channel channel = e.getChannel();
       if (channel instanceof NioServerSocketChannel) {
           handleServerSocket(e);
       else if (channel instanceof NioSocketChannel) {
           handleAcceptedSocket(e);
       }
   }
 
NioWorker worker = nextWorker();
               worker.register(new NioAcceptedSocketChannel(
                       channel.getFactory(), pipeline, channel,
                       NioServerSocketPipelineSink.this, acceptedSocket,
                       worker, currentThread), null);

另外两者实例化时都会走一遍如下流程:

setConnected();
      fireChannelOpen(this);
      fireChannelBound(this, getLocalAddress());
      fireChannelConnected(this, getRemoteAddress());

而对应的ChannelSink里面的处理代码就不同于ServerSocketChannel了,因为走的是 handleAcceptedSocket(e)这一块代码,从默认实现代码来说,实例化调用 fireChannelOpen(this);fireChannelBound(this,getLocalAddress());fireChannelConnected(this,getRemoteAddress())没有什么意义,但是对于自己实现的ChannelSink有着特殊意义。具体的用途我没去了解,但是可以让用户插手Server accept连接到准备读写数据这一个过程的处理。

switch (state) {
          case OPEN:
              if (Boolean.FALSE.equals(value)) {
                  channel.worker.close(channel, future);
              }
              break;
          case BOUND:
          case CONNECTED:
              if (value == null) {
                  channel.worker.close(channel, future);
              }
              break;
          case INTEREST_OPS:
              channel.worker.setInterestOps(channel, future, ((Integer) value).intValue());
              break;
          }

猜你喜欢

转载自liao492006.iteye.com/blog/1820650