ポートチャネルのバインディングネッティーシリーズサーバーのソースコード解析

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

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

問題

:この記事は、次の二つの記事を読む行くことができます興味を持っている友人たちによって書かれた最初の二つの記事が続いているソースコード解析サーバチャンネルの初期化のネッティーシリーズソースコード解析サーバチャネルレジスタのネッティーシリーズ

ネッティーはNIOパッケージネイティブJDKがあるので、JDKのネイティブコントラストNIO文言は、まず、次の二つの質問を検討することができます。

  1. ネイティブで書かれたJDKでNIOは、呼び出すserverSocketChannel.bind(新規たInetSocketAddress(ポート))バインドポート番号、そしてネッティー、ポート番号をバインド操作は、それを達成するためにどこかにありますか?

  2. ネイティブで書かれたJDK NIOでは、マルチプレクサのServerSocketChannelセレクタまでの時間レジスタに、関心のServerSocketChannelのイベントのために提供されますOP_ACCEPTのイベント、およびネッティーに、ときチャネルは、関心のあるチャネルにセレクタを登録しますイベントセットがある0任意のイベントに興味を持っていないこと、。サーバー・チャネル・イベントが設定されている場合次にネッティーで、関心のものですOP_ACCEPTそれ?

JDKのネイティブNIOの文言は、以下の通りです。

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 轮询器,不同的操作系统对应不同的实现类
Selector selector = Selector.open();
// 绑定端口
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
// 将服务端channel注册到轮询器上,并告诉轮询器,自己感兴趣的事件是ACCEPT事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
复制代码

ポートのバインディング

あなたは呼び出すとserverBootstrap.bind(ポート)、への呼び出しAbstractBootstrap.doBind(最終のSocketAddressをlocalAddress)メソッドを。doBind(をlocalAddress)の方法では、最初のサーバー・チャネルと以前に詳細なソースコード解析されたマルチプレクサに登録サーバーチャネル内の2件の記事初期化するinitAndRegister()メソッドを呼び出す網状ソースコード分析チャネルシリーズサーバの初期化ネッティーソースコード解析サーバーチャネルシリーズは、登録分析し、今日では、分析が続きます(をlocalAddress)doBindのロジックの背後にあります。ソースdoBind(をlocalAddress)メソッドは次のよう

private ChannelFuture doBind(final SocketAddress localAddress) {
    //初始化和注册
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }
    // 无论是进入if还是进入else,最终均会执行doBind0()
    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                // cause!= null 表示channel在初始化和注册过程中出席那了异常
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    // 没有异常,执行doBind0()
                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}
复制代码

initAndRegister()の実行が完了すると、それが返されますChannelFutureを:ChannelFutureは(この記事を参照することができます将来の詳細については今後のインタフェースを実装する-最も一般的に使用されるパフォーマンス最適化ツール並行プログラミングシリーズ将来、コール)ChannelFuture.isDone() 実装されていないメソッドが完了するinitAndRegisterを()を決定することができますので、 initAndRegister initAndRegister()メソッド戻り、それは必ずしも実行完了内部ロジックを有していない非同期実行スレッド他のロジックを使用して()メソッド)。

initAndRegister()メソッドが完全に終了行われた場合、論理進み、ブロックし、次に呼び出す場合)doBind0を( ; initAndRegister()が実行されない場合は、他に論理ブロックに。実行された場合、実行時に他のリスナーを追加することにより、initAndRegisterを聞くために()、、リスナーが実行を完了した直後に()initAndRegisterを知ることができるようになり、その後、まだ呼び出しdoBind0()メソッドを。

それは論理ブロック、または他の論理ブロックの中にあるかどうかをその場合は、最終的に呼び出しているdoBind0()メソッドを。次のようにソースdoBind0()メソッドです。

private static void  doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {
    /**
     * 此时的channel就是NioServerSocketChannel
     * channel.eventLoop()获取到的就是服务端channel绑定的NioEventLoop线程
     */
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
            	// 调用NioServerSocketChannel的bind()方法
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}
复制代码

このとき、チャネルは(initAndRegister()メソッドに結合した)スレッドを結合サーバーチャネルNioEventLoop取得さNioServerSocketChannel、channel.eventLoop()の一例です。従ってdoBind0()メソッドはNioEventLoop非同期実行スレッドを介して実際に)(channel.bind、ソースchannel.bind()メソッドは次の通りです。

public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}
复制代码

channel.bind()、それは実際にチャンネル()メソッドでバインドパイプラインへの呼び出しです。次のようにソースpipeline.bind()メソッドです。

public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}
复制代码

pipeline.bind()メソッドは、パイプラインは、テールノードバインド()メソッドと呼ばれています。tail.bind()メソッドでは、それだけで(ここでテール・ノードから前方のパイプラインを探し始める)ので、最終的には、ヘッドノードに呼び出されるバインド()メソッド内の次のノードでのパイプラインを呼び出して、他の処理ロジックを実行しませんバインド()メソッド。ソースとしてバインドヘッドノード()メソッド。

