ネッティーソースコード解析サーバチャネルシリーズ・レジスタ

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

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

問題

  1. よるJDKネイティブNIOの文言ではserverSocketChannel.register(selector,SelectionKey.OP_ACCEPT)、サーバのチャンネルその後、ネッティーで、マルチプレクサセレクタに登録するだけでなく、どのようにNioServerSocketChannelには、マルチプレクサ、それに登録されましたか?登録プロセスの間に、ネッティーは、余分な何の事を行っていますか?
  2. 前記事のソースコード分析サーバチャンネル初期設定の網状シリーズ ChannelInitializer、匿名クラスinitChannel(チャネル)メソッドは、実行される:、パイプライン匿名クラスに追加INITにおける分析(チャネル)方法、それが重要なロジックです:パイプラインで2つのハンドラを追加します。しかし、記事では、結果の直接言うinitChannel(チャンネル)で、コードは(チャネル)メソッドで匿名クラスinitChannelへのコールバックがどのように言っていなかった、以下の記事は、より詳細にこれを説明します。

パートレビュー

呼び出された場合ServerBootstrap.bind(port)、コードが実行するAbstractBootstrap.doBind(localAddress)方法を、順番にdoBind()メソッドを呼び出すinitAndRegister()方法。次のようにコード簡略initAndRegister()メソッドです。

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        /**
         * newChannel()会通过反射创建一个channel,反射最终对调用到Channel的构造器
         * 在channel的构造器中进行了Channel很多属性的初始化操作
         * 对于服务端而言,调用的是NioServerSocketChannel的无参构造器。
         */
        channel = channelFactory.newChannel();
        /**
         * 初始化
         * 调用init方法后,会想服务端channel的pipeline中添加一个匿名类,这个匿名类是ChannelInitializer
         * 这个匿名类非常重要,在后面channel的register过程中,会回调到该匿名类的initChannel(channel)方法
         */
        init(channel);
    } catch (Throwable t) {
        // 省略部分代码...
    }
    ChannelFuture regFuture = config().group().register(channel);
    // 省略部分代码...
    return regFuture;
}
复制代码

initAndRegister()作用の方法は、サーバーのチャネルを初期化する、すなわちNioServerSocketChannelであり、そしてサーバーチャネルマルチプレクサに登録されています。前記事でソースコード解析サーバの網状シリーズチャネル初期化は、サーバーチャネル初期化処理である前半の()メソッドにinitAndRegisterを分析しました。サービス側チャネルの初期化中にchannelFactory.newChannel()パイプライン、安全でない:NioServerSocketChannelコンストラクタコールに対する引数を反映しないであろう、それは最終的NioServerSocketChannelインスタンスが作成され、構成プロセスには、例えば、NioServerSocketChannel多くの属性を初期化します。その後、呼び出すinit(channel)メソッドを、NioServerSocketChannelために設定されoptions、attr、その他の性質、最も重要なのは、パイプラインNioServerSocketChannelで匿名クラスのChannelInitinalizer型の追加です。

一つは、INIT(チャネル)メソッドを完了した後にコードを実行した後ChannelFuture regFuture = config().group().register(channel);このコード行に分析の焦点は、今日、この記事であり、その主な機能は、サーバーチャネルマルチプレクサに登録することです。

レジスタ(チャネル)ソース

ChannelFuture regFuture = config().group().register(channel);
复制代码

このコード行では、config()あなたが私たちは、このような、ハンドラをbossGroup、workerGroup、オプションを設定すると、網状にサーバを設定したプロパティの一部を保持するServerBootstrapConfigオブジェクトを取得、 childHandler 他の属性は、このオブジェクトServerBootstrapConfigに格納されています。(BossGroup、workerGroupは、我々は次のコードを作成したものであり、スレッドプールから主反応器NioEventLoopGroupを表します。)

// 负责处理连接的线程组
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 负责处理IO和业务逻辑的线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
复制代码

