第六章 Netty深入剖析之Netty编码

上一章节我们分析了netty的ByteToMessageDecoder,本章节我们分析netty的编码功能,其核心功能封装在父类MessageToByteEncoder中,本章节我们以案例驱动,下面为本章功能代码已经列出,我们已经学习过了netty的事件传播机制,故知道ChannelInboundHandler的调用顺序是从下到上,故先调用BizHandler再调用Encoder.BizHandler的writeAndFlush()方法,这里就是我们本章故事的起点。

//我们要编码的对象
public class User {
    private int age;
    private String name;

    public User(int age, String name) {

        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

public class BizHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //...

        User user = new User(19, "zhangsan");

        ctx.channel().writeAndFlush(user);
    }
}

 

/**
 * ---------------------
 *|   4    |  4  |  ?   |
 * ---------------------
 *| length | age | name |
 * ---------------------
 */



public class Encoder extends MessageToByteEncoder<User> {
    @Override
    protected void encode(ChannelHandlerContext ctx, User user, ByteBuf out) throws Exception {

        byte[] bytes = user.getName().getBytes();
        out.writeInt(4 + bytes.length);
        out.writeInt(user.getAge());
        out.writeBytes(bytes);
    }
}
public final class Server {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new Encoder());
                    ch.pipeline().addLast(new BizHandler());
                }
            });

            ChannelFuture f = b.bind(8888).sync();

            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

目录

writeAndFlush()抽象步骤

MessageToByteEncoder

写buffer队列

刷新buffer队列


 

writeAndFlush()抽象步骤

首先会调用TailContext的writeAndFlush()方法,在其内部最终会调用带flush 标志位的write()方法,我们点击进入该方法,其内部最终会调用去封装的channelOutboundHandler的invokeWriteAndFlush()方法,

其会依次调用handler的Write 和Flush 方法,根据事件传播机制,write方法会依次传递给我们自定义的MessageToByteEncoder的write()方法 和HeadContext 的write()方法。下一小节我们分析Encoder的write()方法。

public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
        if (msg == null) {
            throw new NullPointerException("msg");
        }

        if (!validatePromise(promise, true)) {
            ReferenceCountUtil.release(msg);
            // cancelled
            return promise;
        }
        //调用带有flush标志位的write方法
        write(msg, true, promise);

        return promise;
    }

//其内部最终会调用去封装的channelOutboundHandler的invokeWriteAndFlush()方法
private void write(Object msg, boolean flush, ChannelPromise promise) {
        AbstractChannelHandlerContext next = findContextOutbound();
        final Object m = pipeline.touch(msg, next);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            if (flush) {
                next.invokeWriteAndFlush(m, promise);
            } else {
                next.invokeWrite(m, promise);
            }
        } else {
            AbstractWriteTask task;
            if (flush) {
                task = WriteAndFlushTask.newInstance(next, m, promise);
            }  else {
                task = WriteTask.newInstance(next, m, promise);
            }
            safeExecute(executor, task, promise, m);
        }
    }

//我们看到其依次调用handler的Write 和Flush 方法
 private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
        if (invokeHandler()) {
            invokeWrite0(msg, promise);
            invokeFlush0();
        } else {
            writeAndFlush(msg, promise);
        }
    }

MessageToByteEncoder

本节我们从MessageToByteEncoder的write方法开始讲解,基本经历了四个步骤,第一是判断当前handler是否支持类型的对象的编码过程,第二 分配内存,第三 进行用户自定义代码的编码功能,第四将编码后的内容向下传播

 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
               //判断当前handler是否支持该种类型的编码
            if (acceptOutboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                //分配一个buffer供编码后的字节内容进行写入
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
                //进行编码操作
                    encode(ctx, cast, buf);
                } finally {
                    ReferenceCountUtil.release(cast);
                }
                //将写事件向下传递
                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
                    buf.release();
                    ctx.write(Unpooled.EMPTY_BUFFER, promise);
                }
                buf = null;
            } else {
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable e) {
            throw new EncoderException(e);
        } finally {
            if (buf != null) {
                buf.release();
            }
        }
    }

写buffer队列

