写数据是NIO Channel实现的另一个比较复杂的功能。每一个channel都有一个outboundBuffer,这是一个输出缓冲区。当调用channel的write方法写数据时,这个数据被一系列ChannelOutboundHandler处理之后,它被放进这个缓冲区中,并没有真正把数据写到socket channel中。然后再调用channel的flush方法,flush会把outboundBuffer中数据真正写到socket channel。正常情况下flush之后,数据已经真正写完了。但使用Selector加非阻塞socket的方式写数据,让写操作变得复杂了。操作系统为每个socket维护了一个数据发送缓冲区,它的长度SO_SNDBUF, 每次发送数据,先把数据写到这个缓冲区中,操作系统负责把这个发送缓冲区中的数据发送出去,并清理这个缓冲区。当向缓冲区写的速率大于系统的发送速率时,它会被填满,在非阻塞模式下的表现为: 调用socket的write方法写入长度为n数据,实际写入的数据长度m的范围是:0=<m<n。这个时候还剩下长度为n-m的数据没有写入到socket,而数据必须以正确的顺序完整地写入到socket中。 outboundBuffer正是为解决这个问题而设计的,没写进socket的剩余数据会以正确的顺序保存在outboundBuffer中,当发送缓冲区中有空间可以写时,可以从outboundBuffer中取出剩余的数据继续写入到socket中。

  

  Channel write实现: 把数据写到outboundBuffer中

  write调用栈:

 io.netty.channel.AbstractChannel#write(java.lang.Object)
io.netty.channel.DefaultChannelPipeline#write(java.lang.Object)
io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object)
io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, boolean, io.netty.channel.ChannelPromise)
io.netty.channel.AbstractChannelHandlerContext#invokeWrite
io.netty.channel.DefaultChannelPipeline.HeadContext#write
io.netty.channel.AbstractChannel.AbstractUnsafe#write

  write的主要逻辑在io.netty.channel.AbstractChannel.AbstractUnsafe#write中实现,这个方法把要写的数据msg对象放到outboundBuffer中。在执行close时,netty不希望有希望写新的数据,避免引起不可预料的错误,因此会把outboundBuffer置为null。这里在向outboundBuffer写数据之前会把对它进行检查,如果是null就抛出错误。下面是这个write方法的实现。

 @Override
public final void write(Object msg, ChannelPromise promise) {
assertEventLoop(); ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
ReferenceCountUtil.release(msg);
return;
} int size;
try {
msg = filterOutboundMessage(msg);
size = pipeline.estimatorHandle().size(msg);
if (size < 0) {
size = 0;
}
} catch (Throwable t) {
safeSetFailure(promise, t);
ReferenceCountUtil.release(msg);
return;
} outboundBuffer.addMessage(msg, size, promise);
}

  第5-9行,对outboudBuffer进行检查,如果是null抛出错误。这个里有个小细节,用一个局部变量引用outboundBuffer,避免由其他线程对this.outboundBuffer置空引发错误。

  14行,调用filterOutboundMessage对msg进行过滤。这是一个protected方法,默认实现是什么都没做,返回输入的msg参数。子类可以覆盖这个方法,把msg转换成期望的类型。

  15行,计算msg的长度。

  25行,把放入到outboundBuffer中。

  

  Channel flush实现:把数据真正写到channel

  flush调用栈:

 io.netty.channel.AbstractChannel#flush
io.netty.channel.DefaultChannelPipeline#flush
io.netty.channel.AbstractChannelHandlerContext#flush
io.netty.channel.AbstractChannelHandlerContext#invokeFlush
io.netty.channel.DefaultChannelPipeline.HeadContext#flush
io.netty.channel.AbstractChannel.AbstractUnsafe#flush
io.netty.channel.AbstractChannel.AbstractUnsafe#flush0
io.netty.channel.socket.nio.NioSocketChannel#doWrite
io.netty.channel.nio.AbstractNioByteChannel#doWrite
io.netty.channel.socket.nio.NioSocketChannel#doWriteBytes

  以上是io.netty.channel.socket.nio.NioSocketChannel的flush调用栈,对于io.netty.channel.socket.nio.NioDatagramChannel来说,从第8行开始变得不同:

 io.netty.channel.AbstractChannel.AbstractUnsafe#flush0
8 io.netty.channel.nio.AbstractNioMessageChannel#doWrite
9 io.netty.channel.socket.nio.NioDatagramChannel#doWriteMessage

  

  把Byte数据流写入channel

  io.netty.channel.socket.nio.NioSocketChannel#doWrite是Byte数据流的写逻辑,io.netty.channel.nio.AbstractNioByteChannel#doWrite也是,这两者不同的地方在于前者是在outboundBuffer可以转换成java.nio.ByteBuffer的情况下执行,后者是在outboundBuffer中的msg是ByteBuf或FileRegin类型时执行。除此之外其他逻辑都一样:

  1. 尽量把outboundBuffer中的数据写到channel中。
  2. 如果channel无法写入数据,在channel的SelectionKey上注册OP_WRITE事件,等channel可写的时候再继续写入。
  3. 如写入次数超过限制,把flush操作包装成task放到eventLoop排队,等待再次执行。

  下面来看看io.netty.channel.socket.nio.NioSocketChannel#doWrite的实现代码:

 @Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