config().group()BossGroupは、我々は、サーバーのためにそのセットを取得します。だから、config().group().register(channel)実際にNioEventLoopGroupは、レジスタ(チャネル)メソッドを呼び出します。NioEventLoopGroupはMultithreadEventLoopGroupクラスで定義されMultithreadEventLoopGroup、レジスタ(チャネル)を継承しているので、それが呼び出されますMultithreadEventLoopGroup.register(channel)(プロセス中のソースコードを見てネッティークラスの継承は、IDEAのデバッグソースコードの使用を参照するための最良の方法は非常に複雑である、または時々でクラスメソッドの正確具体的な実装を知りません)。

次のようにソースMultithreadEventLoopGroup.register(チャネル)方法です。

public ChannelFuture register(Channel channel) {
    return next().register(channel);
}
复制代码

NioEventLoopGroupはNioEventLoopのセットが含まれているスレッドグループ、です。next()NioEventLoopGroupこの方法は実行がNioEventLoopを通して(チャネル)を登録し、その後、(NioEventLoopは単にスレッドであることが理解される)ポーリング方式NioEventLoopによってスレッドグループから削除されます。

あなたが呼び出すとNioEventLoop.register(channel)、呼び出しが実際にSingleThreadEventLoopクラスレジスタ(チャネル)です。SingleThreadEventLoop.register源として(チャネル)。

public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}
复制代码

チャンネルがNioServerSocketChannelである。この時点で、これはNioEventLoopです。オブジェクトを作成し、このオブジェクトへのチャネルとNioEventLoop ChannelPromise保存、それはチャンネルにし、ChannelPromise NioEventLoopから戻って取得するのは簡単ですChannelPromise。

別のコールに続いてレジスタSingleThreadEventLoop(channelPromise)過負荷です。ソースは以下のとおりです。

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

レジスタ(最終ChannelPromiseの約束)で、promise.channel()買収はNioServerSocketChannel(前述のように、あなたがChannelPromiseを作成するときに、NioServerSocketChannelは約束に保存されますので、ここに得ることができる)です。

promise.channel().unsafe()実際にはNioServerSocketChannel.unsafe()、それはそれになるNioMessageUnsafeオブジェクト。ときにオブジェクトがNioServerSocketChannelそれにそれを保存することですか?反射NioServerSocketChannelのコンストラクタ呼び出し、コンストラクタNioServerSocketChannel親クラスが関係するサーバーチャネルのための危険なプロパティを初期化すると、危険なNioMessageUnsafeインスタンスオブジェクトの属性であり、関係するクライアントチャネルは、NioSocketChannelUnsafeプロパティインスタンスが安全ではありませんオブジェクトが(これが重要であることを覚えて、新しい接続の後ろのアクセスは、読み取りと書き込みのデータが安全でないが、達成これら2に基づいています)。

ここでは、サーバーのチャンネルであるので、promise.channel().unsafe().register(this, promise)実際に(これ、約束)メソッドNioMessageUnsafeクラスを登録するための呼び出しです。NioMessageUnsafeは継承されたAbstractUnsafeクラスのregister(this, promise)メソッドが実際にAbstractUnsafeクラスで定義され、NioMessageUnsafeクラスは、このメソッドの毛をオーバーライドしていないので、最終的にに呼び出されますAbstractUnsafe.register(this, promise)そして最後にコアコードに!

AbstractUnsafe.register(this, promise)ソースとして削除ソースの方法。

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

    // 对服务端channel而言,这一步是给NioServerSocketChannel的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) {
            // 省略部分代码
        }
    }
}
复制代码

上記ロジックコードは、二つの部分に分けることができます。まず:によってAbstractChannel.this.eventLoop = eventLoopイベントループプロパティNioServerSocketChannelの割り当てに、このNioEventLoopにバインドへのチャネルバックのサービス側ので、すべての操作は、このスレッドによって実行しました。第二:によってeventLoop.inEventLoop()判断は、同期レジスタ0()メソッド、または非同期実行レジスタ0()メソッドです。

