上一章节我们分析了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()抽象步骤
首先会调用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);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/1039ab6892f1a245b85dbc887a605061.png)
刷新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;
}
}