序文
でネッティーソースコード分析(2) -サーバーの起動プロセスの分析ServerSocketChannel
リスニングポートバインディングプロセスの最後に、我々はすでに結合操作が完了した後、網状の非同期タスクが呼び出す提出する、言及したpipeline.fireChannelActive()
通知チャンネルが活性化されている、コールバックプロセッサchannelActive()
方法、およびサーバーチャネルによって監視されるイベントはに更新されSelectionKey.OP_ACCEPT
ます。したがって、この記事では、新しいサーバー接続の確立を2つのステップに分け、そのプロセスを図に示します。
- モニタリングイベントSelectionKey.OP_ACCEPTの設定
- MainReactorによってSubReactorに確立された新しい接続の登録
1.監視イベントSelectionKey.OP_ACCEPTの設定
-
AbstractUnsafe#bind()
完了時にServerSocketChannel
リスニングサーバーポートのバインド、今回はチャネルはすでにActive
状態であり、通知チャネルでアクティブ化された非同期タスクをイベントループスレッドに送信しますpublic final void bind(final SocketAddress localAddress, final ChannelPromise promise) { assertEventLoop(); ...... boolean wasActive = isActive(); try { doBind(localAddress); } catch (Throwable t) { safeSetFailure(promise, t); closeIfClosed(); return; } if (!wasActive && isActive()) { invokeLater(new Runnable() { @Override public void run() { pipeline.fireChannelActive(); } }); } safeSetSuccess(promise); }
-
キューに入り、非同期タスクの実行をスケジュールするプロセスについては、フローチャートを参照してください。ここでは、特定の分析は行われません。Nettyでのデータの処理はビジネスコンポーネントこのタスクを完了するには、final
DefaultChannelPipeline#fireChannelActive()
メソッドの通知への非同期呼び出しをアクティブにしましたpublic final ChannelPipeline fireChannelActive() { AbstractChannelHandlerContext.invokeChannelActive(head); return this; }
-
最後に、
HeadContext#channelActive()
メソッドを呼び出す上記の手順で、ハンドラプロセッサの双方向リンクを入力しました。この方法では、主に2つのことが行われていることがわかります。- ctx.fireChannelActive()は、通知メソッドを呼び出して次のプロセッサにチャネルアクティブ化イベントを通知します。これにより、次のプロセッサのchannelActive()メソッドがコールバックされます。
- readIfIsAutoRead()は、自動読み取り構成に従ってデータの読み取りを自動的に開始するかどうかを決定します。デフォルトは自動読み取りであり、チャネルの読み取りメソッドが呼び出されます。
public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.fireChannelActive(); readIfIsAutoRead(); } private void readIfIsAutoRead() { if (channel.config().isAutoRead()) { channel.read(); } }
-
Channel
の読み取りデータは実際にはまだ依存してDefaultChannelPipeline#read()
おり、最後にパイプラインの双方向プロセッサリンクリストのテールノードが読み取られて読み取り操作が開始されます。public final ChannelPipeline read() { tail.read(); return this; }
-
TailContext#read()
findContextOutbound()
リンクされたリストの末尾からプロセスコンテキストアウトバウンドイベントを見つけるためのメソッドを介したメソッドメインロジック、つまりHeadContext
、ハンドラーread()
メソッド内のパッケージ呼び出しpublic ChannelHandlerContext read() { final AbstractChannelHandlerContext next = findContextOutbound(); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeRead(); } else { Runnable task = next.invokeReadTask; if (task == null) { next.invokeReadTask = task = new Runnable() { @Override public void run() { next.invokeRead(); } }; } executor.execute(task); } return this; }
-
HeadContext#read()
メソッドロジックが実際にUnsafe#beginRead()
メソッドを呼び出すことはめったにありません。これを実現するメソッドはAbstractUnsafe#beginRead()
、AbstractNioChannel#doBeginRead()
メソッドへの最後の呼び出しです。ことがわかるAbstractNioChannel#doBeginRead()
行うための主なものをすることである。内部ロジックは比較的簡潔でもあり、イベント監視の動作ビットを変更してAbstractNioChannel
保存した内部属性readInterestOp
、およびこの属性の値は、サーバーの起動時に設定されているSelectionKey.OP_ACCEPT
、と言うことです現在のステップを通じて、サーバーは新しい接続イベントの監視を開始しますprotected void doBeginRead() throws Exception { // Channel.read() or ChannelHandlerContext.read() was called final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { return; } readPending = true; final int interestOps = selectionKey.interestOps(); if ((interestOps & readInterestOp) == 0) { selectionKey.interestOps(interestOps | readInterestOp); } }
2.SubReactorでの新しい接続の登録
-
コアがあることEevntイベント処理
NioEventLoop
、NioEventLoop#run()
循環のためのスペース、開かNioEventLoop#processSelectedKeys()
チャンネルに関連するすべてのイベントを処理し、対応する最後のイベントは、処理方法であるNioEventLoop#processSelectedKey()
ためSelectionKey.OP_ACCEPT
、イベントが呼び出されますunsafe.read()
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); ...... try { int readyOps = k.readyOps(); // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise // the NIO JDK channel implementation may throw a NotYetConnectedException. 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(); } // Process OP_WRITE first as we may be able to write some queued buffers and so free memory. 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(); } // 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(); } } catch (CancelledKeyException ignored) { unsafe.close(unsafe.voidPromise()); } }
-
unsafe.read()
これはインターフェイス呼び出しであり、サーバーはとして実装されます。NioMessageUnsafe#read()
このメソッドが実行するより重要なことは次のとおりです。- 実装インターフェイスNioServerSocketChannel#doReadMessages()呼び出しがJDK内蔵インターフェース接続受け入れ、操作イベント監視する位置に充填ことdoReadMessages()
SelectionKey.OP_READ
のNioSocketChannel
返されたオブジェクトを、この新しい接続から確立されます - pipe.fireChannelRead(readBuf.get(i))は、新しく確立されたNioSocketChannelをパラメーターに取り込み、ビジネス処理コンポーネントを呼び出して処理し、最後にサーバーの組み込みプロセッサーを呼び出します。
ServerBootstrapAcceptor#channelRead()
MainReactorからSubReactorへの新しい接続を登録します
public void read() { assert eventLoop().inEventLoop(); ...... 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(); } } }
- 実装インターフェイスNioServerSocketChannel#doReadMessages()呼び出しがJDK内蔵インターフェース接続受け入れ、操作イベント監視する位置に充填ことdoReadMessages()
-
上記のフローチャートから、新しい接続の確立と、新しい接続を呼び出すプロセスのビジネス処理コンポーネントを理解できます。ここでは分析せず、コアのみに焦点を当て
ServerBootstrapAcceptor#channelRead()
ます。SubReactorに登録された新しく作成されたチャネルを参照するServerBootstrap
と、SubReactor構成オプションとプロセッサNioEventLoopGroup
インスタンスの保存に使用されるこのメソッドを確認できます。childGroup
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); } }
-
childGroup.register(child)
登録プロセスとNettyソースコード分析(2)-いくつか言及したサーバー起動プロセス登録プロセスには、スレッドの作成と起動のSubReactorイベントループ、およびPiplelineのチャネルの作成と構成が含まれます。登録が完了するとpipeline.fireChannelActive()
、チャンネルはチャンネルアクティベーションイベント通知を呼び出します。次に、この記事の最初の部分では、チャネルの監視イベント操作ビットの変更がトリガーされますが、この時点で設定されている監視イベント操作ビットはSelectionKey.OP_READ