eventLoop.inEventLoop()ロジックは、比較的単純であり、現在のスレッドのイベントループスレッドで決定され、格納されているに等しい、同期実行レジスタ0(IF)、等しい(非同期実行レジスタ0に等しくない場合)。現在のスレッドのために、この時間はとてもイベントループによって非同期レジスタ0実行するmain()のスレッド、スレッドに確かに等しくないイベントループ、()です。

これまでのところ、まだチャネルマルチプレクサに結合するサーバー側のコードを見ていません。従って、結合操作()は、次のルックレジスタ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已经激活,就执行下面逻辑。
       	// 由于此时服务端channel还没有绑定端口,因此isActive()会返回false,不会进入到if逻辑块中
        if (isActive()) {
            // 省略部分代码
        }
    } catch (Throwable t) {
        // 省略部分代码...
    }
}
复制代码

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

doRegister()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のServerSocketChannel中央の学生です。

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

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

ネイティブJDKレジスタ()メソッドを呼び出すときに、第三のパラメータは、この中に渡されることに注意してくださいは、NioServerSocketChannel、この場合は、表現現在の主題です。これは、そうすることの利点は、バックチャネルマルチプレクサセレクタは、サービスの終了に得ることができるということです、マルチプレクサセレクタへの添付ファイルとして保存されます。渡された2番目のパラメータは、この時間は、サービスサイドチャネルマルチプレクサに登録されることを示し、0であり、サーバchennelに関心イベント識別子は、いずれにしても、この時点で関心がないこと、0です。(本当にサーバーチャネル上のリスニングポートの後にイベントを受け取ることに興味があるようになりました)。

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

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        // 只有在第一次注册时候才会执行这儿的逻辑
        // 回调所有Handler的handlerAdded()方法
        callHandlerAddedForAllHandlers();
    }
}
复制代码

最初の登録時に、チャンネルは、実行されます場合にのみ、callHandlerAddedForAllHandlers()この方法を。コアロジックcallHandlerAddedForAllHandlers()に。

private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;

        // 该通道本身已注册。
        registered = true;

        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
        this.pendingHandlerCallbackHead = null;
    }
    // pendingHandlerCallbackHead属性的值是什么时候被初始化的?
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();
        task = task.next;
    }
}
复制代码

callHandlerAddedForAllHandlers()我々はメソッドのソースコードを見つけることができ、そのコアロジックはこれですwhile(task!=null)、タスクのリサイクルを行い、サイクル。タスクが初期値であるthis.pendingHandlerCallbackHead、すなわち、DefaultChannelPipeline.pendingHandlerCallbackHead質問があるので、pendingHandlerCallackHeadが初期化プロパティの値がどのようです。(そして、それはIDEAのデバッグ機能を再生されます、コードはジャンプの多様となり、無知を強制するために、この時間を開始します)

ではinitAndRegister()、プロセス、呼び出し、init(channel)本論文では、()、メソッドを初期化(チャネル)メソッドでpipeline.addLastによってパイプラインに匿名クラスのChannelInitializerタイプを追加し”上篇回顾“、今、このセクションでは、このステップは非常に重要であることに言及それがどれだけ重要です。

結局DefaultChaannelPipelineにpipeline.addLast()メソッドの呼び出し呼び出すときaddLast(EventExecutorGroup group, String name, ChannelHandler handler)の方法を以下のように、メソッドのソースコードです。

public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        newCtx = newContext(group, filterName(name, handler), handler);
        addLast0(newCtx);

        // 默认为false,当为false时,表示的channel还没有被注册到eventLoop中
        if (!registered) {
            //判断handlerState属性等于0  并且设置为1
            // pending:待定,听候
            newCtx.setAddPending();
            // 将ChannelHandlerContext封装成一个PendingHandlerCallback(实际上就是一个Runnable类型的Task)
            // 然后添加到回调队列中
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        //返回NioEvenGroup
        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}
复制代码

上記のコードでは、addLast0(newCtx)それは実際にパイプライン内のハンドラに追加しますが、添加終了後、だけでなく、メソッドを実行することです:callHandlerCallbackLater(newCtx, true)メソッド名の変換は次のようになります。コールバックコールバックメソッドのハンドラの後の時点で。下記をご覧callHandlerCallbackLater(newCtx, true)指定された論理の方法。

