示例代码
public class SessionHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
BalanceNetProtocol.BasePackage basePackage = (BalanceNetProtocol.BasePackage) msg;
ctx.writeAndFlush(basePackage);
}
...
...
write流程
AbstractChannel#writeAndFlush--->DefaultChannelPipeline#writeAndFlush。然后从tail节点向前传播一个write事件,最终到head。
@Override
public ChannelFuture writeAndFlush(Object msg) {
return pipeline.writeAndFlush(msg);
}
@Override
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
Netty的writeAndFlush流程分析
AbstractChannelHandlerContext#invokeWriteAndFlush先调用write方法,传播一个write事件,然后再调用flush方法。
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
if (invokeHandler()) {
invokeWrite0(msg, promise);
invokeFlush0();
} else {
writeAndFlush(msg, promise);
}
}
调用HeadContext中的write方法。
主要进行两步操作,消息过滤,如果使用的不是直接内存需要申请一块直接内存将msg包装一下,然后,添加到channelOutBounfBuffer中,这是一个链表结构。
因此在传输数据的时候,最好使用DirectByteBuf可以减少一次拷贝。为什么需要这次拷贝呢?因为堆内存是会随着垃圾回收动态变化的,数据拷贝的时候需要保证内存地址不变。
ChannelOutboundBuffer 缓存是一个链表结构,每次传入的数据都会被封装成一个 Entry 对象添加到链表中。ChannelOutboundBuffer 包含三个非常重要的指针:第一个被写到缓冲区的节点 flushedEntry、第一个未被写到缓冲区的节点 unflushedEntry和最后一个节点 tailEntry。 在初始状态下这三个指针都指向 NULL,当我们每次调用 write 方法是,都会调用 addMessage 方法改变这三个指针的指向,可以参考下图理解指针的移动过程会更加形象。
ChannelOutboundBuffer包装了bytebuf,如果一直往里面写数据,可能会造成OOM,因此Netty里面引入了高低水位,也就是每次写一个entry后,会计算当前的字节数如果超过了高水位,就会传播一个不可写的事件。但是这个不可写的话,需要用户去手动判断 channel.isWriteAble,否则的话,还是可以继续写的。
2、Flush操作
flush操作就是将缓冲区的数据写到socket缓冲区的过程。将用户缓冲区的数据写到内核缓冲区。
HeadContext的flush:
主要包含两个重要方法,addflush和flush0。
addflush用于移动指针,并且减少channelOutBoundBuffer中的字节数。当减少到小于低水位的时候,就传播一个可写事件,同样是提供给用户处理的。
flush0 主要完成刷新的操作,这里引入了一个自选锁:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = -1;
for (;;) {
Object msg = in.current();
if (msg == null) {
// Wrote all messages.
clearOpWrite();
break;
}
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
int readableBytes = buf.readableBytes();
if (readableBytes == 0) {
in.remove();
continue;
}
boolean setOpWrite = false;
boolean done = false;
long flushedAmount = 0;
if (writeSpinCount == -1) {
writeSpinCount = config().getWriteSpinCount();
}
for (int i = writeSpinCount - 1; i >= 0; i --) {
int localFlushedAmount = doWriteBytes(buf); // 这里才是实际将数据写出去的地方if (localFlushedAmount == 0) {
setOpWrite = true;
break;
}
flushedAmount += localFlushedAmount;
if (!buf.isReadable()) {
done = true;
break;
}
}
in.progress(flushedAmount);
if (done) {
in.remove();
} else {
incompleteWrite(setOpWrite);
break;
}
} else if (msg instanceof FileRegion) {
FileRegion region = (FileRegion) msg;
boolean setOpWrite = false;
boolean done = false;
long flushedAmount = 0;
if (writeSpinCount == -1) {
writeSpinCount = config().getWriteSpinCount();
}
for (int i = writeSpinCount - 1; i >= 0; i --) {
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount == 0) {
setOpWrite = true;
break;
}
flushedAmount += localFlushedAmount;
if (region.transfered() >= region.count()) {
done = true;
break;
}
}
in.progress(flushedAmount);
if (done) {
in.remove(); // 根据写出的数据的数量情况, 来判断操作是否完成, 如果完成则调用 in.remove()
} else {
incompleteWrite(setOpWrite);
break;
}
} else {
throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg));
}
}
}
1、如果写完了调用in.remove:触发接听
public final boolean remove() {
if (isEmpty()) {
return false;
}
Entry e = buffer[flushed];
Object msg = e.msg;
if (msg == null) {
return false;
}
ChannelPromise promise = e.promise;
int size = e.pendingSize;
e.clear();
flushed = flushed + 1 & buffer.length - 1;
if (!e.cancelled) {
// only release message, notify and decrement if it was not canceled before.
safeRelease(msg);
safeSuccess(promise); // 这里, 调用了promise的trySuccess()方法, 触发Listener
decrementPendingOutboundBytes(size);
}
return true;
}
2、如果没写完 incompleteWrite(setOpWrite);setOpWrite这个是这次实际发出去的字节,如果为0,代表socket无法写数据,此时应该在通道上注册一个写事件,然后结束此次写的动作,避免浪费cpu。下次selector时,如果此通道可写了再写。如果setOpWrite不为0,代表socket可写,但是为了避免这个线程长时间被占用,把剩余的下次再写。
protected final void incompleteWrite(boolean setOpWrite) {
// socket不可写
if (setOpWrite) {
setOpWrite();
} else {
// Schedule flush again later so other tasks can be picked up in the meantime
Runnable flushTask = this.flushTask;
if (flushTask == null) {
flushTask = this.flushTask = new Runnable() {
@Override
public void run() {
flush();
}
};
}
eventLoop().execute(flushTask);
}
}