Nettyソースコード分析(3)-サーバー側の新しい接続処理

序文

ネッティーソースコード分析(2) -サーバーの起動プロセスの分析ServerSocketChannelリスニングポートバインディングプロセスの最後に、我々はすでに結合操作が完了した後、網状の非同期タスクが呼び出す提出する、言及したpipeline.fireChannelActive()通知チャンネルが活性化されている、コールバックプロセッサchannelActive()方法、およびサーバーチャネルによって監視されるイベントはに更新されSelectionKey.OP_ACCEPTます。したがって、この記事では、新しいサーバー接続の確立を2つのステップに分け、そのプロセスを図に示します。

  1. モニタリングイベントSelectionKey.OP_ACCEPTの設定
  2. MainReactorによってSubReactorに確立された新しい接続の登録

ここに写真の説明を挿入

1.監視イベントSelectionKey.OP_ACCEPTの設定

  1. 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);
         }
    
  2. キューに入り、非同期タスクの実行をスケジュールするプロセスについては、フローチャートを参照してください。ここでは、特定の分析は行われません。Nettyでのデータの処理はビジネスコンポーネントこのタスクを完了するには、finalDefaultChannelPipeline#fireChannelActive()メソッドの通知への非同期呼び出しをアクティブにしました

    public final ChannelPipeline fireChannelActive() {
          
          
         AbstractChannelHandlerContext.invokeChannelActive(head);
         return this;
     }
    
  3. 最後に、HeadContext#channelActive()メソッドを呼び出す上記の手順で、ハンドラプロセッサの双方向リンクを入力しました。この方法では、主に2つのことが行われていることがわかります。

    1. ctx.fireChannelActive()は、通知メソッドを呼び出して次のプロセッサにチャネルアクティブ化イベントを通知します。これにより、次のプロセッサのchannelActive()メソッドがコールバックされます。
    2. readIfIsAutoRead()は、自動読み取り構成に従ってデータの読み取りを自動的に開始するかどうかを決定します。デフォルトは自動読み取りであり、チャネルの読み取りメソッドが呼び出されます。
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
          
          
        ctx.fireChannelActive();
    
        readIfIsAutoRead();
    }
    
    private void readIfIsAutoRead() {
          
          
         if (channel.config().isAutoRead()) {
          
          
             channel.read();
         }
    }
    
  4. Channelの読み取りデータは実際にはまだ依存してDefaultChannelPipeline#read()おり、最後にパイプラインの双方向プロセッサリンクリストのテールノードが読み取られて読み取り操作が開始されます。

    public final ChannelPipeline read() {
          
          
         tail.read();
         return this;
     }
    
  5. 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;
     }
    
  6. 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での新しい接続の登録

  1. コアがあることEevntイベント処理NioEventLoopNioEventLoop#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());
         }
     }
    
  2. unsafe.read()これはインターフェイス呼び出しであり、サーバーはとして実装されます。NioMessageUnsafe#read()このメソッドが実行するより重要なことは次のとおりです。

    1. 実装インターフェイスNioServerSocketChannel#doReadMessages()呼び出しがJDK内蔵インターフェース接続受け入れ、操作イベント監視する位置に充填ことdoReadMessages()SelectionKey.OP_READNioSocketChannel返されたオブジェクトを、この新しい接続から確立されます
    2. 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();
                 }
             }
         }
    
  3. 上記のフローチャートから、新しい接続の確立と、新しい接続を呼び出すプロセスのビジネス処理コンポーネントを理解できます。ここでは分析せず、コアのみに焦点を当て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);
             }
         }
    
  4. childGroup.register(child)登録プロセスとNettyソースコード分析(2)-いくつか言及したサーバー起動プロセス登録プロセスには、スレッドの作成と起動のSubReactorイベントループ、およびPiplelineのチャネルの作成と構成が含まれます。登録が完了するとpipeline.fireChannelActive()、チャンネルはチャンネルアクティベーションイベント通知を呼び出します次に、この記事の最初の部分では、チャネルの監視イベント操作ビットの変更がトリガーされますが、この時点で設定されている監視イベント操作ビットは SelectionKey.OP_READ

おすすめ

転載: blog.csdn.net/weixin_45505313/article/details/107232680