private void callHandlerCallbackLater(AbstractChannelHandlerContext ctx, boolean added) {
    assert !registered;
    // added为true
    PendingHandlerCallback task = added ? new PendingHandlerAddedTask(ctx) : new PendingHandlerRemovedTask(ctx);
    PendingHandlerCallback pending = pendingHandlerCallbackHead;
    if (pending == null) {
        pendingHandlerCallbackHead = task;
    } else {
        // Find the tail of the linked-list.
        while (pending.next != null) {
            pending = pending.next;
        }
        pending.next = task;
    }
}
复制代码

パラメータCTXはそれが何ですか?それはChannelInitializerの前に、我々はAbstractChannelHandlerContextオブジェクトに匿名のパッケージを作成し、このクラスであり、この時点で追加の着信パラメータが真です。だから、タスク市から出て作成しPendingHandlerAddedTask(ctx)、その後、我々はpendingHandlerCallbackHeadプロパティを作成するには、最後の割り当てタスクを見つけることができます。

以前に戻るcallHandlerAddedForAllHandlers()の実行ので、方法は、我々は、値がPendingHandlerAddedTask pendingHandlerCallbackHeadプロパティ(CTX)であることを知っているtask.execute()時間が、実行はPendingHandlerAddedTaskオブジェクト()を実行しています。execute()メソッドの呼び出しでcallHandlerAdded0(ctx)、方法、呼び出しctx.callHandlerAdded()、オブジェクトがAbstractChannelHandlerContextのオブジェクトとしてChannelInitializer CTX匿名クラスです。フォローアップしていきctx.callHandlerAdded()、最終的に見つけ、それが最終的にhandlerAdded()メソッドハンドラオブジェクトを呼び出し、ソースコードの方法を。ここでは、最後に見つかったhandlerAdded()場所のコールバックメソッドがあります。これまでのところ、pipeline.invokeHandlerAddedIfNeeded()この方法の最終実行が終了しました。

バックレジスタ0()メソッドは、pipeline.invokeHandlerAddedIfNeeded()この方法の実行が完了すると、コードが実行されるまで実行pipeline.fireChannelRegistered()我々は前述の第三のステップです。行うにはこのステップは、それが広がってどのように、スプレッドチャンネルがイベントを登録しているのですか?ハンドラ内のパイプラインに沿って第1のノードの始まりである、ハンドラは、次の順番毎に行うchannelRegistered()方法。

ステップバイステップのフォローアップを通じて、我々は見ることができますpipeline.fireChannelRegistered()AbstractChannelContextHandlerオブジェクトへの最後の呼び出しをinvokeChannelRegistered()次のようにメソッドのソースです。

private void invokeChannelRegistered() {
    if (invokeHandler()) {
        try {
            // handler()就是获取handler对象
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRegistered();
    }
}
复制代码

ハンドラは、()を取得しますどこに現在のAbstractChannelContextHandlerオブジェクトは、先に作成したなどとして、ハンドラオブジェクトをラップしていますChannelInitializer的匿名类その後、コールハンドラオブジェクトのchannelRegistered(this)メソッドなので、最終的に呼び出し方法。ここでは、ChannelInitializerの匿名クラスを見ての方法は、何をすべきかを行っています。ChannelInitializer的匿名类channelRegistered(this)channelRegistered(this)

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    // Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
    // the handler.
    // 主要看initChannel(ctx)方法的逻辑
    if (initChannel(ctx)) {
        // we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
        // miss an event.
        ctx.pipeline().fireChannelRegistered();

        // We are done with init the Channel, removing all the state for the Channel now.
        removeState(ctx);
    } else {
        // Called initChannel(...) before which is the expected behavior, so just forward the event.
        ctx.fireChannelRegistered();
    }
}
复制代码

