本章節(jié)分析服務(wù)端如何accept客戶端的connect請求。
在Netty源碼分析之NioEventLoop章節(jié)中,已經(jīng)分析了NioEventLoop的工作機制,當(dāng)有客戶端connect請求,selector可以返回其對應(yīng)的SelectionKey,方法processSelectedKeys進行后續(xù)的處理。
private void processSelectedKeys() {
if (selectedKeys != null) {
processSelectedKeysOptimized(selectedKeys.flip());
} else {
processSelectedKeysPlain(selector.selectedKeys());
}
}
默認(rèn)采用優(yōu)化過的SelectedSelectionKeySet保存有事件發(fā)生的selectedKey。
1、SelectedSelectionKeySet內(nèi)部使用兩個大小為1024的SelectionKey數(shù)組keysA和keysB保存selectedKey。
2、把SelectedSelectionKeySet實例映射到selector的原生selectedKeys和publicSelectedKeys。
private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
for (int i = 0;; i ++) {
final SelectionKey k = selectedKeys[i];
if (k == null) {
break;
}
// null out entry in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
selectedKeys[i] = null;
final Object a = k.attachment();
if (a instanceof AbstractNioChannel) {
processSelectedKey(k, (AbstractNioChannel) a);
} else {
@SuppressWarnings("unchecked")
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
processSelectedKey(k, task);
}
if (needsToSelectAgain) {
// null out entries in the array to allow to have it GC'ed once the Channel close
// See https://github.com/netty/netty/issues/2363
for (;;) {
i++;
if (selectedKeys[i] == null) {
break;
}
selectedKeys[i] = null;
}
selectAgain();
// Need to flip the optimized selectedKeys to get the right reference to the array
// and reset the index to -1 which will then set to 0 on the for loop
// to start over again.
//
// See https://github.com/netty/netty/issues/1523
selectedKeys = this.selectedKeys.flip();
i = -1;
}
}
}
因為selector的I/O多路復(fù)用機制,一次可以返回多個selectedKey,所以要用for循環(huán)處理全部selectionKey。
假設(shè)這時有請求進來,selectedKeys中就存在一個selectionKey,這塊邏輯不清楚的可以回頭看看深入淺出Nio Socket。
1、通過k.attachment()可以獲取ServerSocketChannel注冊時綁定上去的附件,其實這個附件就是ServerSocketChannel自身。
2、如果selectedKey的附件是AbstractNioChannel類型的,執(zhí)行processSelectedKey(k, (AbstractNioChannel) a)方法進行下一步操作。
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();
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
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 ignored) {
unsafe.close(unsafe.voidPromise());
}
}
1、獲取ServerSocketChannel的unsafe對象。
2、當(dāng)前selectionKey發(fā)生的事件是SelectionKey.OP_ACCEPT,執(zhí)行unsafe的read方法。
該read方法定義在NioMessageUnsafe類中:
private final List<Object> readBuf = new ArrayList<Object>();
@Override
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
if (!config.isAutoRead() && !isReadPending()) {
// ChannelConfig.setAutoRead(false) was called in the meantime
removeReadOp();
return;
}
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
final ChannelPipeline pipeline = pipeline();
boolean closed = false;
Throwable exception = null;
try {
try {
for (;;) {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
// stop reading and remove op
if (!config.isAutoRead()) {
break;
}
if (readBuf.size() >= maxMessagesPerRead) {
break;
}
}
} catch (Throwable t) {
exception = t;
}
setReadPending(false);
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
pipeline.fireChannelReadComplete();
if (exception != null) {
if (exception instanceof IOException && !(exception instanceof PortUnreachableException)) {
// ServerChannel should not be closed even on IOException because it can often continue
// accepting incoming connections. (e.g. too many open files)
closed = !(AbstractNioMessageChannel.this instanceof ServerChannel);
}
pipeline.fireExceptionCaught(exception);
}
if (closed) {
if (isOpen()) {
close(voidPromise());
}
}
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!config.isAutoRead() && !isReadPending()) {
removeReadOp();
}
}
}
1、readBuf 用來保存客戶端NioSocketChannel,默認(rèn)一次不超過16個。
2、方法doReadMessages進行處理ServerSocketChannel的accept操作。
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = javaChannel().accept();
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
1、javaChannel()返回NioServerSocketChannel對應(yīng)的ServerSocketChannel。
2、ServerSocketChannel.accept返回客戶端的socketChannel 。
3、把 NioServerSocketChannel 和 socketChannel 封裝成 NioSocketChannel,并緩存到readBuf。
4、遍歷redBuf中的NioSocketChannel,觸發(fā)各自pipeline的ChannelRead事件,從pipeline的head開始遍歷,最終執(zhí)行ServerBootstrapAcceptor的channelRead方法。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
for (Entry<ChannelOption<?>, Object> e: childOptions) {
try {
if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {
logger.warn("Unknown channel option: " + e);
}
} catch (Throwable t) {
logger.warn("Failed to set a channel option: " + child, t);
}
}
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);
}
}
1、child.pipeline().addLast(childHandler)添加childHandler到NioSocketChannel的pipeline。
其中childHandler是通過ServerBootstrap的childHandler方法進行配置的,和NioServerSocketChannel類似,NioSocketChannel在注冊到selector后會觸發(fā)其pipeline的fireChannelRegistered方法,并執(zhí)行initChannel方法,為NioSocketChannel的pipeline添加更多自定義的handler,進行業(yè)務(wù)處理。
2、childGroup.register(child)將NioSocketChannel注冊到work的eventLoop中,這個過程和NioServerSocketChannel注冊到boss的eventLoop的過程一樣,最終由work線程對應(yīng)的selector進行read事件的監(jiān)聽。
當(dāng)readBuf中緩存的NioSocketChannel都處理完成后,清空readBuf,并觸發(fā)ChannelReadComplete。
到此為止,一次accept流程已經(jīng)執(zhí)行完。
END。
我是占小狼。
在魔都艱苦奮斗,白天是上班族,晚上是知識服務(wù)工作者。
如果讀完覺得有收獲的話,記得關(guān)注和點贊哦。
非要打賞的話,我也是不會拒絕的。