接続しているアクセスの新シリーズのネッティーソースコード解析

Fanger魏コードまたは次のスキャンの公共マイクロチャンネル番号を検索するには菜鸟飞呀飞、あなたは、マイクロチャネルのパブリック番号に焦点を当てるより読むことができるSpring源码分析、とJava并发编程の記事。

マイクロチャンネル公衆数

1.問題

サーバが起動ネッティーするときは、接続されたクライアントの受信を開始することができます。その後、サーバはそれの新しい接続を作成する方法を網状のですか?ソースコードを読むために開始する前に、次の3つの質問を考慮することができます。

  • どのようにサーバーの検出は、(後に新しい接続へのアクセスを参照)新しいクライアント要求のアクセスということでしょうか?
  • JDKのネイティブNIOでは、サービスがで終了します)(ServerSocketChannel.acceptクライアントへの新しいアクセス、ネッティー内のサーバーとどのように新たな接続へのアクセスにそれを処理するために対応するクライアントチャネルを作成するには?
  • ネットワークIOはNioEventLoopがそれをバインドするワーカースレッドプールを読み取りおよび書き込み操作はネッティーにNioEventLoopスレッドで行われ、その後、クライアントチャネルとどのように?

2.アクセスへの新しい接続の検出

記事では、実装プロセスネッティーソースコード解析NioEventLoopシリーズ、開始後NioEventLoopスレッドを分析するには、イベント処理ネットワークIO、一般的なタスクとタイミングタスクをサイクリングに行くしていきます。IOは、ネットワークイベントを処理する際にポーリングがIO OP_ACCEPT(コード下図)である場合、イベント・タイプは、その新しい接続が検出され、サーバに接続するために、新しいクライアントを意味します。今回は、サーバーチャネルは、新しい接続を読み込みます。

if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
    unsafe.read();
}
复制代码

イベントはOP_ACCEPTが呼び出されますされたときに見ることができるunsafe.read()新しい接続にアクセスするための方法。オブジェクトのこの危険なNioMessageUnsafeタイプでは、なぜ、インスタンスのですか?唯一のサービスチャネルは関心のOP_ACCEPTイベントに終了し、サーバーチャネルに保存されるため、安全でないプロパティは、インスタンスNioMessageUnsafeタイプです。

二、読むためにコールdoReadMessages()メソッド:読み取りスプレッドは、パイプライン内のチャネルを介してサーバに接続されたソースは、この方法が非常に長いですが、それは二つのこと、最初は主にやっている)(読み、ハンドラchannelRead()メソッドの最後の実行のそれぞれ。

クライアントチャネルを作成します3。

サーバーのチャンネルOP_ACCEPTイベントを聞いた後、それは、新しい接続のためのクライアントチャネルを作成、読み取り、クライアントのチャネルによって行われ、次の書き込みデータます。クライアントとこのチャネルは、そのソース以下NioServerSocketChannelで定義されてdoReadMessages()メソッドによって作成されます。

protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = SocketUtils.accept(javaChannel());
    try {
        if (ch != null) {
            // 将原生的客户端channel包装成netty中的客户端channel:NioSocketChannel
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
        // 异常日志打印等...
    }
    return 0;
}
复制代码

この方法では、最初に(javaChannelを渡します)のServerSocketChannelは、サービス側のチャネルネイティブがNioServerSocketChannelに保存されていることを、サーバーチャネルJDKのネイティブに取得CH属性を、NioServerSocketChannelを初期化するときは、うをchプロパティの割り当てを(缶参考記事:ソースコード解析サーバチャンネルの初期化のネッティーシリーズ)。サーバーチャネルJDKのネイティブを作成した後、ネイティブJDK SocketUtilsを通じてこのツールのカテゴリー、すなわちSocketChannelへのクライアントチャネルを作成します。ツールは、達成するための基盤となるSocketUtilsは、実際には、)(ServerSocketChannel.accept、JDKのネイティブAPIを呼び出します。

NioSocketChannel:ネイティブのSocketChannelを作成した後、網状必要が網状すなわち、定義されたサーバーチャネル型にパッケージされます。どのようにそれをパッケージ化するには?新しいキーワードのコンストラクタ呼び出しNioSocketChannelでパッケージングされます。コンストラクタでは、初期化作業の多くを行います。ソースの追跡、あなたはコンストラクタAbstractNioChannelクラスに次の呼び出しがあります。

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    // 此时的parent = NioServerSocketChannel,ch = SocketChannel(JDK原生的客户端channel),readInterestOp = OP_READ
    super(parent);
    // 保存channel和感兴趣的事件
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
        // 设置为非阻塞
        ch.configureBlocking(false);
    } catch (IOException e) {
        // 异常处理...
    }
}
复制代码

