Nettyソースコード読解(2) - サーバーソースコードまとめ

上記でクライアントのソース コードについては大体理解できたので、サーバーのソース コードを理解しやすくするために、サーバーとクライアントの違いを分析していきます。

目次

違い

 ④

 ①


違い

 ④

クライアント: .option(ChannelOption.TCP_NODELAY, true)

TCP/IPプロトコルでは、どんなに多くのデータを送信しても、データの前には必ずプロトコルヘッダが付加され、同時に相手がデータを受信する際には、確認を示すACKを送信する必要があります。ネットワーク帯域幅を最大限に利用するために、TCP は常にできるだけ多くのデータを送信したいと考えています。これには、Nagle と呼ばれるアルゴリズムが含まれます。その目的は、多くの小さなデータ ブロックでネットワークがあふれるのを避けるために、大きなデータ ブロックをできるだけ多く送信することです。
TCP_NODELAY は、Nagle アルゴリズムを有効にするために使用されます。高いリアルタイム パフォーマンスが必要で、データがすぐに送信される場合は、このオプションを true に設定して Nagle アルゴリズムを無効にします。送信回数を減らし、ネットワーク インタラクションを減らしたい場合は、false に設定して、一定のサイズを待機します。送信する前に蓄積されます。デフォルトは false です。

サーバー: .option(ChannelOption.SO_BACKLOG, 100)

サーバー側の TCP カーネル モジュールは、A と B と呼ぶ 2 つのキューを維持します。クライアントがサーバーに接続すると、TCP カーネル モジュールは 2 回目のハンドシェイク中にクライアント接続を A キューに追加し、3 回目のハンドシェイク中にクライアント接続を A キューに追加します。ハンドシェイクにより、TCP はクライアント接続を A キューから B キューに移動します。接続が完了すると、アプリケーションの accept が戻り、accept は 3 ウェイ ハンドシェイクが完了した接続を B キューから取り出します。

A キューと B キューの長さの合計がバックログです。A キューと B キューの長さの合計がバックログより大きい場合、新しい接続は TCP カーネルによって拒否されます。バックログが小さすぎるため、受け入れ速度が追いつかない可能性があります。AB キューがいっぱいで、新しいクライアントが接続できなくなります。バックログは、プログラムがサポートする接続数に影響を与えないことに注意してください。バックログは、accept によって取り出されなかった接続にのみ影響します。

 こちらも一般的に使用されます: .option(ChannelOption.SO_REUSEADDR, true)

このパラメータは、ローカル アドレスとポートの再利用が許可されていることを示します。たとえば、サーバー プロセスがリッスンのために TCP ポート 80 を占有している場合、そのポートが再度リッスンされるとエラーが返されます。このパラメータを使用すると、問題を解決できます。このパラメータにより、
サーバー プログラムでより一般的に使用されるポートの共有が可能になります。

クライアント: .channel(NioSocketChannel.class)

サーバー: .channel(NioServerSocketChannel.class)

ロード方法と型決定のプロセスは同じなので、インスタンス化の違いに焦点を当てましょう。

NioSocketChannel と同様に、newSocket メソッドが呼び出されますが、次の呼び出しは異なります。サーバーは openServerSocketChannel メソッドを呼び出し、クライアントは openSocketChannel メソッドを呼び出します。名前が示すように、1 つはクライアント側の Java SocketChannel、もう 1 つはサーバー側の Java ServerSocketChannel です。

 次に、オーバーロードされたメソッドが呼び出され、SelectionKey.OP_ACCEPT がここに渡されます。Java NIO ソケットに関する知識がある友人は、Java NIO が Reactor モードであることを理解しているため、セレクターを使用して I/O 多重化を実装します。サーバーはクライアントの接続リクエストをリッスンする必要があるため、ここでは SelectionKey.OP_ACCEPT を設定します。お客様からの感想はまだ残っていますか?下の写真を比較してください

 次に、クライアント コードと同じように、コンストラクターを段階的に呼び出します。安全でないオブジェクトとパイプラインがインスタンス化されます。

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

しかし、newUnsafe は異なり、サーバー側の unsafe フィールドは実際には AbstractNioMessageChannel#AbstractNioUnsafe のインスタンスです。そしてクライアントは AbstractNioByteChannel#NioByteUnsafe のインスタンスです

 ①

クライアント側では、EventLoopGroup オブジェクトを 1 つだけ提供しましたが、サーバー側の初期化では、2 つの EventLoopGroup を設定しました。1 つは BossGroup、もう 1 つは WorkerGroup です。では、これら 2 つの EventLoopGroup は何に使用されますか? 実際、bossGroup は次のとおりです。サーバーの受け入れ、つまりクライアントの接続要求の処理に使用されます。実際に作業を行うのは workerGroup であり、クライアント接続チャネルの IO 操作を担当します。以下に示すように

