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

上一篇讲到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缓冲区一有数据就会发送读信号开始读取,每次读到数据就开始链式分发,交给管道处理,管道处理得到我们想要的数据,直到读够数据为止,返回数据给客户端。

































































  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值