当上面我们自定义的MessageToByteEncoder转化为字节数据后会向下传递到HeadContext的write()方法,在该方法中会调用我们封装协议的write()方法。在unsafe的write中就是写入buffer队列的逻辑了,该方法大致做了三件事情,1)将bytebuf 进行direct持久化,2)插入写队列,3)设置是否可以写的状态,这相当于是一个生产者消费者模式。首先分析第一点,将bytebuf进行持久化的工作是在filterOutboundMessage方法中进行的,该方法逻辑比较简单,就是简单的判断该bytebuf是不是直接内存如果不是在将其拷贝到新构建的直接内存byteBuf中。在插入队列操作中,我们可以简单的归结为一个数据结构的指针移动操作,参将添加元素时的指针移动操作图解,在保存完元素后调用 incrementPendingOutboundBytes(size, false);该方法设置是否可写入的状态,该方法,首先将添加到缓冲区的字节数进行累加,之后判断其是否超过允许写入的最高水平线,如果超过则设置不可写入状态。到此为止写入buffer队列的操作完成,并且我们知道该队列一个

 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
            unsafe.write(msg, promise);
        }

 

 public final void write(Object msg, ChannelPromise promise) {
            assertEventLoop();

            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
           ...
                return;
            }

            int size;
            try {
///将bytebuf 进行direct持久化
                msg = filterOutboundMessage(msg);
             ...
            } catch (Throwable t) {
              ...
            }
//插入写队列,设置是否可以写的状态
            outboundBuffer.addMessage(msg, size, promise);
        }
protected final Object filterOutboundMessage(Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
//就是简单的判断该bytebuf是不是直接内存如果不是在将其拷贝到新构建的直接内存byteBuf中
            if (buf.isDirect()) {
                return msg;
            }

            return newDirectBuffer(buf);
        }

        if (msg instanceof FileRegion) {
            return msg;
        }

        throw new UnsupportedOperationException(
                "unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
    }
public void addMessage(Object msg, int size, ChannelPromise promise) {
//首先我们将编码的内容封装为一个netry实例对象
        Entry entry = Entry.newInstance(msg, size, total(msg), promise);
//然后进行指针的移动操作,由于此处不够具体我们划入具体说明一下每次的指针变化
        if (tailEntry == null) {
            flushedEntry = null;
            tailEntry = entry;
        } else {
            Entry tail = tailEntry;
            tail.next = entry;
            tailEntry = entry;
        }
        if (unflushedEntry == null) {
            unflushedEntry = entry;
        }

        //之后设置写状态,我们进去查看该代码的执行逻辑
        incrementPendingOutboundBytes(size, false);
    }

//该方法,首先将添加到缓冲区的字节数进行累加,之后判断其是否超过允许写入的最高水平线,如果超过则设置不可写入状态
 private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
        if (size == 0) {
            return;
        }

        long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
        if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
            setUnwritable(invokeLater);
        }
    }

 

添加元素时的指针移动操作

 

刷新buffer队列

