netty源码深入研究(从客户端入手)第二篇(详解读消息的管道处理流程)

来源:互联网 发布:大学老师知乎 编辑:程序博客网 时间:2024/05/24 02:52

上一篇讲到netty和服务器建立连接的所有过程,接着上一篇的结尾,看代码

private static void doConnect(            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {               final Channel channel = connectPromise.channel();      标记:  channel.eventLoop().execute(new Runnable() {            @Override            public void run() {                if (localAddress == null) {                    channel.connect(remoteAddress, connectPromise);                } else {                    channel.connect(remoteAddress, localAddress, connectPromise);                }                connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);            }        });    }

看标记处,此处处直接加入队列一个任务执行连接,好找到我们的管道(channel即是NioSocketChannel),eventLoop()返回的就是NioEventLoopGroup,进入,在它的父类找到这个方法

 @Override    public void execute(Runnable command) {        next().execute(command);    }

上一篇已经讲过这个类next()方法获取的是NioEventLoop,那么这个类就是负责整个读写事件分发的最重要的一个类了,跟进

 @Override    public void execute(Runnable task) {        if (task == null) {            throw new NullPointerException("task");        }//判断当前处理任务的线程是否启动        boolean inEventLoop = inEventLoop();//没有启动的话开启线程,启动的话直接加入任务        if (inEventLoop) {            addTask(task);        } else { 标记:           startThread();            addTask(task);//加入线程池关闭了,移除任务,抛拒绝任务的异常            if (isShutdown() && removeTask(task)) {                reject();            }        }//唤醒线程继续处理任务 if (!addTaskWakesUp && wakesUpForTask(task)) { wakeup(inEventLoop); } }看标记处现在最需要关心的就是启动线程了,private void doStartThread() {        assert thread == null;        executor.execute(new Runnable() {            @Override            public void run() {                thread = Thread.currentThread();//线程是否被打断                if (interrupted) {                    thread.interrupt();                }                boolean success = false;//更新最后一次启动的时间 updateLastExecutionTime(); try {//执行当前的run方法,进入这个方法                    SingleThreadEventExecutor.this.run();                    success = true;

最后在NioEventLoop中发现最终处理任务的方法,如下代码

 protected void run() {        for (;;) {            try {                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {                    case SelectStrategy.CONTINUE:                        continue;                    case SelectStrategy.SELECT:                        select(wakenUp.getAndSet(false));                        if (wakenUp.get()) {                            selector.wakeup();                        }                    default:                        // fallthrough                }                cancelledKeys = 0;                needsToSelectAgain = false;                final int ioRatio = this.ioRatio;                if (ioRatio == 100) {                    try {  标记:                      processSelectedKeys();                    } finally {                        // Ensure we always run tasks.                        runAllTasks();                    }                } else {                    final long ioStartTime = System.nanoTime();                    try { 标记:                  processSelectedKeys(); } finally { // Ensure we always run tasks. final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); } } } catch (Throwable t) { handleLoopException(t); } // Always handle shutdown even if the loop processing threw an exception. try { if (isShuttingDown()) { closeAll(); if (confirmShutdown()) { return; } } } catch (Throwable t) { handleLoopException(t); } } }

看标记处,其他的方法都是一些无关紧要的判断,真正开始读写消息了

private void processSelectedKeysPlain(Set<SelectionKey> selectedKeys) {      //selectedKeys集合是第一篇当中连接之前,注册的一些key值,nio通信基础,不熟悉的小伙伴请自行查找        if (selectedKeys.isEmpty()) {            return;        }        Iterator<SelectionKey> i = selectedKeys.iterator(); //死循环,不断监测通道信息,是否可读,可写 for (;;) { final SelectionKey k = i.next(); final Object a = k.attachment(); i.remove();//a肯定属于AbstractNioChannel if (a instanceof AbstractNioChannel) { 标记: processSelectedKey(k, (AbstractNioChannel) a); } else { @SuppressWarnings("unchecked") NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a; processSelectedKey(k, task); } if (!i.hasNext()) { break; } if (needsToSelectAgain) { selectAgain(); selectedKeys = selector.selectedKeys(); // Create the iterator again to avoid ConcurrentModificationException if (selectedKeys.isEmpty()) { break; } else { i = selectedKeys.iterator(); } } } }

不容易啊,最终终于找到最核心的方法

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {        final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();        if (!k.isValid()) {            final EventLoop eventLoop;            try {                eventLoop = ch.eventLoop();            } catch (Throwable ignored) {                               return;            }                      if (eventLoop != this || eventLoop == null) {                return;            }            // close the channel if the key is not valid anymore            unsafe.close(unsafe.voidPromise());            return;        }        try {            int readyOps = k.readyOps();           //已经连接上,nio收到这个通知            if ((readyOps & SelectionKey.OP_CONNECT) != 0) {                             int ops = k.interestOps();                ops &= ~SelectionKey.OP_CONNECT;                k.interestOps(ops);                unsafe.finishConnect();            }           //已经可以写的时候收到这个通知 if ((readyOps & SelectionKey.OP_WRITE) != 0) { ch.unsafe().forceFlush(); } //最后可以读的时候收到这个通知 if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { unsafe.read(); } } catch (CancelledKeyException ignored) { unsafe.close(unsafe.voidPromise()); } }

终于找到入口点,unsafe又是什么鬼,曹曹曹.....,我想说整这么多类真的好吗,真正这相当于一个中介啊,搞了半天真正执行方法的是unsafe,什么鬼啊

 public interface NioUnsafe extends Unsafe {        /**         * Return underlying {@link SelectableChannel}         */        SelectableChannel ch();        /**         * Finish connect         */        void finishConnect();        /**         * Read from underlying {@link SelectableChannel}         */        void read();        void forceFlush();    }

一个接口,好,通道里面持有这个接口,再次进入NioSocketChannel

 protected AbstractNioUnsafe newUnsafe() {        return new NioSocketChannelUnsafe();    }

最后居然是NioSocketChannelUnsafe类,隐藏够深的,我只能说写的真好

真正的读终于开始了
 public final void read() {//获取配置文件 config = new NioSocketChannelConfig(this, socket.socket());            final ChannelConfig config = config();//得到当前的管道 final ChannelPipeline pipeline = pipeline(); //allocator 是AdaptiveRecvByteBufAllocator//ByteBuffer的封装类最终靠他解析字节并返回给客户端的 final ByteBufAllocator allocator = config.getAllocator();//allocHandle 最终是通过FixedRecvByteBufAllocator获取的       标记1:     final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();            allocHandle.reset(config);            ByteBuf byteBuf = null;            boolean close = false;/循环开始读 try { do { 标记2: byteBuf = allocHandle.allocate(allocator);/向byteBuf里填充读取数据标记3: allocHandle.lastBytesRead(doReadBytes(byteBuf)); if (allocHandle.lastBytesRead() <= 0) { // nothing was read. release the buffer. byteBuf.release(); byteBuf = null; close = allocHandle.lastBytesRead() < 0; break; } allocHandle.incMessagesRead(1); readPending = false;标记:最重要的方法开始分发管道了 pipeline.fireChannelRead(byteBuf); byteBuf = null; } while (allocHandle.continueReading()); allocHandle.readComplete(); pipeline.fireChannelReadComplete(); if (close) { closeOnRead(pipeline); } } catch (Throwable t) { handleReadException(pipeline, byteBuf, t, close, allocHandle); } finally { if (!readPending && !config.isAutoRead()) { removeReadOp(); } } } }

看标记1怎么获取到handler的 ,最终是获得HandleImpl这个类

public Handle newHandle() {        return new HandleImpl(bufferSize);    }

那么标记2的allocHandle.allocate(allocator);默认申请直接内存2048个字节的ByteBuf ,不了解java的nio的请自行百度ByteBuf的用法。

看标记3,我们进入doReadBytes(byteBuf)的方法

  protected int doReadBytes(ByteBuf byteBuf) throws Exception {//妈的刚转到NioSocketChannel又转回NioSocketChannelUnsafe   final RecvByteBufAllocator.Handle allocHandle =unsafe().recvBufAllocHandle();//byteBuf还有多少空间可写,即position离limit的距离,写入HandleImpl的attemptedBytesRead属性中,做记录        allocHandle.attemptedBytesRead(byteBuf.writableBytes());//真正的读取 return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead()); }
好了现在该返回上一个方法了,即allocHandle.lastBytesRead(doReadBytes(byteBuf));方法,进入该方法

public final void lastBytesRead(int bytes) {//最后一次读取的字节的个数            lastBytesRead = bytes;            if (bytes > 0) {//总共读取的字节数                totalBytesRead += bytes;            }        }

这个方法的主要作用是记录从流中读了多少数据,那么现在思路清晰多了,HandleImpl类就是用来做记录用的一个辅助类


//如果读取的的字节为0的话,那么释放byteBuf if (allocHandle.lastBytesRead() <= 0) {                        // nothing was read. release the buffer.                        byteBuf.release();                        byteBuf = null;                        close = allocHandle.lastBytesRead() < 0;                        break;                    }            //将读的次数加1 allocHandle.incMessagesRead(1); readPending = false;//调用通道将读取的数据传入 pipeline.fireChannelRead(byteBuf); byteBuf = null;//如果仍然可读继续读 } while (allocHandle.continueReading()); allocHandle.readComplete(); pipeline.fireChannelReadComplete(); if (close) { closeOnRead(pipeline); }

接下来找到我们的DefaultChannelPipeline类   public final ChannelPipeline fireChannelRead(Object msg) {        AbstractChannelHandlerContext.invokeChannelRead(head, msg);        return this;    }

继续,AbstractChannelHandlerContext又是什么鬼呢,还记得上一篇讲的吗,这是个管道封装类,管道即我们添加的

ch.pipeline().addLast("encoder", new Encoder());ch.pipeline().addLast("decoder", new Decoder());ch.pipeline().addLast("handler",new ClientHandler(NettyCore.this));

这些类通通是继承ChannelInboundHandlerAdapter或ChannelOutboundHandlerAdapter的类,先看一下怎么添加的

 private void addLast0(AbstractChannelHandlerContext newCtx) {        AbstractChannelHandlerContext prev = tail.prev;        newCtx.prev = prev;        newCtx.next = tail;        prev.next = newCtx;        tail.prev = newCtx;    }

以队列的方式插入到tail的前面和 head的后面,那么这两个又是干啥的呢

 protected DefaultChannelPipeline(Channel channel) {        this.channel = ObjectUtil.checkNotNull(channel, "channel");        succeededFuture = new SucceededChannelFuture(channel, null);        voidPromise =  new VoidChannelPromise(channel, true);        tail = new TailContext(this);        head = new HeadContext(this);        head.next = tail;        tail.prev = head;    }

初始化时创建,将tail放在head的后面,这是addFirst方法


private void addFirst0(AbstractChannelHandlerContext newCtx) {        AbstractChannelHandlerContext nextCtx = head.next;        newCtx.prev = head;        newCtx.next = nextCtx;        head.next = newCtx;        nextCtx.prev = newCtx;    }

将newCtx插入到队列head的后面,继续


//第一个参数是head头,第二参数是byteBuf static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {//判断管道是否可用 final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeChannelRead(m); } else { executor.execute(new Runnable() { @Override public void run() { next.invokeChannelRead(m); } }); } }

饶了这么一大圈终于到了传消息了

private void invokeChannelRead(Object msg) {        if (invokeHandler()) {            try {      标记1:   ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { notifyHandlerException(t); } } else { 标记2: fireChannelRead(msg); } }

进入标记2的方法先从队列头开始读


 public ChannelHandlerContext fireChannelRead(final Object msg) {        invokeChannelRead(findContextInbound(), msg);        return this;    }

findContextInbound又是什么鬼呢?


private AbstractChannelHandlerContext findContextInbound() {        AbstractChannelHandlerContext ctx = this;        do {            ctx = ctx.next;        } while (!ctx.inbound);        return ctx;    }

重点终于来了

//读管道我们必须继承ChannelInboundHandler private static boolean isInbound(ChannelHandler handler) {        return handler instanceof ChannelInboundHandler;    }//写管道我必须继承ChannelOutboundHandler private static boolean isOutbound(ChannelHandler handler) { return handler instanceof ChannelOutboundHandler; }

现在思路已经很清晰了,从head开始调用invokeChannelRead-》fireChannelRead-》invokeChannelRead-》调用队列下一个fireChannelRead,以此形

成循环,知道调用队列最后一个tail的时候invokeHandler()为true走标记1,此时结束一次读取。现在整个读取就形成了一条链。


总结:tcp缓冲区一有数据就会发送读信号开始读取,每次读到数据就开始链式分发,交给管道处理,管道处理得到我们想要的数据,直到读够数据为止,返回数据给客户端。










 
阅读全文
0 0