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缓冲区一有数据就会发送读信号开始读取,每次读到数据就开始链式分发,交给管道处理,管道处理得到我们想要的数据,直到读够数据为止,返回数据给客户端。
- netty源码深入研究(从客户端入手)第二篇(详解读消息的管道处理流程)
- netty源码深入研究(从客户端入手)第三篇(详解写消息的管道处理流程)
- netty源码深入研究(从客户端入手)第四篇(读写超时详解)
- netty源码深入研究(从客户端入手)第一篇
- MQTT---HiveMQ源码详解(十二)Netty-MQTT消息、事件处理(流程)
- Netty(一):基础概念及消息处理流程
- Netty源码分析(九)—IO事件处理流程
- Glide系列第二弹,从源码的角度深入理解Glide的执行流程
- 第二人生的源码分析(三十七)消息处理的完整流程
- 蔡军生先生第二人生的源码分析(三十七)消息处理的完整流程
- (笔记)第二章 :先从看到的入手-探究活动
- 管道的应用(pipe)《深入分析Linux内核源码》
- MQTT---HiveMQ源码详解(十三)Netty-MQTT消息、事件处理(源码举例解读)
- okio读写流源码详解(第二篇(缓存BufferedSink 读入流程详解))
- (三)Netty源码学习笔记之boss线程处理流程
- react native实现原理解析(从源码入手,nice)
- Netty源码– Netty服务器处理流程分析
- 深入研究了解Windows的消息处理机制
- Java-cmd命令运行java程序
- Struts2入门--第一个简单例子
- TOP命令查看Linux系统CPU和内存使用情况
- JAVA参数验证 Validation(一)
- windows之win10无法使用小米随身wifi
- netty源码深入研究(从客户端入手)第二篇(详解读消息的管道处理流程)
- Maven 生命周期与插件
- Hive自定义函数UDF
- android 可配置的圆弧进度条
- camera2 opengl实现滤镜效果录制视频 五 音视频合并
- CSS3总结——选择器
- struts2中result的type属性详解
- 【web开发】小小网络工程狮的日常-(2017-9-8)
- 从 0 开始创建一个属于你自己的 PHP 框架