Netty writeAndFlush() 流程与异步
Netty writeAndFlush()方法分为两步, 先 write 再 flush

@Override
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
DefaultChannelHandlerContext next;
next = findContextOutbound(MASK_WRITE);
ReferenceCountUtil.touch(msg, next);
next.invoker.invokeWrite(next, msg, promise);
next = findContextOutbound(MASK_FLUSH);
next.invoker.invokeFlush(next);
return promise;
}

以上是DefaultChannelHandlerContext中的writeAndFlush方法, 可见实际上是先调用了write, 然后调用flush
1. write
write方法从TailHandler开始, 穿过中间自定义的各种handler以后到达HeadHandler, 然后调用了HeadHandler的成员变量Unsafe的write
如下

@Override
public void write(Object msg, ChannelPromise promise) {
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
// If the outboundBuffer is null we know the channel was closed and so
// need to fail the future right away. If it is not null the handling of the rest
// will be done in flush0()
// See https://github.com/netty/netty/issues/2362
safeSetFailure(promise, CLOSED_CHANNEL_EXCEPTION);
// release message now to prevent resource-leak
ReferenceCountUtil.release(msg);
return;
}
outboundBuffer.addMessage(msg, promise);
}

最终会把需要write的msg和promise(也就是一个future, 我们拿到手的future, 添加Listener的也是这个)放入到outboundBuffer中, msg和promise在outboundBuffer中的存在形式是一个自定义的结构体Entry.
也就是说调用write方法实际上并不是真的将消息写出去, 而是将消息和此次操作的promise放入到了一个队列中
2. flush
flush也是从Tail开始, 最后到Head, 最终调用的也是Head里的unsafe的flush0()方法, 然后flush0()里再调用doWrite()方法, 如下:

@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));
}
}
}

红字部分就是最后将数据写出去的地方, 这里写数据最终调用的是 GatheringByteChannel 的 write() 方法, 这是个原生Java接口, 具体实现依赖于实现这个接口的Java类, 例如会调用 NIO 的 SocketChannel 的write()方法, 至此, 实际写数据的过程出现了, SocketChannel可以运行在non-blocking模式, 也就是非阻塞异步模式, write数据会马上返回写入的数据数量 (并不一定是所有数据都写入成功, 对于是否写入了所有数据, Netty有自己的处理逻辑, 也就是上面代码中的红字的那段for循环, 具体参看下SocketChannel的javadoc和netty源码).
当所有数据写入SocketChannel成功, 开始调用in.remove(), 这个 in 就是第一步 1. write 里的那个 outboundBuffer, 他的类型是 ChannelOutboundBuffer, 代码如下:

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;
}

最后会调用Promise的notifyListeners()操作, 触发Listener完成整个异步流程
---------
最后, 回到我们应用netty的时候的代码

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.writeAndFlush(new Object()).addListener(new ChannelFutureListener() { @Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// do sth
} else {
// do sth
}
}
});
}