public void bind(
        ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
    unsafe.bind(localAddress, promise);
}
复制代码

サーバーチャネルの目的のために、安全でないここにあるNioMessageUnsafeのタイプ(インスタンス化され、安全でないNioServerSocketChannelにおけるコンストラクタ)のインスタンス。バインドNioMessageUnsafeクラス()メソッドがAbstractChannelで定義され、以下のように、合理ソースコードです。

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    // 省略部分代码...

    // 此时由于服务端channel还没哟绑定端口号,所以isActive()返回的是false
    boolean wasActive = isActive();
    try {
        // 真正绑定端口号的操作
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
    // 经过doBind(localAddress)这一步,服务端channel已经绑定了端口号,所以isActive()会返回true
    // !wasActive && isActive() 运算的结果为true
    if (!wasActive && isActive()) {
        // 通过线程异步执行任务:在pipeline中传播执行handler的channelActive()方法
        invokeLater(new Runnable() {
            @Override
            public void run() {
            	// 通过pipeline来传播执行handler的channelActive()方法
                pipeline.fireChannelActive();
            }
        });
    }

    safeSetSuccess(promise);
}
复制代码

これは、バインド()メソッドで見ることができ、doBind(をlocalAddress)メソッドを呼び出し、メソッドは通常、メソッドの開始時に行いますので、本当のことをやっている、全体のことについて最終的に幸せなメソッド名の先頭を見ています。実用的な事実、doBindで()メソッドは、バインディングポート番号の操作を行います。次のようにソースdoBind NioServerSocketChannelクラス()メソッドです。

protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}
复制代码

どのjavaChannel() JDKのネイティブチャネル、すなわちのServerSocketChannelを取得し、その後、JDK NIOネイティブAPI呼び出し:バインド(localAddressでは、バックログ)、このステップは、仕上げは、チャネルネッティーはそれをサービス側とポート番号にバインド達成するために実行され、記事の冒頭で最初の問題を解決します。しかし、第二の問題は、まだそう振り返るし続け、解決していません。

この場合doBind()メソッドの実装後に、バックAbstractChannel.bind()メソッドは、サーバーのチャンネルはそう、バインドされたポート番号となっているので!WasActive &&のisActive() TRUEと評価に、もしそうならに入力されます論理ブロック。論理ブロック、およびの非同期実行した場合pipeline.fireChannelActive() あなたはメソッドの名前で推測することができ、このステップでは、パイプラインで実行ハンドラの普及chanelActive()メソッドで何かをやっています。pipeline.fireChannelActive()ソースコードの次の通りです。

public final ChannelPipeline fireChannelActive() {
    // 从head节点开始传播
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}
复制代码

あなたは、見ることができますinvokeChannelActive(頭が)最終的にchannelActiveハンドラの(ChannelHandlerContext CTX)への呼び出し、ヘッドノードの広がりから、パイプラインの実装を開始します。channelActive(CTX)メソッドノード以下ルックヘッドコードは次のようにHeadContext.channelActive(CTX)、そのソースコードが見つかったので、ヘッドノードは、HeadContext型です。

public void channelActive(ChannelHandlerContext ctx) {
    // 将channelActive时间传播给pipeline中下一个handler
    ctx.fireChannelActive();
    // 如果服务端channel的autoRead属性为1,就表示自动读数据,那么这个时候,就开始读数据
    // 默认情况下,autoRead属性为1,即开启自动读功能
    readIfIsAutoRead();
}
复制代码

2行のコードは、最初のコードのすべての最初の行の、もはや4つのノードのサービス側のためのパイプライン、実際には、参照する必要がない(読むためにそこ続ける、上のパイプライン実行channelActive(CTX)メソッドで普及に継続しないことです他のchannelActive少数のノード(CTX)メソッドは、重要なロジック処理をしないので、見ることができません)。

コードの2行目で見て、それが呼び出すreadIfIsAutoRead()メソッドを、メソッド名の変換は次のようになります。状態が自動的に読み込まれている場合、彼らは、デフォルトでは、自動的にスイッチが、デフォルトの開始読みになって読んで読み始めます。ここではそれが正確に何を読み取るために、少し無知力もありか?フォローアップしていき、私たちはそうreadIfIsAutoRead()ソースメソッドを。

private void readIfIsAutoRead() {
    // 默认自动读
    if (channel.config().isAutoRead()) {
        // 读数据
        channel.read();
    }
}
复制代码

ように自動読取りデフォルトので、1に等しいchannel.config()。IsAutoRead()が trueを返すので、論理ブロック、コールchannel.read()メソッドを入力した場合、。この場合、サーバーチャネルは、そうコールがNioServerSocketChannelは、read()メソッドです。AbstractChannelで定義された方法は、ソースは以下の通りです。