あなたは、それが最初に呼び出されます見ることができるinitChannel(ctx)方法を、次に呼び出しctx.pipeline().fireChannelRegistered()またはctx.fireChannelRegistered()メソッド)それが(パイプラインでchannelRegistered実行ハンドラを広めるために継続することで、これらの2つの方法の後ろには、メソッド名で参照することができ、あなたがルックアップすることができません。ここでの焦点initChannel(CTX)メソッドを見てください。注:パラメータタイプinitChannelこちら(CTX)メソッドがあるChannelHandlerContextタイプ、後ろにinitChannel(チャネル)方法があるでしょう、それはパラメータの型でChannel、具体的には約ここで思い出した、2つのオーバーロードされたメソッドを混同しないでください。

次のようにソースinitChannel(CTX)方法があります。

private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
    if (initMap.add(ctx)) { // Guard against re-entrance.
        try {
            //ChannelInitializer匿名类的initChannel(channel)的方法 将在这里被调用
            initChannel((C) ctx.channel());
        } catch (Throwable cause) {
            // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
            // We do so to prevent multiple calls to initChannel(...).
            exceptionCaught(ctx, cause);
        } finally {
            //删除此节点
            ChannelPipeline pipeline = ctx.pipeline();
            if (pipeline.context(this) != null) {
                pipeline.remove(this);
            }
        }
        return true;
    }
    return false;
}
复制代码

これはinitChannel(CTX)メソッド、最初の呼び出しinitChannel(チャネル)方法で見ることができます。ChannelInitializer匿名クラスは、以前に作成したので、この時間はinitChannel(チャネル)メソッドを書き換えるために呼び出されるので、initChannel(チャネル)メソッドを書き換えます。ショットによって与えられたクラスのインスタンスを作成するときに見やすいように、ChannelInitializer匿名のコードを以下に示します。

initChannel(チャンネル)

initChannel(チャネル)書き換え方法では、我々は()ハンドラサーバに設けられ、その後によって主にXianxiangパイプラインを追加することを確認することができch.eventLoop().execute()、パイプラインに非同期でコードの行ハンドラのServerBootstrapAcceptorタイプを追加し、ハンドラは、ハンドラへのクライアントアクセスを担当しServerBootstrapAcceptorの背後にある非常に重要です。

intiChannel(チャネル)をコールバックするとき、そして今ようやく知っている:ここでは最後に、資料の冒頭に2番目の質問にため息をつくことができます。

しかし、それはまだ終わっていません。戻るinitChannel(CTX)メソッドは、我々は最終的にブロックして、あること、ChannelInitializer匿名クラスが表すハンドラは、パイプラインから外し、非常に重要な操作をしたことがわかりました。この匿名クラスの存在の目的は、意志だけでサービスチャネルの初期化と登録プロセス、その初期化パイプラインの構造、そして今サーバーチャネルの初期化と登録が完了していると、サーバーチャネルで最後になるからですサービスは、初期化時間を開始したときに、そのパイプラインから削除されるように、匿名の存在意義の背面に直面していない、最終パイプライン内のサーバーのチャネル構成図は次の通りです。

図のパイプライン構造。

これまでのところ、分析のinitAndRegister()メソッドは最終的に終わりました。

概要

  • 本稿では、JDK NIOパッケージの最後の呼び出しであるプロセスチャネルマルチプレクサにinitAndRegister()メソッド、すなわち、登録サーバの後半を解析し、マルチチャネルへのServerSocketChannel方法、登録サーバに登録しますマルチプレクサのセレクタ。
  • 次いで、物品は、コードを介して段階的には、以下の詳細な説明ChannelInitializer匿名クラスのインスタンスinitChannel(チャネル)は、最終的にはチャネル内のサーバのパイプライン構成を形成し、コールバック処理です。
  • これまでのところ、ネッティーサーバーチャネルの初期化と登録が完了しているが、サーバーの起動プロセスが最終段階残して、まだ終わっていません:サーバーチャネルとポート番号、ポートバインド処理、次の記事の分析を結合します。

勧告

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

おすすめ

転載: juejin.im/post/5df50f5851882512523e7ba4