好久没有看netty了,今天无意中一个东西用到了netty,发现原来新版本的final都已经出来了。。
但是我以前看的都是4.0的rc版本。。。瞬间觉得好郁闷。。。
不过起码以前看过了netty的代码,它的主要的脉络还是一样的。。
在这里还要赞一下final版本,它对数据的处理变得更简单了,,看起来更清晰。。。。
这里就拿数据的读取来举例子吧:
(1)还是先从nioeventloop中获取有数据可以读的channel
(2)利用channel的unsafe对象来将数据读取出来
(3)调用当前channel的pipeline上的fireChannelRead方法,用于从pipeline上由前向后的调用handler,并用合适的handler来处理刚刚读取的数据,这里所谓的合适就是inboundhandler,最新版本不再分byte类型和message类型了,这个其实思路比老版本更清晰。。。
好了,那么接下来还是进入老话题,开始看进入源代码,不过要记住这里看的已经是最新版本的源代码了,那么就从数据读取的入口开始,也就是processSelectedKey函数:
private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read(); //调用当前channel的unsafe对象来读取数据
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
} catch (CancelledKeyException e) {
unsafe.close(unsafe.voidPromise());
}
}
其实这部分代码没有太大的变化,直接就是获取当前的channel,然后调用该channel的unsafe对象的read函数来进行下一步的处理就是了。。。。
好吧,那么接下来我们来看看unsafe对象的read方法做了什么工作吧:
private final class NioByteUnsafe extends AbstractNioUnsafe {
private RecvByteBufAllocator.Handle allocHandle;
@Override
public void read() {
assert eventLoop().inEventLoop();
final SelectionKey key = selectionKey();
final ChannelConfig config = config();
if (!config.isAutoRead()) {
int interestOps = key.interestOps();
if ((interestOps & readInterestOp) != 0) {
// only remove readInterestOp if needed
key.interestOps(interestOps & ~readInterestOp);
}
}
//当前channel的pipeline
final ChannelPipeline pipeline = pipeline();
RecvByteBufAllocator.Handle allocHandle = this.allocHandle;
if (allocHandle == null) {
this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();
}
final ByteBufAllocator allocator = config.getAllocator(); //buffer的allocater
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
boolean closed = false;
Throwable exception = null;
ByteBuf byteBuf = null;
int messages = 0;
try {
for (;;) {
byteBuf = allocHandle.allocate(allocator);
int localReadAmount = doReadBytes(byteBuf); //将数据读进来
if (localReadAmount == 0) {
byteBuf.release();
byteBuf = null;
break;
}
if (localReadAmount < 0) {
closed = true;
byteBuf.release();
byteBuf = null;
break;
}
pipeline.fireChannelRead(byteBuf); //调用pipeline上面的handler来处理数据
allocHandle.record(localReadAmount); //记录已经读取的数据量
byteBuf = null;
if (++ messages == maxMessagesPerRead) {
break;
}
}
} catch (Throwable t) {
exception = t;
} finally {
if (byteBuf != null) {
if (byteBuf.isReadable()) {
pipeline.fireChannelRead(byteBuf);
} else {
byteBuf.release();
}
}
pipeline.fireChannelReadComplete(); //表示读取已经完成了。。。
if (exception != null) {
if (exception instanceof IOException) {
closed = true;
}
pipeline().fireExceptionCaught(exception);
}
if (closed) { //有异常了,那就需要关掉
setInputShutdown();
if (isOpen()) {
if (Boolean.TRUE.equals(config().getOption(ChannelOption.ALLOW_HALF_CLOSURE))) {
key.interestOps(key.interestOps() & ~readInterestOp);
pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
} else {
close(voidPromise());
}
}
}
}
}
}
这部分就出现了与老版本不太一样的地方吧,读取了数据之后,在unsafe对象里面调用pipeline的fireChannelRead来处理读取进来的数据,记得在老版本里面实在外面调用这个方法的吧,。。。
不管了,都是小事情,那么下面来看看pipeline上上面有什么更新么。。
//表示当前channel有数据读了进来,保存在msg里面,一般情况下它就是一个bytebuf
//从头开始向后找inboundhandler来处理数据
public ChannelPipeline fireChannelRead(Object msg) {
head.fireChannelRead(msg);
return this;
}
到这里并没有什么好多说的,直接来看fireChannelRead方法吧:
public ChannelHandlerContext fireChannelRead(final Object msg) {
if (msg == null) {
throw new NullPointerException("msg");
}
//找到第一个inboundhandler就可以了,这里不再分byte和message类型什的
final DefaultChannelHandlerContext next = findContextInbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRead(msg); //调用方法来处理数据
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRead(msg);
}
});
}
return this;
}
这里findcontextinbound的时候,就不会再区分什么byte类型或者message类型的handler了,统一就一种类型,那就是object,其实这样一来反而更简单了,没有以前那么繁琐,开发人员难道不知道自己要处理的数据类型是什么么。。以前真的有点画蛇添足的感觉。。。而且也没有了什么buff用于存储这些数据,从开始就只有这一个数据,你爱怎么处理怎么处理
private void invokeChannelRead(Object msg) {
try {
((ChannelInboundHandler) handler).channelRead(this, msg);
} catch (Throwable t) {
notifyHandlerException(t);
}
}
另外这里也有跟老版本方法有不同的地方,这里是channelRead方法,直接来处理这个数据,而以前是inboundbufferupdated方法,听名字就觉得现在的方法更清晰。。。
也就是说我们现在的handler里面也将会使用channelRead方法了。。。。
到这里整个数据的读取流程就差不多了。。。
总的来说,主要的核心部分与老版本还是保持一致的,不过新版本的netty变的更简洁了。。赞一下。。