開篇
在此前的章節http://www.lxweimin.com/p/e38844b145fb,我們介紹了Netty的引導啟動類,但是并沒有詳細解析系統啟動后的建鏈過程,本文將會就這一過程進行深入解析。
服務端
1、監聽初始化
服務端從doBind()
開始
private ChannelFuture doBind(final SocketAddress localAddress) {
// 完成初始化以及注冊操作
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// 忽略
}
}
話不多說,直接看initAndRegister()
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 創建NioServerSocketChannel
channel = channelFactory.newChannel();
// 初始化channel
init(channel);
} catch (Throwable t) {
// 忽略
}
// 注冊channel到EventLoop的selector
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
采用反射方式創建NioServerSocketChannel,會調到其無參構造函數,最終會調用
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
可以看到會將channel的readInterestOp初始化為SelectionKey.OP_ACCEPT,用于接受建鏈請求。channel創建好后,開始進行初始化
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
ChannelPipeline p = channel.pipeline();
// 這個childGroup就是在引導類中配置的workerGroup
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
}
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
// 將在啟動類中配置的channelHandler加入到NioServerSocketChannel對應的pipeline中
pipeline.addLast(handler);
}
// 將serverBootstrapAcceptor這個channelHandler加入到nioServerSocketChannel對應的pipeline中
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
ServerBootstrapAcceptor就是用來接收建鏈請求的。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
// 將啟動類中配置的childHandler添加到NioSocketChannel對應的pipeline中
child.pipeline().addLast(childHandler);
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
try {
// 將NioSocketChannel注冊到EventLoop的selector上
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);
}
}
好,我們返回到initAndRegister,至此NioServerSocketChannel已經創建并且初始化完畢,開始進行注冊操作
ChannelFuture regFuture = config().group().register(channel);
會調到MultithreadEventLoopGroup的register方法
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
// 從引導類中配置的EventLoop線程池中輪詢一個EventLoop
public EventLoop next() {
return (EventLoop) super.next();
}
接著往下,最終會調到AbstractChannel的register方法
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
// ...省略
// 由于當前eventLoop的thread還是null,所以這里返回false
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
進入到eventLoop的execute方法
private void execute(Runnable task, boolean immediate) {
boolean inEventLoop = inEventLoop();
addTask(task);
if (!inEventLoop) {
startThread();
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
}
if (reject) {
reject();
}
}
}
if (!addTaskWakesUp && immediate) {
wakeup(inEventLoop);
}
}
將task(也即register0方法)放入隊列,判斷當前線程非EventLoop自己的線程,開始創建線程
private void startThread() {
if (state == ST_NOT_STARTED) {
if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
boolean success = false;
try {
doStartThread();
success = true;
} finally {
if (!success) {
STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
}
}
}
}
}
private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
@Override
public void run() {
// 將EventLoop的線程綁定為當前線程
thread = Thread.currentThread();
if (interrupted) {
thread.interrupt();
}
boolean success = false;
updateLastExecutionTime();
try {
// EventLoop線程開始死循環,處理IO事件和自定義任務以及定時任務
SingleThreadEventExecutor.this.run();
success = true;
} catch (Throwable t) {
logger.warn("Unexpected exception from an event executor: ", t);
} finally {
}
調用executor的execute,會新創建一個線程執行task,這個線程就是EventLoop綁定的線程,在整個系統的生命周期范圍內,這種綁定關系都是固定的,不會發生變更。下面來分解NioEventLoop的run方法
case SelectStrategy.SELECT:
long curDeadlineNanos = nextScheduledTaskDeadlineNanos();
if (curDeadlineNanos == -1L) {
curDeadlineNanos = NONE; // nothing on the calendar
}
nextWakeupNanos.set(curDeadlineNanos);
try {
if (!hasTasks()) {
// 最終調用Java NIO的Selector的select方法處理IO事件
strategy = select(curDeadlineNanos);
}
} finally {
nextWakeupNanos.lazySet(AWAKE);
}
final long ioStartTime = System.nanoTime();
try {
// 處理準備好的IO事件
processSelectedKeys();
} finally {
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
// 從任務隊列取任務執行(還記得之前已經加入到隊列里的task,也即register0方法嗎?)
ranTasks = runAllTasks(0);
從任務隊列中取出任務,開始執行之前加入到任務隊列中的register0方法
private void register0(ChannelPromise promise) {
try {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
// 調用Java NIO方法,將channel注冊到selector
doRegister();
neverRegistered = false;
// 設置已經注冊的標志位
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// isActive是通過channel是open狀態并且已經綁定本地地址和端口來判斷的
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
// 忽略
}
}
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 調用Java NIO方法,將channel注冊到selector,注意這里的監聽事件設置的為0,目的是要讓
// channel執行完其他操作后,再設置OP_ACCEPT的監聽
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
}
// 忽略
}
}
注意這里的register將channel注冊到selector時,傳遞的監聽事件為0,意思是什么都不監聽,等channel完成其他操作后,再設置OP_ACCEPT監聽,那么是在哪里設置的呢?
- 如果isActive方法返回true,并且autoRead是true(默認是true),則會進入beginRead,最終執行doBeginRead,所以注冊完成后,就會設置OP_ACCEPT事件監聽。
protected void doBeginRead() throws Exception {
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
// 還記得之前創建NioServerSocketChannel時,設置readInterestOp為OP_ACCEPT嗎?將該事件加入
// 到NioServerSocketChannel對應的Selector的監聽序列中
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
-
如果isActive方法返回false,又是在哪里將OP_ACCEPT事件加到監聽序列的呢?因為Netty的很多任務都是封裝成task,放入隊列中,再由EventLoop線程從隊列中取出執行,所以直接從代碼中跳躍著找不是很方便,我們可以在doBeginRead方法打個斷點,看下調用棧
doBeginRead調用棧1.PNG
沒錯!我們在這里看到了熟悉的doBind0,這就是在本文的一開始的doBind方法中設置的監聽回調
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
也就是在上面的NioServerSocketChannel初始化并注冊成功后,會調到doBind0方法,在這個方法中將Channel的bind操作封裝成一個任務,加入到EventLoop的任務隊列
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
接下來,這個bind操作會在NioServerSocketChannel對應的pipeline上的outBound方向的channelHandler之間傳遞,也就是tail handler--> 其他的handler -> head headler,最終在head handler的bind方法中調用AbstractChannel的bind真正執行綁定操作
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
// 省略...
boolean wasActive = isActive();
try {
// 調用Java NIO接口真正執行綁定本地地址和端口的操作
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
// 觸發channelActive事件
pipeline.fireChannelActive();
}
});
}
safeSetSuccess(promise);
}
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
可以看到,在綁定操作執行完后,觸發了channelActive事件,這個事件首先會被head handler的channelActive方法處理
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
readIfIsAutoRead();
}
private void readIfIsAutoRead() {
if (channel.config().isAutoRead()) {
channel.read();
}
}
這個read操作同樣的跟上面的bind操作一樣,會在NioServerSocketChannel對應的pipeline上的outBound方向的channelHandler之間傳遞,也就是tail handler--> 其他的handler -> head headler,最終調到head handler的read方法
public void read(ChannelHandlerContext ctx) {
unsafe.beginRead();
}
public final void beginRead() {
assertEventLoop();
if (!isActive()) {
return;
}
try {
doBeginRead();
} catch (final Exception e) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireExceptionCaught(e);
}
});
close(voidPromise());
}
}
經過百轉千回終于走到doBeginRead方法了!有沒有被繞暈,哈哈。不要著急,現在只是完成了本地地址和端口的綁定,NioServerSocketChannel的selector開始監聽OP_ACCEPT事件,做好了接收建鏈請求的準備。
那么,當有客戶端建鏈請求進來時,又是怎樣處理的?接著往下看。
2、接收建鏈請求
回到EventLoop的run方法,當有IO事件被監聽到時,會調用processSelectedKeys方法
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
在processSelectedKey方法中,判斷是OP_ACCEPT事件,則會調用unsafe的read方法
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
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 {
// 調用NioServerSocketChannel的doReadMessages方法
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;
}
在NioServerSocketChannel的doReadMessages方法中調用Java NIO的accept方法真正接受建鏈請求,并new一個NioSocketChannel用于后續的讀寫IO操作。
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = SocketUtils.accept(javaChannel());
try {
if (ch != null) {
// 新建一個NioSocketChannel用于后續的讀寫請求
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;
}
再回到read方法,這時候的readBuf中已經加入了新創建的NioSocketChannel,所以會觸發channelRead事件在pipeline中傳遞。
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
還記得我們在channel初始化的時候,把ServerBootstrapAcceptor這個handler加入到pipeline了嗎,來看下它的channelRead回調處理
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 這個msg,其實就是NioSocketChannel
final Channel child = (Channel) msg;
// 將啟動類中配置的channelInitializer加入到NioSocketChannel對應的pipeline
child.pipeline().addLast(childHandler);
// 設置NioSocketChannel的屬性
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
try {
// 將NioSocketChannel注冊到childGroup的EventLoop的selector,準備接收和發送數據
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);
}
}
就是在這里,初始化由NioServerSocketChannel創建的NioSocketChannel,并將其注冊到在啟動類中配置的childGroup線程池中的某個EventLoop上,準備接收發送數據。
至此,服務端的啟動初始化以及建鏈流程分析完畢,總結如下:
- 利用反射,創建NioServerSocketChannel,并進行初始化,同時將ServerBootstrapAcceptor加入到pipeline;
- 將NioServerSocketChannel注冊到EventLoop的selector上,在這個過程中會創建新線程,開始執行NioEventLoop的run方法,run方法內部會監聽IO事件,同時從任務隊列中取任務執行,這個注冊操作其實也是隊列中的任務;
- 在注冊成功的回調里面執行綁定本地地址和端口的操作;
- 綁定成功后,會觸發channelActive事件,這個事件首先會被每個pipeline都有的head handler攔截處理;
- 在head handler觸發read操作,最終會執行到doBeginRead,將之前創建NioServerSocketChannel時,設置的OP_ACCEPT事件加入到NioServerSocketChannel對應的Selector的監聽序列中,至此,監聽初始化完畢。
- NioEventLoop的run方法監聽到OP_ACCEPT事件;
- 在processSelectedKey中調用NioServerSocketChannel的doReadMessages,新建NioSocketChannel,用于處理后續的讀寫請求,同時觸發channelRead事件;
- ServerBootstrapAcceptor攔截channelRead事件,初始化NioSocketChannel,并將NioSocketChannel注冊到childGroup的EventLoop上,這個注冊流程和NioServerSocketChannel相似,只是設置的readInterestOp為OP_READ,用于讀取消息,不再贅述。
- 至此,NioSocketChannel真正接管NioServerSocketChannel accept的底層socket鏈路,開始收發消息。
客戶端
有了以上服務端的啟動、初始化、建鏈流程的分析后,客戶端的流程就相對來說簡單很多了。
客戶端從doResolveAndConnect開始
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.isDone()) {
if (!regFuture.isSuccess()) {
return regFuture;
}
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
和服務端一樣,也是調用initAndRegister進行channel的初始化,并將channel注冊到EventLoop上,不同的是,初始化的監聽事件是OP_READ,而不是OP_ACCEPT,具體過程不再重復。
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
接下來調用doResolveAndConnect0和doConnect方法,將connect操作封裝成task加入到任務隊列中
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
EventLoop的線程從隊列中取出任務執行。connect操作在pipeline上流轉,connect是outBound方向的操作,所以先從tail handler開始,最終到head handler,調用NioSocketChannel的doConnect方法
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
// 調用Java NIO的接口發送建鏈請求
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
// 將監聽事件改為OP_CONNECT
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
當監聽到OP_CONNECT事件時,調用Java NIO的finishConnect結束建鏈流程,標識已經建鏈成功,為收發消息做好準備。
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
至此,客戶端的建鏈流程分析完畢,整個流程,相對服務端簡單很多。
總結
本文詳細分析了Netty作為客戶端和服務端的整個初始化和建鏈過程,其中會涉及到許多Netty的核心概念,后面會針對這些概念逐個進行分析。Netty的整體框架是基于異步編程的概念構建的,跟我們平時的編碼習慣可能有些差別,不過,沒關系,平時多看看源碼,看多了也就習慣了。Netty的源碼也有很多值得我們借鑒的編碼習慣或者說技巧,在我們平時的編碼中可以加以應用,針對這點,后面我也會專門寫一篇文章來總結。
需要說明一下,本文是基于目前(2020.8.18)Netty最新的4.1.52版本的源碼進行解析的。