サーバー側の BossGroup はクライアント接続があるかどうかを常に監視しており、新しいクライアント接続が見つかると、bossGroup はこの接続用のさまざまなリソースを初期化し、workerGroup から EventLoop を選択してこのクライアント接続 Middle にバインドします。サーバーとクライアント間の次の対話プロセスはすべて、割り当てられた EventLoop 内で行われます。ソースコード分析を続けてみましょう。

    public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
        super.group(parentGroup); // 1
        if (this.childGroup != null) {
            throw new IllegalStateException("childGroup set already");
        }
        // 2
        this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
        return this;
    }

上記のコードは 2 つのことを行います。1 は、AbstractBootstrap の group メソッドを入力してグループ属性を指定することです。これはクライアント側に直接あります。2はServerBootstrapのchildGroup属性を指定することです。プロパティを初期化した後、それらはどこで使用されるのでしょうか?

BossGroup : AbstractBootstrap.bind -> AbstractBootstrap.doBind -> AbstractBootstrap.initAndRegister は boosGroup を NioServerSocketChannsl に関連付けます

ChannelFuture regFuture = config().group().register(channel);

 workGroup : initAndRegister メソッドに表示されるのは、やはり init(channel) です。

   void init(Channel channel) {
        // 只贴出和childGroup有关的关键代码
        final EventLoopGroup currentChildGroup = childGroup;
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }

init メソッドは ServerBootstrap で書き直され、childGroup は主に ServerBootstrapAcceptor クラスにカプセル化されるため、このクラスに焦点を当てます。

クリックすると、それが静的な内部クラスであることがわかりました。childGroupに関する操作は主にクラスで書き換えられたchannelReadメソッド内に配置されます。

 childGroup.register(child).addListener(...);

ここの子は NioSocketChannel です。これは、workerGroup 内の EventLoop が NioSocketChannel に関連付けられていることを意味します。

では、この channelRead メソッドはいつ呼び出されるのでしょうか? クライアントがサーバーに接続すると、基礎となる Java NIO ServerSocketChannel に SelectionKey.OP_ACCEPT 準備完了イベントが発生し、NioServerSocketChannel.doReadMessages を呼び出します。

            SocketChannel ch = SocketUtils.accept(javaChannel());
            if (ch != null) {
                buf.add(new NioSocketChannel(this, ch));
                return 1;
            }

 accept メソッドを通じてクライアントへの接続を取得し、それを NioSocketChannel としてカプセル化します。渡された this は、NioSocketChannel をカプセル化する親チャネルである NioServerSocketChannel を参照します。次に、Netty の ChannelPipeline メカニズムを通じて、読み取りイベントが各ハンドラーに段階的に送信されるため、前述した ServerBootstrapAcceptor.channelRead がトリガーされます。

ここでもboosGroupやworkGroupと同様にhandlerとchildHandlerが出てきますが、前者2つと同じものになるのでしょうか?1 つは接続を処理し、1 つは IO イベントを処理しますか? コードを見てみましょう。

④ではServerBootstrapのinitメソッドの書き換えについて触れました。ハンドラーの操作も関係します

        final ChannelHandler currentChildHandler = childHandler; // 1

        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler(); //2
                if (handler != null) {
                    pipeline.addLast(handler);
                }

                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });

まず 2 (上記のコードの注 2) を見てください。ここで、config.handler() はサーバー コード .handler(new LoggingHandler(LogLevel.INFO)) で指定されたハンドラーです。空でない場合は、パイプラインに追加します。 。

 次に 1 (上記のコード コメント 1) を見てください。ここの childHandler は依然として ServerBootstrapAcceptor を構築するプロパティであり、このクラスをクリックすると、この childHandler が ServerBootstrapAcceptor によって書き換えられた channelRead メソッド内にあることがわかります (いつ呼び出されたか覚えていますか?)。接続確立後)

child.pipeline().addLast(childHandler);

この子を覚えておいてください。彼は NioSocketChannel であり、childHandler が彼のパイプラインに追加されています。

要約すると、次のとおりです

  • ハンドラーは受け入れフェーズで動作し、クライアントの接続要求を処理します。
  • childHandler はクライアント接続が確立された後に動作し、クライアント接続の IO 対話を担当します。

おすすめ

転載: blog.csdn.net/wai_58934/article/details/127860262