for (;;) {
int size = in.size();
if (size == 0) {
// All written so clear OP_WRITE
clearOpWrite();
break;
}
long writtenBytes = 0;
boolean done = false;
boolean setOpWrite = false; // Ensure the pending writes are made of ByteBufs only.
ByteBuffer[] nioBuffers = in.nioBuffers();
int nioBufferCnt = in.nioBufferCount();
long expectedWrittenBytes = in.nioBufferSize();
SocketChannel ch = javaChannel(); // Always us nioBuffers() to workaround data-corruption.
// See https://github.com/netty/netty/issues/2761
switch (nioBufferCnt) {
case 0:
// We have something else beside ByteBuffers to write so fallback to normal writes.
super.doWrite(in);
return;
case 1:
// Only one ByteBuf so use non-gathering write
ByteBuffer nioBuffer = nioBuffers[0];
for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) {
final int localWrittenBytes = ch.write(nioBuffer);
if (localWrittenBytes == 0) {
setOpWrite = true;
break;
}
expectedWrittenBytes -= localWrittenBytes;
writtenBytes += localWrittenBytes;
if (expectedWrittenBytes == 0) {
done = true;
break;
}
}
break;
default:
for (int i = config().getWriteSpinCount() - 1; i >= 0; i --) {
final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt);
if (localWrittenBytes == 0) {
setOpWrite = true;
break;
}
expectedWrittenBytes -= localWrittenBytes;
writtenBytes += localWrittenBytes;
if (expectedWrittenBytes == 0) {
done = true;
break;
}
}
break;
} // Release the fully written buffers, and update the indexes of the partially written buffer.
in.removeBytes(writtenBytes); if (!done) {
// Did not write all buffers completely.
incompleteWrite(setOpWrite);
break;
}
}
}

  第5-7行,如果outboundBuffer中已经没有数据了,调用clearOpWrite方法清除channel SelectionKey上的OP_WRITE事件。

  第15-17行,把outboundBuffer转换成ByteBuffer类型,并得到数据长度。

  25行,outboundBuffer不能转换成ByteBuffer, 调用io.netty.channel.nio.AbstractNioByteChannel#doWrite执行写操作。

  29-42,45-57的逻辑基本已经,都是尽量把ByteBuffer中的数据写到channel中,满足下列条件中的任意一个时,结束本次写操作:

    1. ByteBuffer中的数据已经写完,正常结束。

    2. channel已经不能写入数据,需要在channel可以写是继续执行写操作。

    3. 者超过channel config中写入次数限制,需要选择合适的实际继续执行写操作。

  62行,把已经写入到channel的数据从outboundBuffer中删除。

  64-66行, 如果数据没写完,调用incompleteWrite处理没写完的情况。当setOpWrite==true时,在channel的SelectionKey上设置OP_WRITE事件,等eventLoop触发这个事件时再继续执行flush操作。否则,把flush包装成task放到eventLoop中排队执行。

  当NioEventLoop检测到OP_WRITE事件时,会调用processSelectedKey方法处理:

if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}

  forceFlush的调用栈如下:

 io.netty.channel.nio.AbstractNioChannel.AbstractNioUnsafe#forceFlush
io.netty.channel.AbstractChannel.AbstractUnsafe#flush0
io.netty.channel.socket.nio.NioSocketChannel#doWrite
io.netty.channel.nio.AbstractNioByteChannel#doWrite
io.netty.channel.socket.nio.NioSocketChannel#doWriteBytes

  把数据写入UDP类型的channel

  io.netty.channel.nio.AbstractNioMessageChannel#doWrite是数据报的写逻辑。相较于Byte流类型的数据,数据报的写逻辑简单一些。它只是把outboundBuffer中的数据报依次写入到channel中,如果channel写满了,在channel的SelectionKey上设置OP_WRITE事件随后退出,其后OP_WRITE事件处理逻辑和Byte流写逻辑一样。 真正的写操作在io.netty.channel.socket.nio.NioDatagramChannel#doWriteMessage中实现,这个方法的实现如下:

 @Override
protected boolean doWriteMessage(Object msg, ChannelOutboundBuffer in) throws Exception {
final SocketAddress remoteAddress;
final ByteBuf data;
if (msg instanceof AddressedEnvelope) {
@SuppressWarnings("unchecked")
AddressedEnvelope<ByteBuf, SocketAddress> envelope = (AddressedEnvelope<ByteBuf, SocketAddress>) msg;
remoteAddress = envelope.recipient();
data = envelope.content();
} else {
data = (ByteBuf) msg;
remoteAddress = null;
} final int dataLen = data.readableBytes();
if (dataLen == 0) {
return true;
} final ByteBuffer nioData = data.internalNioBuffer(data.readerIndex(), dataLen);
final int writtenBytes;
if (remoteAddress != null) {
writtenBytes = javaChannel().send(nioData, remoteAddress);
} else {
writtenBytes = javaChannel().write(nioData);
}
return writtenBytes > 0;
}

  5-9行,处理AddressedEnvelope类型的数据报,得到数据报的远程地址和数据。

  10-12行,发送的是一个ByteBuf。没有指定远程地址。这种情况下需要先调用channel的connect方法。

  20-26行,分别针对两种情况发送数据报. 23行指定了远程地址,25行没有指定远程地址,但调用过了connect方法。

  

