Netty源码分析3:新连接接入

2020-03-31  本文已影响0人  LucasHao

本文参考自慕课网《Java读源码之netty》

如何检测新连接? boss线程轮循出accept事件,通过底层的accept()接收连接

如何注册到NioEventLoop线程? 通过serverSocketChannel的Adptor中,调用workGroup的chooser.next()来确定EventLoop,从而调用其register()绑定selector,并设置对读事件敏感

四步骤:

1.新连接检测

NioEventLoop.java

    private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
        NioUnsafe unsafe = ch.unsafe();
        //...
        if ((readyOps & 17) != 0 || readyOps == 0) {
            unsafe.read();
            //...
        }
    }

AbstractNioMessageChannel.java

    public void read() {
            //...
            try {
                int localRead;
                try {
                    do {
                        localRead = AbstractNioMessageChannel.
                                this.doReadMessages(this.readBuf);
                        //...当发现读完了,那么localRead就是一个非正数,就break;跳出
                        allocHandle.incMessagesRead(localRead);//连接计数
                    } while(allocHandle.continueReading());//一直读到没有限定条件
                    //.... 
                }
            }
    }
//可以看到这里限定的是netty单次处理read()事件允许接受的一些最大门限
    public boolean continueReading() {
            return this.config.isAutoRead()
            && this.totalMessages < this.maxMessagePerRead   //这个值默认16
            && this.totalBytesRead < 2147483647;
        }

NioServerSocketChannel.java

    protected int doReadMessages(List<Object> buf) throws Exception {
        SocketChannel ch = this.javaChannel().accept();  //接收到ch
        try {
            if (ch != null) {
                buf.add(new NioSocketChannel(this, ch));//并放入上面MessageChannel中的List中
                return 1;
            }
            //否则没有新连接了,返回非正数
        }
        //...
    }

2.NioChannel的创建

通过上述的new NioSocketChannel(this, ch)创建一个netty封装的channel(可以看到服务端是通过工厂模式反射,而这用的New instance)

new NioSocketChannel(this, ch)
  //this就是当前的正在处理新连接的NioServeSocketChannel
  //ch就是new出来的jdk自带channel

可以看到NioSocketChannel 的构造函数里面分为了两个步骤

  public NioSocketChannel(Channel parent, java.nio.channels.SocketChannel socket) {
        super(parent, socket);   //调用父类构造函数
        this.config = new NioSocketChannel    //新建绑定的config类
          .NioSocketChannelConfig(this, socket.socket());
    }

X. 题外话 Netty中的 Channel 分类

我们自己所使用的channel有

  • 服务端 NioServeSocketChannel 通过工厂 反射实现
  • 客户端 NioSocketChannel 通过连接触发serveSocketChannel的read进行 new产生
  • unsafe 客户端channel构造的时候 产生

而所有channel的继承关系大致如下


image.png

对于unsafe(),封装了channel的读写事件,对于服务端来说,read()会调用doReadMessage()中的jdk read方法接收请求;对客户端来说read()会调用doReadBytes()读取放入ByteBuffer中


3.新连接NioEventLoop的分配和selector的注册

首先我们回顾服务端channel的创建过程;在服务端channel创建后会通过init()初始化,其最后又几行逻辑:

ServerBootstrap

    void init(Channel channel) throws Exception {
        //...
        ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(
                            new ChannelHandler[]{new ServerBootstrap
                            .ServerBootstrapAcceptor(
                                currentChildGroup,
                                currentChildHandler,
                                currentChildOptions,
                                currentChildAttrs)});
                    }
                });
    }

可以看到服务端channel的Pipline中加入了一个ServerBootstrapAcceptor作为adptor,这个adptor会在接收到新连接的时候其作用;同时传入的还有用户自定义的Options和Attributes以及Handler,这些自定义的东西会在创建每个新连接的channel中给他们赋值,设置。

对于新连接,首先执行上述两部分的任务:NioEventLoop中拿到Key后会调用processSelectedKey()来处理channel,里面会调用channel的unsafe来执行unsafe.read(),unsafe是NioMessageChannel中的内部类read()函数中通过doReadMessage()通过jdk底层accept获取到连接的channel;

然后调用pipeline.fireChannelRead()交给自动添加的adptor进行处理:
AbstractNioMessageChannel

    private final class NioMessageUnsafe extends AbstractNioUnsafe {
        public void read() {
             //...
             AbstractNioMessageChannel.this.doReadMessages(this.readBuf);
             //...
                for(int i = 0; i < localRead; ++i) {
                    AbstractNioMessageChannel.this.readPending = false;
                    pipeline.fireChannelRead(this.readBuf.get(i));//传给上份代码中的adptor
                }
        }
    }

ServerBootstrapAcceptor中,主要有三件事:

4 客户端NioSocketChannel中读事件的注册

上一步绑定到selector后其实并没有结束,如果不做任何操作是不会产生任何事件的,原因在于绑定的channel并没有设置感兴趣的事件,而selector只有感兴趣的事件发生才会检测到;所以在register至置定的EventLoop之后,会通过设置感兴趣的事件为readInterestOp:
AbstractNioChannel

    protected void doBeginRead() throws Exception {
        SelectionKey selectionKey = this.selectionKey;
        if (selectionKey.isValid()) {
            this.readPending = true;
            int interestOps = selectionKey.interestOps();
            //一般情况下interstOps=0 表示对任何事件不敏感
            if ((interestOps & this.readInterestOp) == 0) {
                selectionKey.interestOps(interestOps | this.readInterestOp);
                //所以这里设置了对读事件敏感
            }
        }
    }
上一篇下一篇

猜你喜欢

热点阅读