新連接的建立
可以分爲三個步驟
1.檢測到有新的連接
2.將新的連接註冊到worker線程組
3.註冊新連接的讀事件
檢測到有新連接的接入
我們已經知道,當服務端啓動後,服務端channel已經註冊到boss reactor線程中,reactor不斷檢測有新的事件,直到檢測出有accept事件發生
NioEventLoop.java
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
上面這段代碼表示boss reactor線程已經輪訓到SelectionKey.OP_ACCEPT事件,說明有新連接接入,此時將調用Unsafe進行實際操作
NioMessageUnsafe
@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();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
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 (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
}
可以調用doReadMessages方法不斷的讀取消息,用readBuf做爲容器,這裏是讀取一個個連接,然後調用pipeline.fireChannelRead(),將每條連接通過pipeline的handler,之後便是清理容器,觸發pipeline.fireChannelReadComplete(),整個過程清晰明瞭。
下面分析一下doReadMessages
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
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;
}
public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<SocketChannel>() {
@Override
public SocketChannel run() throws IOException {
return serverSocketChannel.accept();
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}
這裏通過serverSocketChannel.accept()標準的jdk的玩法,返回客戶端的channel,然後在初始化NioSocketChannel,初始化類似於初始化NioServerSocketChannel,不在細講。
pipeline.fireChannelRead(NioSocketChannel);
在服務端啓動的時候,在處理新連接的pipeline中,已經自動添加了一個handler爲ServerBootstrapAcceptor,並且已經將用戶代碼中設置的一系列的參數傳入了構造函數,接下來,我們就來看下ServerBootstrapAcceptor
@Override
@SuppressWarnings("unchecked")
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
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);
}
}
前面的pipeline.fireChannelRead(NioSocketChannel();最終通過head-unsafe-ServerBootstrapAcceptor的調用到channelRead()方法的,而channelRead的msg就是之前我們new NioSocketChanel,將用戶設置的childChannel,添加到pipeline中。目前pipeline中對應的handler有head->ChannelInitializer->tail,這個後面會提到
childGroup.register(child)
private void register0(ChannelPromise promise) {
try {
// 檢查通道是否仍然打開,因爲它可以在寄存器的平均時間內關閉
// 調用在eventLoop之外
//promise=DefaultChannelPromise
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
//調用NioServerSocketChannel 通過反射創建出來nio底層channel的register方法 選擇器看不同操作系統
doRegister();
neverRegistered = false;
registered = true;
// 確保在實際通知承諾之前調用handlerAdded(…)。這是需要的
// 用戶可能已經通過ChannelFutureListener中的管道觸發事件。
//會執行handlerAdded方法
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
//會執行channelRegistered
pipeline.fireChannelRegistered();
// 只有當通道從未被註冊時,才激活該通道。這可以防止解僱
// 如果取消註冊並重新註冊通道,則多個通道將激活。
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// 這個通道之前已經註冊,並設置了autoRead()。這意味着我們需要開始讀取
// 這樣我們就可以處理入站數據。
//
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// 直接關閉通道,避免FD泄漏。
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
和服務端啓動過程一樣,先是調用doRegister();做真正的註冊流程,如下
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
eventLoop().selectNow();
selected = true;
} else {
throw e;
}
}
}
}
執行完綁定會執行pipeline.invokeHandlerAddedIfNeeded();
最終會調用ChannelInitializer
的 handlerAdded
方法
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
initChannel(ctx);
}
}