netty源码解解析(4.0)-15 Channel NIO实现:写数据的更多相关文章

  1. netty源码解解析(4.0)-14 Channel NIO实现:读取数据

     本章分析Nio Channel的数据读取功能的实现. Channel读取数据需要Channel和ChannelHandler配合使用,netty设计数据读取功能包括三个要素:Channel, Eve ...

  2. netty源码解解析(4.0)-11 Channel NIO实现-概览

      结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ...

  3. netty源码解解析(4.0)-12 Channel NIO实现:channel初始化

    创建一个channel实例,并把它register到eventLoopGroup中之后,这个channel然后处于inactive状态,仍然是不可用的.只有在bind或connect方法调用成功之后才 ...

  4. netty源码解解析(4.0)-13 Channel NIO实现: 关闭和清理

    Channel提供了3个方法用来实现关闭清理功能:disconnect,close,deregister.本章重点分析这个3个方法的功能的NIO实现. disconnect实现: 断开连接 disco ...

  5. netty源码解解析(4.0)-3 Channel的抽象实现

    AbstractChannel和AbstractUnsafe抽象类 io.netty.channel.AbstractChannel 从本章开始,会有大量的篇幅涉及到代码分析.为了能够清晰简洁的地说明 ...

  6. netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现

    io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ...

  7. netty源码解解析(4.0)-18 ChannelHandler: codec--编解码框架

    编解码框架和一些常用的实现位于io.netty.handler.codec包中. 编解码框架包含两部分:Byte流和特定类型数据之间的编解码,也叫序列化和反序列化.不类型数据之间的转换. 下图是编解码 ...

  8. netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理

    事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ...

  9. netty源码解解析(4.0)-20 ChannelHandler: 自己实现一个自定义协议的服务器和客户端

    本章不会直接分析Netty源码,而是通过使用Netty的能力实现一个自定义协议的服务器和客户端.通过这样的实践,可以更深刻地理解Netty的相关代码,同时可以了解,在设计实现自定义协议的过程中需要解决 ...

随机推荐

  1. java内存性能调优编码注意

    1.没有必要时请不用使用静态变量 使用Java的开发者都知道,当某个对象被定义为stataic变量所引用,这个对象所占有的内存将不会被回收.有时,开发者会将经常调用的对象或者变量定义为static,以 ...

  2. 数据库-mysql语句-查-WEB服务器

    (1)MySQL中的查询 (2)WEB服务器 Order:  订单.排序 Set:设置.集合 Table:表.桌子 1.MySQL中的简单查询 —— 查询结果的排序 示例:查询出所有员工信息,要求按工 ...

  3. Office365 Manager Plus之报表

    Office365 Manager Plus之报表 也许您刚刚开始使用Office 365,对它的各个组件还有很多疑问,如何快速掌握Office 365各种服务的用法?如何管理邮箱?如何监控邮件流量? ...

  4. Python开发——1.基础知识

    一.开发 开发语言分为高级语言和低级语言 高级语言:Python.Java.PHP.C++.C#.GO.Ruby等:低级语言:C.汇编语言. 高级语言对应的是字节码,是将代码编译成字节码,然后交给机器 ...

  5. Beta冲刺 (5/7)

    Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 组员1(组长)柯奇豪 过去两天完成了哪些任务 共享编辑文章的后端数据处理 部分代码 ...

  6. google guava Multimap的学习介绍

    1.https://blog.csdn.net/gongxinju/article/details/53634434

  7. windows 批处理语言学习

    程序员应该根植于心的一个理念是:重复的工作交给代码.windows上的批处理脚本就是这种理念的体现. 批处理bat能做的事很多,自动配置vs工程中的代码依赖环境,调用其它程序处理数据.自动编译代码等等 ...

  8. 二叉搜索树的平衡--AVL树和树的旋转

    二叉搜索树只有保持平衡时其查找效率才会高. 要保持二叉搜索树的平衡不是一件易事.不过还是有一些非常经典的办法可以做到,其中最好的方法就是将二叉搜索树实现为AVL树. AVL树得名于它的发明者 G.M. ...

  9. winform判断一个事件是否已经绑定了事件处理函数

    public static class ComponentHelper<T> where T : Control { public static bool HaveEventHandler ...

  10. 收藏的blog

    https://www.cnblogs.com/xifengxiaoma/tag/vue/ https://www.cnblogs.com/xifengxiaoma/p/9400200.html