这就是整个流程
最后提一下, Netty的AbstractNioChannel里封装了selectionKey, 在accept socket的时候, socket会被注册到eventLoop()的Selector, 这个selectionKey就会被赋值, 如下
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
在以后Selector的select()的时候, 则会通过这个key来获取到channel, 然后调用 AbstractChannel 里的 DefaultChannelPipeline 来触发 Handler 的 connect, read, write 等等事件...
Netty writeAndFlush() 流程与异步的更多相关文章
- netty 服务器端流程调度Flow笔记
create NioEventLoopGroup Instance 一.NioServerSocketChannel init note:Initializing ChannelConfig crea ...
- Netty执行流程分析与重要组件介绍
一.环境搭建 创建工程,引入Netty依赖 二.基于Netty的请求响应Demo 1.TestHttpServerHandle 处理器.读取客户端发送过来的请求,并且向客户端返回hello worl ...
- Netty实现的一个异步Socket代码
本人写的一个使用Netty实现的一个异步Socket代码 package test.core.nio; import com.google.common.util.concurrent.ThreadF ...
- Netty源码分析第1章(Netty启动流程)---->第3节: 服务端channel初始化
Netty源码分析第一章:Netty启动流程 第三节:服务端channel初始化 回顾上一小节的initAndRegister()方法: final ChannelFuture initAndRe ...
- Netty源码分析第1章(Netty启动流程)---->第4节: 注册多路复用
Netty源码分析第一章:Netty启动流程 第四节:注册多路复用 回顾下以上的小节, 我们知道了channel的的创建和初始化过程, 那么channel是如何注册到selector中的呢?我们继 ...
- ES transport client底层是netty实现,netty本质上是异步方式,但是netty自身可以使用sync或者await(future超时机制)来实现类似同步调用!因此,ES transport client可以同步调用也可以异步(不过底层的socket必然是异步实现)
ES transport client底层是netty实现,netty本质上是异步方式,但是netty自身可以使用sync或者await(future超时机制)来实现类似同步调用! 因此,ES tra ...
- Netty启动流程剖析
编者注:Netty是Java领域有名的开源网络库,特点是高性能和高扩展性,因此很多流行的框架都是基于它来构建的,比如我们熟知的Dubbo.Rocketmq.Hadoop等,针对高性能RPC,一般都是基 ...
- Netty编码流程及WriteAndFlush()的实现
编码器的执行时机 首先, 我们想通过服务端,往客户端发送数据, 通常我们会调用ctx.writeAndFlush(数据)的方式, 入参位置的数据可能是基本数据类型,也可能对象 其次,编码器同样属于ha ...
- Netty推荐addListener回调异步执行
说明 Netty推荐使用addListener的方式来回调异步执行的结果,这种方式优于Future.get,能够更精确地把握异步执行结束的时间. 错误理解使用addListener的方式 代码如下: ...
随机推荐
- Linq to DataTable 左连接
所先创建2个DataTable并对其赋值,来进行模拟. DataTable dt1 = new DataTable(); dt1.Columns.Add("ID", typeof( ...
- 【BZOJ】1535: [POI2005]Sza-Template
题意 给一个串\(s(1 \le |s| \le 500000)\),求一个最长的串,使得这个串能覆盖整个串(可以重叠). 分析 首先这个串肯定是前缀也肯定是后缀. 题解 对串kmp后,建立\(fai ...
- URAL 1287. Mars Canals
题目链接 这题挺水,看懂了,就OK.卡了几下内存,还是卡过了. #include <iostream> #include <cstdio> #include <cstri ...
- PHP面向对象学习五 类中接口的应用
类中接口的应用 接口:一种成员属性全部为抽象的特殊抽象类,在程序中同为规范的作用 抽象类:1.类中至少有一个抽象方法.2.方法前需要加abstract 接口: 1.类中全部为抽象方法,抽象方法前不 ...
- Oracle 游标使用全解(转)
-- 声明游标:CURSOR cursor_name IS select_statement --For 循环游标 --(1)定义游标 --(2)定义游标变量 --(3)使用for循环来使用这个游标 ...
- CentOS上安装RabbitMQ3.6.X
RabbitMQ3.6.1的安装方法跟以前的版本有点不一样,我在网上找了很多资料,基本都是3.1左右的版本,而且安装过程很繁琐,所以我花了一下午 的时间研究如何实现最简安装.为了让大家少走弯路,就把安 ...
- 深入C#判断操作系统类型的总结详解(转载)
Windows操作系统的版本号一览 操作系统 PlatformID 主版本号 副版本号 Windows95 1 4 0 Windows98 1 4 10 WindowsMe ...
- Node.Js —— PM2介绍
pm2 是一个带有负载均衡功能的Node应用的进程管理器.当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完美的.它非常适合IaaS结构,但不要把它用于 ...
- 用GruntJS合并、压缩JS文件
为什么要合并.压缩你的JS文件? 一个项目开发完成我们总能发现有一堆js文件非常混乱. 一般在一个HTML文档加载的时候,浏览器会根据HTML代码从上到下读取所需要加 ...
- 已知树的前序、中序,求后序的java实现&已知树的后序、中序,求前序的java实现
public class Order { int findPosInInOrder(String str,String in,int position){ char c = str.charAt(po ...