この工法では、最初のネイティブクライアントチャネルを保存し、クライアントチャネルにとって関心のあるイベント、次いでブロッキングモードクライアントチャネルがNIOネットワークプログラミングで(ブロッキングがないことを示し、falseに設定され、このステップは必要ですそうでない場合は)文句を言うでしょう開始。また、親クラスのコンストラクタを呼び出している間、親クラスがAbstractChannelです。クラスのコンストラクタソースAbstractChannelは、以下の通りです。

protected AbstractChannel(Channel parent) {
    // parent的值为NioServerSocketChannel
    this.parent = parent;
    id = newId();
    // 对于客户端channel而言,创建的unsafe是NioSocketChannelUnsafe
    unsafe = newUnsafe();
    // DefaultChannelPipeline
    pipeline = newChannelPipeline();
}
复制代码

この工法、関係するクライアントチャネル、親値NioServerSocketChannel、ネッティーサーバの起動時に作成されたサーバーチャネルで。その後、安全でないがNioSocketChannelUnsafe、デフォルトのクライアントチャネルを作成するパイプラインの最後で作成し、この時点でのパイプライン建設は、以下のようです。(以前の記事を読んでいれば、あなたは、サーバー側のチャネルを作成するときにも、コンストラクタに呼ばれることがあります)

図のパイプライン構造。

NioSocketChannelは最終的にあなたが設定オブジェクトを作成するときにTCPクライアントチャネルの構成設定と属性の数のユーザーを格納するために使用されるNioSocketChannelConfigオブジェクトはTCP TCP_NODELAYパラメータがtrueに設定されているだろうが作成されます。デフォルトではTCP、後続のデータパケットは小さなデータパケットよりも蓄積される前に送出され、網状にタイムリーなデータのIより小さなパケットには伝送遅延がないことを示す、trueにTCP_NODELAYパラメータ従って、送信しました。

これまでのところ、今作成したクライアントチャネル、読み取りと書き込みのバックデータネットワークに対応する新しい接続は、NioSocketChannelが行わに基づいています。

4.バインディングNioEventLoop

チャネルがクライアントで作成された場合、read()メソッドは、パイプラインが順次ハンドラ()メソッドの各々をチャネルpipeline.fireChannelRead(のSocketChannel)パイプラインによるコードのこのラインを介してクライアント伝播実行channelReadう。(注ここでパイプラインを使用すると、クライアントチャネルを作成するときに、それぞれの新しいクライアントチャネルのためのパイプラインを作成する、サーバー・チャネル・パイプラインに格納され、ここで、混同しないでください)

サーバーが開始されると、パイプラインのサーバーチャネル構造図は、以下のようにした場合(詳細な説明は、これらの3件の記事を参照:ソースコード解析サーバチャンネル初期設定の網状シリーズソースコード解析サーバチャンネル登録の網状シリーズ及び網状ソースコード解析シリーズ結合チャネルポートのサービス側)。

サーバのパイプライン

パイプラインは、頭と尾のために、タリアchannelRead()メソッドは、任意の実用的な意義を動作しない、それが直接、次のノードの広がりで、ここで重要なchannelRead ServerBootstrapAcceptorノード()メソッドです。次のようにメソッドのソースです。

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;

    // 向客户端的channel中添加用户自定义的childHandler
    child.pipeline().addLast(childHandler);

    // 保存用户为客户端channel配置的属性
    setChannelOptions(child, childOptions, logger);

    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    try {
        // 将客户端channel注册到工作线程池,即从workerGroup中选择出一个NioEventloop,再将客户端channel绑定到NioEventLoop上
        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);
    }
}
复制代码

この方法では、最初のchildHandler内のノードのパイプラインクライアントチャネルに追加し、このchildHandlerはそれがどういう意味、ユーザが定義しているのですか?以下に示すように、カスタム・タイプchildHandler ChannelInitializerのユーザchildHandler()メソッドは、この場合は、クライアント・ノードにパイプラインchildHandlerチャンネルに追加される(この場所は非常に重要であり、それは、後に使用されます)。その後setChannelOptionsは、TCPクライアントチャネル構成のためのユーザパラメータとプロパティを保存します。

デモ

