接着上篇NioEventLoop遗留的问题,Accept事件和Read事件。
当前selectionKey发生的事件是SelectionKey.OP_ACCEPT或者SelectionKey.OP_READ,执行unsafe的read方法。
说到unsafe,其实在NioServerSocketChannel中提到过,Unsafe是定义在Channel中的接口,看看它的实现:
在PerocessKey方法中的定义是这样的
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
两个不同的实现NioByteUnsafe和NioMessageUnsafe分别是为NioSocketChannel和NioServerSocketChannel服务的,当然前者的实现分别是后者或者父类的内部类。那么他是何时与Channel绑定的?以NioServerSocketChannel为例,实例化的时候,调用了父类的构造方法:
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
newUnsafe()的实现分别在不同的Unsafe实现中。根据多态来实现不同类型Channel的相同API,说白了就是NioMessageUnsafe的read处理Accept事件,NioByteUnsafe的处理Read事件,但是入口都是read。
下面我们开始分析accept事件的处理。
read方法定义在NioMessageUnsafe类中,代码如下:
private final class NioMessageUnsafe extends AbstractNioUnsafe {
private final List<Object> readBuf = new ArrayList<Object>();
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
//...
} finally {
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
doReadMessages 方法的作用是通过 ServerSocketChannel 的 accept 方法获取到 Tcp 连接,然后封装成 Netty 的 NioSocketChannel 对象
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
//省略异常捕获代码
SocketChannel ch = SocketUtils.accept(javaChannel());
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
return 0;
}
ServerSocketChannel有阻塞和非阻塞两种模式:
阻塞模式:ServerSocketChannel.accept() 方法监听新进来的连接,当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。阻塞模式下, accept()方法会一直阻塞到有新连接到达。
非阻塞模式:accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null。
在NioServerSocketChannel的构造函数分析中,我们知道,其通过ch.configureBlocking(false);语句设置当前的ServerSocketChannel为非阻塞的。
SocketChannel的注册启动是通过ServerBootStrap中的ServerBootStrapAcceptor类来实现的,它是在ServerBootStrap启动过程中添加到ServerSocketChannel关联的PipeLine中的一个Inboundhandler。接下单独一节分析PipeLine中Accept事件的传输过程,fireChannelRead方法的实现在DefaultChannelPipeline类中。
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
内部调用了静态方法invokeChannelRead并传入了 head 节点,msg 就是 容器中的 Socket,该静态方法内部调用了AbstractChannelHandlerContext的invokeChannelRegistered(),注意这个方法和是普通方法并且没有参数,此方法内部接着调用了和context绑定的handler的channelRegistered方法,当然首先是head节点。几乎所有的Inbound事件都是一个模式,在Head节点调用AbstractChannelHandlerContext的fireChannelRegistered,这个方法的内部首先是寻找下一个Inbound类型的Context,然后Invoke对应Handler的ChannelRead方法。
Head节点的ChannelRead方法很直观易懂,这里我们重点分析ServerBootStarpAcceptor的ChannelRead方法。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
//添加childHandler到NioSocketChannel的pipeline。
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
首先添加childHandler到NioSocketChannel的pipeline。childGroup.register(child)将NioSocketChannel注册到work的eventLoop中,这个过程和NioServerSocketChannel注册到boss的eventLoop的过程一样。这个注册过程主要有三个重点:
.childHandler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new Someonehandller());
}
fireChannelRegister会最中会执行initChannel方法,把Someonehandller添加到SocketChannel的pipeline中。并且把这个匿名类移除。这块逻辑前面分析过,这里就不在赘述
4. 启动EventLoop的线程,开始等待读事件的到来
最后在此Read方法中没有调用fireChannelRead方法,因此不会传到Tial节点,至此Accept事件的处理就结束了。