public Channel read() {
    // 从pipeline中开始传播,一次调用pipeline中的handler的read()方法
    pipeline.read();
    return this;
}
复制代码

FMLは、read()メソッドは、パイプラインに沿って普及に実行されます、パイプライン、レッスンは、私たちは推測することができました。そして、パイプラインのread()メソッドは、以下のソースを見てください。

public final ChannelPipeline read() {
    tail.read();
    return this;
}
复制代码

それが発見されたテール・ノードは、私は、ソースコードを見ているので、最終的には既知のパイプラインに加えて、再生ヘッドノード()メソッドを実行する、read()メソッドを呼び出し、その後、前方テイルノード行うリード()に沿って伝播します外側のヘッドノード、他のノードを読み取り()メソッドは、かなりの処理ロジック、ヘッドノードジャンプ直接読み取る()メソッドを実行しませんでした。そのソースコードは次の通り。

public void read(ChannelHandlerContext ctx) {
    // 对于服务端的channel而言,unsafe的值为NioMessageUnsafe类型的实例
    // 对于客户端channel而言,unsafe的值为NioSocketChannelUnsafe类型的实例
    unsafe.beginRead();
}
复制代码

読み取りヘッドノード()メソッドが呼び出されますには、見ることができ、安全でないBeginRead属性()メソッド。チャネルサーバは、安全でない値NioMessageUnsafeのタイプのインスタンス、クライアントチャネル、安全でない値NioSocketChannelUnsafeタイプの例。ここでは、サーバーのチャンネルがあるので、そうbeginReadのNioMessageUnsafe()メソッドを呼び出します。次のようにAbstractUnsafeクラスで定義された方法は、そのソースコードがあります。

public final void beginRead() {
    assertEventLoop();

    if (!isActive()) {
        return;
    }

    try {
        // 真正开始读数据
        doBeginRead();
    } catch (final Exception e) {
        invokeLater(new Runnable() {
            @Override
            public void run() {
                pipeline.fireExceptionCaught(e);
            }
        });
        close(voidPromise());
    }
}
复制代码

彼らはするコードで見ることができません方法で始まる:)doBeginRead( 次のようにソースコード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属性的值,是在NioServerSocketChannel的构造器中,被设置为SelectionKey.OP_ACCEPT,即16
     * public NioServerSocketChannel(ServerSocketChannel channel) {
     *    super(null, channel, SelectionKey.OP_ACCEPT);
     *    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
     * }
     */
    if ((interestOps & readInterestOp) == 0) {
        // 对于服务端channel而言,interestOps | readInterestOp运算的结果为16,即SelectionKey.OP_ACCEPT
        // 所以最终selectionKey感兴趣的事件为OP_ACCEPT事件,至此,服务端channel终于可以开始接收客户端的链接了。
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}
复制代码

最初とSelectionKey得るためにinterestOpsのプロパティの値を、属性値は0なぜ?サーバはマルチプレクサチャネルに登録されたときなので、渡された値である0図に示すように。

登録()

次いで、得られたreadInterestOpのこのプロパティの値は16、即ちSelectionKey.OP_ACCEPTなぜ?コンストラクタNioServerSocketChannelで、それに割り当てられた値であるためSelectionKey.OP_ACCEPT図に示すように。

コンストラクタ

最後に、OR:interestOps | readInterestOp GETの結果は、16であること、OP_ACCEPT、その結果がに設定されているとSelectionKeyこのイベントに興味がある、サーバーのチャンネルであるOP_ACCEPTのイベントなので、新しいクライアントが接続したときに、サーバーチャネルこれは、資料の冒頭に2番目の質問に答えるとSelectionKey、によって知覚することができます。

ここでは、最終的に完成プロセスネッティーサーバーのソースコードの解析を開始します。ため息、あまりにTMは複雑!我々はネッティーを使用していますが、本当にそれは、コードが特に簡単であるが、その根底にあるロジックが非常に複雑であるとき、静かな良いの年は、それはラインをロードする前に、あなたのためだったかそう

概要

  • 本論文では、サーバーチャネルとポートをバインドする方法、プロセスネッティーを分析し、ネッティーはどのようにサーバーチャネルのイベントに関心があるOP_ACCEPTの。
  • 前の二つの記事と合わせてソースコード解析サーバチャンネルの初期化のネッティーシリーズ登録された網状のソースコード解析サーバチャネルシリーズ、これまでのところ、記事の裏に行わネッティーサーバの起動プロセス分析は、ソースコードを分析し、新しい接続へのアクセスをコンパイルします。ソース復号化処理。
  • 元はあなたにこの記事が参考に思うなら、あなたが感謝し、投稿での広告をクリックすることができ、ここを参照してください、簡単ではありません。

勧告

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

おすすめ

転載: juejin.im/post/5df622386fb9a015ff64e714