childGroup.register(子)の中で最も重要なステップは、このコード行は、クライアントチャネル1 NioEventLoop workerGroupスレッドプールに登録されます。(サーバのポート結合の過程において、NioEventLoopGroupを登録するための呼び出しは、特にbossGroupでNioEventLoopを登録する()メソッドは、サーバーチャネルに類似しています)。

このときchildGroupはworkerGroup(スレッドプールからのスレッドで複数のスレッドからの反応器マスターモデル)、コール・レジスタ()メソッドは、次のメソッドへの呼び出しです。

public ChannelFuture register(Channel channel) {
    // next()方法会从NioEventLoop中选择出一个NioEventLoop
    return next().register(channel);
}
复制代码

next()メソッドは、(参照してください)(次の詳細な方法について:NioEventLoopにNioEventLoopから選択される創造と打ち上げのネッティーソースコード解析NioEventLoopシリーズ)NioEventLoopはSingleThreadEventLoopを継承したためので、ここでの最後の呼び出しがSingleThreadEventLoopであり、レジスタ()メソッド以下。

public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    /**
     *  对于客户端channel而言
     *  promise是DefaultChannelPromise
     *  promise.channel()获取到的是NioSocketChannel
     *  promise.channel().unsafe()得到的是NioSocketChannelUnsafe
     *  由于NioSocketChannelUnsafe继承了AbstractUnsafe,所以当调用unsafe.register()时,会调用到AbstractUnsafe类的register()方法
     */
    // this为NioEventLoop
    promise.channel().unsafe().register(this, promise);
    return promise;
}
复制代码

登録するNioSocketChannelUnsafeはそうあなたが(unsafe.register呼び出すとき、AbstractUnsafeを継承したため、オブジェクトを取得するには、ここ()安全ではない)、NioSocketChannelUnsafeで、呼び出し()メソッドAbstractUnsafeクラス。次のようにソース後のプロセスを合理化します。

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 省略部分代码....

    // 对客户端channel而言,这一步是给NioSocketChannel的eventLoop属性赋值
    AbstractChannel.this.eventLoop = eventLoop;

    // 判断是同步执行register0(),还是异步执行register0()
    if (eventLoop.inEventLoop()) {
        // 同步执行
        register0(promise);
    } else {
        try {
        	// 提交到NioEventLoop线程中,异步执行
            eventLoop.execute(new Runnable() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            // 省略部分代码
        }
    }
}
复制代码

実際には、サーバーのチャネルはまた、メソッドに呼ばれ、NioEventLoopに登録する(記事を参照してください。登録のサーバーのソースコード解析チャンネルネッティーシリーズ)。

クライアントの場合、この方法では、次のコード行では、それはクライアントチャネルNioEventLoopとなり、その資料の冒頭に3番目の質問に対する答え結合しています。

AbstractChannel.this.eventLoop = eventLoop;
复制代码

そして、確かに、ここで、現在のスレッドが入ってくるイベントループスレッドに等しい保存するかどうかを決定します。なぜ?現在のスレッドがスレッドグループbossGroupスレッドで、スレッドグループイベントループはので、ここで、workerGroupスレッドでfalseを返すので、それは非同期レジスタ0()メソッドを実行します。以下のようにレジスタ0のソース()メソッドです。

private void register0(ChannelPromise promise) {
    try {
        // 省略部分代码...
        boolean firstRegistration = neverRegistered;
        /**
         * 对于客户端的channel而言,doRegister()方法做的事情就是将服务端Channel注册到多路复用器上
         */
        doRegister();
        neverRegistered = false;
        registered = true;

        //会执行handlerAdded方法
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        //通过在pipeline传播来执行每个ChannelHandler的channelRegistered()方法
        pipeline.fireChannelRegistered();

           // 如果客户端channel已经激活,就执行下面逻辑。
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                beginRead();
            }
        }
    } catch (Throwable t) {
        // 省略部分代码...
    }
}
复制代码

レジスタ0()メソッドでは、重要な論理的な三つのステップ、最初:doRegister();第二:pipeline.invokeHandlerAddedIfNeeded();第三:pipeline.fireChannelRegistered()。ここでは物事が行われているかを見てみましょうするための3つのステップがあります。

doRegister()は、マルチプレクサのステップに登録されている実際のクライアントチャネルです。doRegister()呼び出しはdoRegister AbstractNioChannelクラス()メソッドは、以下のソースの削除です。

protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            // 异常处理......
        }
    }
}
复制代码

どのjavaChannel()JDKがたSocketChannelでネイティブにある取得します。