我们进入HeadContext对象的flush()方法,其会调用unsafe对象的flush 方法,该方法大致执行以下三中操作 1) 首先获的我们填充到的buffer队列,2)计算我们本次刷新的数量,并移动刷新的指针指向待刷新元素的首位,3)进行刷新操作  。我们先分析计算我们本次刷新的数量,并移动刷新的指针指向待刷新元素的首位,进入addFlush() 方法,首次刷新时,flushedEntry 为null,flushedEntry 指向将未刷新的元素的开始,之后进入while循环,遍历单向列表,并计算待刷新的元素的数量存入flushed 成员变量中,遍历完成后,未刷新指针设置为null。flush0()中我们重点关注doWrite()方法,进行写操作,我们重点刷新操作的核心逻辑方法。该方法大致分为三步,1)获取一个待刷新元素,2)发送,3)在队列中删除该元素。

   @Override
        public void flush(ChannelHandlerContext ctx) throws Exception {
            unsafe.flush();
        }
  public final void flush() {
            assertEventLoop();
            //首先获的我们填充到的buffer队列
            ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
            if (outboundBuffer == null) {
                return;
            }
            //计算我们本次刷新的数量,并移动刷新的指针指向待刷新元素的首位
            outboundBuffer.addFlush();
            //进行刷新操作
            flush0();
        }
 public void addFlush() {
       //本方法将为刷新的指针指向
        Entry entry = unflushedEntry;
        if (entry != null) {
            //首次刷新,flushedEntry 为null,flushedEntry 指向将未刷新的元素的开始
            if (flushedEntry == null) {
                // there is no flushedEntry yet, so start with the entry
                flushedEntry = entry;
            }
            do {
//并计算待刷新的元素的数量存入flushed 成员变量中
                flushed ++;
                if (!entry.promise.setUncancellable()) {
                    // Was cancelled so make sure we free up memory and notify about the freed bytes
                    int pending = entry.cancel();
                    decrementPendingOutboundBytes(pending, false, true);
                }
                entry = entry.next;
            } while (entry != null);

            // 添加过后未刷新指针设置为null
            unflushedEntry = null;
        }
    }

 

  protected void flush0() {
          ...
//获取buffer队列
            final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
         ...

            try {
//进行写操作,我们重点刷新操作的核心逻辑方法
                doWrite(outboundBuffer);
            } catch (Throwable t) {
             ...
            } finally {
                inFlush0 = false;
            }
        }

 

 protected void doWrite(ChannelOutboundBuffer in) throws Exception {
        int writeSpinCount = -1;

        boolean setOpWrite = false;
        for (;;) {
//获取buffer的刷新的元素
            Object msg = in.current();
           ...

            if (msg instanceof ByteBuf) {
                ByteBuf buf = (ByteBuf) msg;
                int readableBytes = buf.readableBytes();
                if (readableBytes == 0) {
                    in.remove();
                    continue;
                }

                boolean done = false;
                long flushedAmount = 0;
                if (writeSpinCount == -1) {
                    writeSpinCount = config().getWriteSpinCount();
                }
                for (int i = writeSpinCount - 1; i >= 0; i --) {
                     //调用jdk底层方法进行消息的发送
                    int localFlushedAmount = doWriteBytes(buf);
                    if (localFlushedAmount == 0) {
                        setOpWrite = true;
                        break;
                    }

                    flushedAmount += localFlushedAmount;
                    if (!buf.isReadable()) {
                        done = true;
                        break;
                    }
                }

                in.progress(flushedAmount);
                //发送完成后在buffer队列中进行删除操作
                if (done) {
                    in.remove();
                } else {
                    // Break the loop and so incompleteWrite(...) is called.
                    break;
                }
            } else if (msg instanceof FileRegion) {
              ...
            }
        }
        incompleteWrite(setOpWrite);
    }

 

//获取一个待刷寻的元素 
public Object current() {
        Entry entry = flushedEntry;
        if (entry == null) {
            return null;
        }

        return entry.msg;
    }
//调用底层的jdk方法进行数据的刷新操作
 protected int doWriteBytes(ByteBuf buf) throws Exception {
        final int expectedWrittenBytes = buf.readableBytes();
        return buf.readBytes(javaChannel(), expectedWrittenBytes);
    }

 

 public boolean remove() {
        //获取当前已经发送完成的元素
        Entry e = flushedEntry;
        if (e == null) {
            clearNioBuffers();
            return false;
        }
        Object msg = e.msg;

        ChannelPromise promise = e.promise;
        int size = e.pendingSize;
        //从队列中进行删除
        removeEntry(e);

        if (!e.cancelled) {
            // only release message, notify and decrement if it was not canceled before.
            ReferenceCountUtil.safeRelease(msg);
            safeSuccess(promise);
            decrementPendingOutboundBytes(size, false, true);
        }

        // recycle the entry
        e.recycle();

        return true;
    }

 

 private void removeEntry(Entry e) {
        if (-- flushed == 0) {
            // 将队列中的所有元素发送完成后,将待刷新队列指针设置为null
            flushedEntry = null;
            //如果已经发送完,将所有指针这是为null
            if (e == tailEntry) {
                tailEntry = null;
                unflushedEntry = null;
            }
        } else {
//没有发送完成就将flushedEntry指向下一个元素
            flushedEntry = e.next;
        }
    }

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值