eventLoop().unwrappedSelector()JDKはネイティブマルチプレクサセレクタ(基礎となるデータ構造を交換した)を取得します。(UnwrappedSelectorイベントループプロパティはNioEventLoopは、初期化、基礎となるデータ構造は、この時点で交換するときに作成されます)

だからjavaChannel().register(eventLoop().unwrappedSelector(), 0, this)、このコード行は、実際にJDKネイティブのSocketChannelを呼び出すregister(selector,ops,attr)方法、そしてクライアントはチャンネルマルチプレクサセレクタに登録されています。

ネイティブJDKレジスタ()メソッドを呼び出すときに、第三のパラメータは、この中に渡されることに注意してくださいは、NioSocketChannel、この場合は、表現現在の主題です。これは、そうすることの利点は、あなたがチャネルマルチプレクサセレクタを通じて、クライアントに得ることができるということです、マルチプレクサセレクタへの添付ファイルとして保存されます。渡された2番目のパラメータは、この場合は、チャネルマルチプレクサに登録されたクライアントを示し、0であり、クライアントは、関係のあるイベント識別子chennel、すなわち、後ろに(この時点で、任意のイベントに興味を持っていない、0でありますイベントは)OP_READを対象としています。

doRegister()メソッドが実行されたとき、それは第二のステップ実行します。pipeline.invokeHandlerAddedIfNeeded()は、このステップのDOは、パイプライン()メソッドでhandlerAddedコールバックハンドラです。

ダウン実装、我々は前述の第三の工程であるpipeline.fireChannelRegistered()へのコードの実行。行うにはこのステップは、それが広がってどのように、スプレッドチャンネルがイベントを登録しているのですか?これは、後で各channelRegisteredハンドラ()メソッドの実装に続いて、ハンドラ内のパイプラインに沿って第1のノードの始まりです。

前述したように実行channelRegistered()メソッドの広がりは、彼らがどの結局意志、channelRegistered()匿名クラスのメソッドを実行しますので、パイプラインの匿名のクライアントチャネルにクラスのChannelInitializerタイプを追加しますinitChannel(チャネル)方法、即ち書き換え匿名クラスを実行する、次のコードは、図に示します。initChannel(チャネル)メソッドを呼び出す方法について、あなたは記事を参照することができます:登録のネッティーソースコード解析サーバチャネルシリーズは非常に詳細な分析を持っていました、。しかし、ソースコードや手を読み込むための最良の方法は、デバッグを使用すると、より深く理解しやすく体験するかもしれないものをデバッグします。

コールバック

再びレジスタ0()メソッドを返し、そして最終的にはこの時点で、クライアントは既にチャネルマルチプレクサに登録されているため、真であるためtrueを返し、そしてかどうか)のisActiveを(決定クライアントによる最初のチャネル以来登録したら、それは意志であるpipeline.fireChannelActive()のコード行、意志すべてのハンドラ()メソッドは、最終的にdoBeginRead AbstractChannelの()メソッドの呼び出し実行channelActiveの普及ダウンパイプラインを介してクライアントチャネル(このステップ呼処理は非常に複雑であり、それは)直接DEBUGを持っているでしょう。次のようにソースdoBeginRead方法があります。

protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;
    /**
     * 在客户端channel注册到多路复用器上时,将selectionKey的interestOps属性设置为了0
     * selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
     */
    final int interestOps = selectionKey.interestOps();
    /**
     * readInterestOp属性的值,是在NioSocketChannel的构造器中,被设置为SelectionKey.OP_READ
     */
    if ((interestOps & readInterestOp) == 0) {
        // 对于客户端channel而言,interestOps | readInterestOp运算的结果为OP_READ
        // 所以最终selectionKey感兴趣的事件为OP_READ事件,至此,客户端channel终于可以开始接收客户端的链接了。
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}
复制代码

これまでのところ、クライアントチャネルの関心がOP_READイベントとなり、その後、データを読み書きすることができます。

5.まとめ

新しい接続が入って来たときにこのホワイトペーパーでは、網状、サーバーがこの新しい接続用のクライアントチャネルを作成する方法、およびNioEventLoopスレッドにそれをバインドする方法である、分析します。クライアントチャネルの登録プロセスと登録プロセスサーバーチャネルは非常に似て、呼び出したプロセスとほぼ同じ、あなたがこの記事読むことをお勧めしているチャンネルのシリーズが登録ネッティーソースコード解析サーバーを

勧告

マイクロチャンネル公衆数

おすすめ

転載: juejin.im/post/5e056edfe51d455802163862
おすすめ