新しい接続元アクセスの解釈Netty-

新しい接続とアクセス、ネッティーどのような状態になる前に新しい接続へのアクセスは何ですか?

ネッティーのサーバーのNioServerSocketChannel初期設定に登録BossGroup NioEventLoop、とするNioServerSocketChannelネイティブの維持をjdk ServerSocketChannel良いポート後バインドは、イベントループは、ポーリング作業を開始します...

それは実際には、それはNioServerSocketChannelがポーリング関心の二次登録イベントを通じて聞いていた何?ポーリングイベントループ今回は、セレクタは、表示されることがあり、自分の体を心配セレクタせて語ったOP_ACCEPT、理にかなっているイベントを、原子炉からの主ネッティー理由スレッドモデルは、BossGroupチャネル唯一の懸念は、OP_ACCEPT接続イベントを確立するために、ユーザの要求であります

どのような仕事の新しい接続ネッティーアクセス?

次の手順でリングIモデルのうち、図の空想、ネッティー新しいアクセス接続、スレッドの対応する部分

  • セレクタは、接続を確立するために、クライアントの要求にサーバーをポーリング
  • リクエストを処理しています
    • ()クライアントチャネルにおけるサーバメンテナンスJDKネイティブのServerSocketChannelから受け入れます
    • チャンネルNioSocketChannelに新しいクライアントパッケージを使用する方法
      • コールスーパー()、初期チャネルコンポーネントアップ層
      • クラスオブジェクトの設定チャンネルの構成を作成します。
    • スプレッドchannelReadイベントダウン
      • クライアントセットアップパラメータをチャネルに
      • イベントループを選択したポーリングアルゴリズムで、クライアントのworkerGroupに登録チャンネル
      • セレクタに登録されたイベントループにJDKのネイティブSocketChanel
      • スプレッドchannelregistイベント
      • channelActiveイベントを広めます
        • イベントを処理することができ、クライアントの二次登録金利ネッティーをチャネルに

これは、マークがはるかに扱うことができるネッティーをシャネルなり、イベントループ上のクライアントNioSocketChannel二レジスタ、終了し、上記の新しいリンク解析をチェックするために最初からアクセスプロセスの私の要約への新しい接続であり、

エントランス:NioEventLoopIOイベントを処理

サーバは、イベントIOイベントループが使用することを検出した場合processSelectedKeys();、処理は、次のソースコード:

private void processSelectedKeys() {
        // todo  selectedKeys 就是经过优化后的keys(底层是数组) , 默认不为null
        if (selectedKeys != null) {
            processSelectedKeysOptimized();
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
}

新しいIOの要求が到着すると、JDKのネイティブとSelectionKeyの選択キーが関心のコレクションに格納され、これは今反射アレイは、データ構造に置き換えられて強制的に網状に設定されているselectedKeys、配列は、空ではありませんフォローアップprocessSelectedKeysOptimized();以下で記述されたソースコードを解析:以下のように、ソースコードは、次のとおりです。

private void processSelectedKeysOptimized() {
for (int i = 0; i < selectedKeys.size; ++i) {
    final SelectionKey k = selectedKeys.keys[i];
    // null out entry in the array to allow to have it GC'ed once the Channel close
    // todo 数组输出空项, 从而允许在channel 关闭时对其进行垃圾回收
    // See https://github.com/netty/netty/issues/2363
    // todo 数组中当前循环对应的keys质空, 这种感兴趣的事件只处理一次就行
    selectedKeys.keys[i] = null;

    // todo 获取出 attachment,默认情况下就是注册进Selector时,传入的第三个参数  this===>   NioServerSocketChannel
    // todo 一个Selector中可能被绑定上了成千上万个Channel,  通过K+attachment 的手段, 精确的取出发生指定事件的channel, 进而获取channel中的unsafe类进行下一步处理
    final Object a = k.attachment();
    // todo

    if (a instanceof AbstractNioChannel) {
        // todo 进入这个方法, 传进入 感兴趣的key + NioSocketChannel
        processSelectedKey(k, (AbstractNioChannel) a);
    } else {
        @SuppressWarnings("unchecked")
        NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
        processSelectedKey(k, task);
    }

    if (needsToSelectAgain) {
        // null out entries in the array to allow to have it GC'ed once the Channel close
        // See https://github.com/netty/netty/issues/2363
        selectedKeys.reset(i + 1);

        selectAgain();
        i = -1;
    }
}
}

?興味のあるイベントを処理する、彼はさらに処理は以下の2点が必要に行う必要があるかについて考えます:

  • 興味のあるイベントは何ですか?
    • これは、上位k個であります
  • チャンネルが表示された関心セレクタのイベント?
    • ここでは、実際には、ないサービス側というの添付ファイルを介して取得することですかNioServerSocketChannel

また、それは置くNioServerSocketChannelに強いターンアップAbstractNioChannelこれはなぜ?


最初のポイント:
私はシャネルのアーキテクチャー・システム上での書き込みは、我々は、網状のNioXXXChannel実際ネッティーベースのシャネルのパッケージJDKのネイティブ、彼の継承階層全体では、これは知っているAbstractNioChannelだろうチャンネルJDKのネイティブを維持する責任が、このDIMを知っている?はい、もちろん、私たちはネイティブサーバchannel.acceptは()==クライアントチャネルのクライアントチャネルに配信つもりです

第二の点:
?データの読み書きが安全でないため、リコールは抽象クラステンプレート関数はIOデータチャネル、それを読み込む定義するものですAbstractNioChannel、それは新しい内部インタフェースであり、したがって、クライアントのためのサービスに読み取りのための異なる実装の異なる専門分野をシャネル

さて、これら二つの条件で、フォローアップしていきprocessSelectedKey(k, (AbstractNioChannel) a);、それがどのように処理されるか確認するために、次のように、ソースコードは次のとおりです。

  • 取得された安全でないオブジェクト(データ読み出し)サービス側
  • readOps K、算出された実行の決定unsafe.read();
// todo 服务端启动后,方法被用用处理新链接,  可以模拟 telnet localhost 8899 新链接的介入
// todo 处理selectedkey
// todo netty底层对数据的读写都是  unsafe完成的
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
// todo 这个unsafe 也是可channel 也是和Channel进行唯一绑定的对象
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {   // todo 确保Key的合法
    final EventLoop eventLoop;
    try {
        eventLoop = ch.eventLoop();
    } catch (Throwable ignored) {
        return;
    }
    if (eventLoop != this || eventLoop == null) { // todo 确保多线程下的安全性
        return;
    }
    unsafe.close(unsafe.voidPromise());
    return;
}
// todo NioServerSocketChannel和selectKey都合法的话, 就进入下面的 处理阶段
try {
    // todo 获取SelectedKey 的 关心的选项
    int readyOps = k.readyOps();
    // todo 在read()   write()之前我们需要调用 finishConnect()  方法, 否则  NIO JDK抛出异常
    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( );

        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();
    }
    // todo 同样是检查 readOps是否为零, 来检查是否出现了  jdk  空轮询的bug
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
        unsafe.read();
    }
} catch (CancelledKeyException ignored) {
    unsafe.close(unsafe.voidPromise());
}
}

ここでは、入力unsafe.read();に直接移動、中に直接ジャンプをAbstractNioChannel次のように必須の型変換を構成して、上記の言ったように、抽象インナークラス、我々はソース:

    /**
     * Special {@link Unsafe} sub-type which allows to access the underlying {@link SelectableChannel}
     */
    public interface NioUnsafe extends Unsafe {
        /**
         * Return underlying {@link SelectableChannel}
         */
        SelectableChannel ch();

        /**
         * Finish connect
         */
        void finishConnect();

      
        void forceFlush();
    }

誰実装は、我々は、チャネルのサービス側であるためであるので、実装クラスは:?でNioMessageUnsafe、彼のソースを参照入力します。次のコードは実際にはかなり長いですが、私は次のように彼の決意でそれを書きました:

@Override
public void read() {
    // todo 同样是断言, 当前的线程必须是在 EventLoop  里面的线程才有资格执行
    assert eventLoop().inEventLoop( );
    final ChannelConfig config = config();
    final ChannelPipeline pipeline = pipeline();
    // todo 用于查看服务端接受的速率, 说白了就是控制服务端是否接着read 客户端的IO事件
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    allocHandle.reset(config);

    boolean closed = false;
    Throwable exception = null;
    try {
        try {
            do {
                // todo 进入
                int localRead = doReadMessages(readBuf);
                if (localRead == 0) {
                    break;
                }
                if (localRead < 0) {
                    closed = true;
                    break;
                }
                //todo 对读到的连接,进行简单的计数
                allocHandle.incMessagesRead(localRead);
            } while (allocHandle.continueReading());
        } catch (Throwable t) {
            exception = t;
        }

        int size = readBuf.size();
        for (int i = 0; i < size; i ++) {
            readPending = false;
            // todo 处理新的连接的逻辑来到这, 意思是让pipeline中发生事件传播,
            // todo pipeline是谁的呢?  现在是NioMessageUnsafe  所以是服务端的,
            // todo 事件是如何传播的呢?  head-->>ServerBootStraptAcceptor-->>tail 依次传播,
            // todo 传播的什么事件?  ChannelRead,  也就是说,会去调用 ServerBootStraptAcceptor的ChannelRead方法,跟进去
            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();
        }
    }
}
}

()三部作をお読みください。

このコードでは、我々は、次のセクションの値についての三つの部分の終わりを気にし、新しいリンクの確立の全体が完了し、
次は三部作の主要な前提は、我々はに現在ありますAbstractNioMessageChannel

  • doReadMessages(readBuf)
  • allocHandle.incMessagesRead(localRead);
  • pipeline.fireChannelRead(readBuf.get(i));

最初のステップ:

クライアントチャネルのJDKのネイティブを作成する方法、それが何をしましたか?

最初のステップdoReadMessages(readBuf)これはAbstractNioMessageChannel?私たちはそのサブクラスを行う専門chanenlは引用対象を、維持する必要がシャネルから読み込まれ、抽象メソッドNioServerSocketChannel、などのソースコードは次のとおりです。解像度は、まだ次のコードで書くことができます

//todo doReadMessage  其实就是 doChannel
// todo 处理新连接, 现在是在 NioServReaderSocketChannel里面
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    // todo java Nio底层在这里 创建jdk底层的 原生channel
    SocketChannel ch = SocketUtils.accept(javaChannel());

    try {
        if (ch != null) {
            // todo  把java原生的channel, 封装成 Netty自定义的封装的channel , 这里的buf是list集合对象,由上一层传递过来的
            // todo  this  --  NioServerSocketChannel
            // todo  ch --     SocketChnnel
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
        logger.warn("Failed to create a new channel from an accepted socket.", t);

        try {
            ch.close();
        } catch (Throwable t2) {
            logger.warn("Failed to close a socket.", t2);
        }
    }

    return 0;
}

これは、主に以下のステップのように上記のコード作業動作のスパンです。

  • ネイティブJDKからのServerSocketChannelは、ネイティブのJDKを受け入れるSocketChanel
  • そのパッケージタイプにパッケージ化ネイティブJDKネッティーソケット NioChannel

なぜ、チャネルサーバは、作成するためにリフレクションを必要とし、顧客のチャネルが新しいダイレクト?

私の理解では、IO、または彼が行うことができます他のタイプのサーバをブロックし、古い、ネッティーだけでなく、サーバーNIOプログラミングモデルを行う、である、我々は受信サーバの種類を渡すシャネルは、彼がサーバーになることができるタイプを決定し、ネッティーデザイナーはそれほど反射によって作成するよう設計され、ユーザーが網状に何をしたいのか分かりません

サーバは、チャネルの種類を決定したら、しかし、チャネル対応クライアントは、新しいダイレクトのように、知っている必要があります

NioSocketChannel作成プロセス

私たちは、フォローアップnew NioSocketChannel(this, ch)と、これは、サーバー側である、読み続けるNioServerSocketChannel次のように、CHは、JDKのネイティブたSocketChannel、呼び出しチェーンの源であります:

public NioSocketChannel(Channel parent, SocketChannel socket) {
    // todo 向上传递
    super(parent, socket);
    // todo 主要是设置 禁用了 NoDelay算法
    config = new NioSocketChannelConfig(this, socket.socket());
}

内部に、表情で、彼はSelectionKey.OP_READそれはとてもcannel二次登録は後で、このパラメータを使用するだろう、彼の親に渡されたNioSocketChannel網状それが起こったイベントを処理するために、興味のある、我々は、重要なサーバーやシャネルを見つけました違いは、ユーザーのサーバー側にNioChannelの注目を受け入れ、で、イベントを読んで、クライアントが関係しているチャンネルは、それはマーク、セレクタサーバーは、セレクタが読むべき伝え、クライアントから送信されたデータに渡され、それを気にします

protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
    super(parent, ch, SelectionKey.OP_READ);
}

フォローアップしていきAbstractNioChannel、彼は次のような作業を行いました。

  • 親の親にスーパー(親)NioServerSocketChannel NioSokcetChannel
  • 独自のネイティブJDKのSocketChannelを維持
  • エキゾチックオプションの感覚を保存
  • 非ブロックに設定されています

ソースは以下のとおりです。

*/ // todo 无论是服务端的channel 还是客户端的channel都会使用这个方法进行初始化
// // TODO: 2019/6/23                null        ServerSocketChannel       accept
// todo  如果是在创建NioSocketChannel  parent==NioServerSocketChannel  ch == SocketChanel
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);// todo  继续向上跟,创建基本的组件
    // todo 如果是创建NioSocketChannel   这就是在保存原生的jdkchannel
    // todo 如果是创建NioServerSocketChannel   这就是在保存ServerSocketChannel
    this.ch = ch;
    // todo 设置上感兴趣的事件
    this.readInterestOp = readInterestOp;
    try {
        // todo 作为服务端, ServerSocketChannel 设置为非阻塞的
        // todo 作为客户端   SocketChannel 设置为非阻塞的
        ch.configureBlocking(false);

ステップ2:

今NioSocketChannelが作成された、およびコールスタックコードは、上記に戻るNioMessageUnsafe.read()方法、我々はそれから見下ろします

//todo 对读到的连接,进行简单的计数
    allocHandle.incMessagesRead(localRead);

第三段階 pipeline.fireChannelRead(readBuf.get(i));

スプレッドchannelRead()ダウン、今、彼はパイプラインがどのような状態でいます、サーバーのために、パイプラインでイベントチャネルを渡しますか?

ヘッダー - > ServerBootStraptAcceptor - >テール

パイプラインコンポーネントのチャンネルそれが何であるかServerBootStraptAcceptorの途中で、デフォルトのリストの頭と尾である頭部と尾に基づいて、双方向リンクリストの実装です?実際に、彼はの作成において、サーバであるNioServerSocketChannel時間は、チャンネル登録が完了された後、コールバックは次のようになりますチャンネルを追加するように機能し、それに追加したときに、本質的ハンドラ、最初の数字は、彼が数字であることを想起ServerBootStrapinit()channelInitializerServerBootStraptAcceptorAcceptor

[OK]を、今、私たちはまっすぐに行くServerBootStraptAcceptor、彼はServerBootStrap次のように、我々はそのchannelRead()メソッドを見て内部クラス、ソースコードは次のとおりです。

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    // todo 给这个来连接的通道添加 childHandler,是我在Server中添加的childHandler, 实际上是那个MyChannelInitializer , 最终目的是添加handler
    child.pipeline().addLast(childHandler);
    // todo 给新来的Channel设置 options 选项
    setChannelOptions(child, childOptions, logger);
    // todo 给新来的Channel设置 attr属性
    for (Entry<AttributeKey<?>, Object> e : childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    try {
        //todo 这里这!!   把新的channel注册进 childGroup
        childGroup.register(child).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {

私たちは、次のように仕事があることがわかります。

  • 初期化プロパティ
  • イベントループ内childGroupチャネルクライアントに登録

ここで継承グラフのチャネル・グループを補完します

私たちはこれを見てchildGroup.regist()私たちが知っている、方法childGroupworkerGroupは、このクラスでは、それがタイプであることをEventLoopGroup、我々はインターフェイスへのビューのソース天然ジャンプに指して、インタフェース変数のタイプである、が、我々は彼がクラスを達成見つける必要があります、誰がそれを行うための方法を書き換えていますか?

私たちは、その直接の実装クラスは一つだけで、マップの上を見に行ってきましたMultiThreadEventGroup実際には、我々はそれを考えると、タスクは今はない最初の?最初のステップはシェーンであることを、イベントループに登録WorkerGroupに、ネイティブクライアントチャネルをもたらすことです、 ?このイベントループ・グループからイベントループを思い付くうまくやって、見に行くMultiThreadEventGroup次のようにソースコードを達成する方法です。

@Override
public ChannelFuture register(Channel channel) {
    // todo  next()  -- 就在上面->  根据轮询算法获取一个事件的执行器  EventExecutor
    // todo, 而每一个EventLoop对应一个EventExecutor   这里之所以是个组, 是因为, 我的机器内核决定我的  事件循环组有八个线程,
    //  todo ?? ????
    // todo 但是一会的责任并没有一直循环, 难道有效的bossGroup只有一条

    // todo 再进去就是SingleThreadEventLoop对此方法的实现了
    return next().register(channel);
}

是的,确实在获取事件循环,我们进行跟进next().register(channel), 现在是 eventloop.regist() ,当我们进入方法时,再次来到EventLoopGroup对这个方法的实现, ok,大家重新去看上面的图,一个eventloop.regist(),现在不再是 循环组.regist 而是 事件循环.regist, 而在图上,我们可以很轻松的看到 , 对EventLoopGroup接口的实现就是 SingleThreadEventLoop, 好,接着进去看它的实现, 源码如下:

// todo register来到这里
@Override
public ChannelFuture register(Channel channel) {
    // todo  ChannelPromise == channel+Executor    跟进去
    // todo   再次调用 register, 就在下面
    return register(new DefaultChannelPromise(channel, this));
}

调用本类的register(new DefaultChannelPromise(channel, this) ,接着进去,源码如下: 同样解析写在源码下面

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    // todo 重点来了
    // todo channel() 获取通道对象
    // todo unsafe()  获取仅供内部使用的unsafe对象   它定义在Channel接口中, 具体的对象是  Channel的子类, AbstractNioChannel
    // todo unsafe对象进行下一步注册 register
    * promise.channel().unsafe().register(this, promise);
    return promise;
}
  • promise.channel() 取出的是客户端的 NioSocketChanenl
  • promise.channel().unsafe()AbstractUnsafe

来到regist()的实现类

方法调用链:

  • 本类方法register
    • 本类方法register0()
      • 本类抽象方法doRegister()
      • pipeline.fireChannelRegistered();传播channel注册事件
      • pipeline.fireChannelActive(); 传播channel Active事件
        • 二次注册事件

其中,上面的doRegister()是真正的将jdk原生的channel注册进原生的selector

pipeline.fireChannelRegistered();是在 header --> ServerBootStraptAccptor --> 用户自己添加的handler --> tail 中,挨个传递 ChannelRegistered, 就是从头开始调用它们的函数, 我们着重看下面的第三个

pipeline.fireChannelActive();其实是比较绕的,涉及到了pipeline中事件的传递,但是它的作用很大,通过传播channelActive挨个回调他们的状态,netty成功的给这条客户端的新连接注册上了netty能处理的感兴趣的事件

整体源码太长了我不一一贴出来了, 直接看关于pipeline.fireChannelActive();的源码,如下:

if (isActive()) {
    if (firstRegistration) {
        // todo 在pipeline中传播ChannelActive的行为,跟进去
        pipeline.fireChannelActive();
    } else if (config().isAutoRead()) {
        // This channel was registered before and autoRead() is set. This means we need to begin read
        // again so that we process inbound data.
        //
        // See https://github.com/netty/netty/issues/4805
        // todo 可以接受客户端的数据了
        beginRead();
    }

第一个判断, if (isActive())针对两个channel,存在两种情况

  • 如果是服务端的channel, 只有在channel绑定完端口后,才会处于active的状态
  • 如果是客户端的channel, 注册到selector+处于连接状态, 他就是active状态

满足条件,进入第一个分支判断,同样满足第一次注册的条件,开始传播事件

回想一下,现在程序进行到什么状态? 看上图的subReactor每一个蓝色的箭头都是一个客户端的channel, 问题是netty还处理不了这些channel上的会发生的感兴趣的事件,因为第一步我们只是把jdk原生的chanel和原生的selector之间进行了关联, 而netty对他们的封装类还没有关联,于是下一步就通过传播active的行为去二次注册关联感兴趣的事件

关于pipeline中的事件传递太多内容了,在下篇博客中写,连载

现在直接给结果,

传递到header的read()源码如下

  @Override
    public void read(ChannelHandlerContext ctx) {
    // todo 如果是服务端: NioMessageUnsafe
    // todo 如果是客户端: NioSocketChannelUnsafe
    unsafe.beginRead();
}

接着跟进

@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
//todo 如果是服务端: 这里的SelectionKey就是我们在把NioServerSocketChannel 注册进BoosGroup中的eventLoop时,返回的selectionKey , 就在上面
//todo 如果是客户端: 这里的SelectionKey就是我们在把NioSocketChannel 注册进BoosGroup中的eventLoop时,返回的selectionKey , 就在上面
final SelectionKey selectionKey = this.selectionKey;
// todo 这SelectionKey 就是我们把 NioServerSocketChannel中的ServerSocketChannel注册进BossGroup时, 附加的第三个参数 0
if (!selectionKey.isValid()) {
    return;
}

readPending = true;
// todo 获取这个Selection 的感兴趣的事件,实际就是当时注册时的第二个参数 0
final int interestOps = selectionKey.interestOps();
// todo 如果是服务端, readInterestOp是创建服务端channel时设置的 op_accept
// todo 如果是客户端的新连接,readInterestOp是创建客户端channel时设置的 op_read
if ((interestOps & readInterestOp) == 0) {
    // todo interestOps | readInterestOp两者进行或运算,原来是0事件 , 现在又增加了一个事件, accept事件或者是read
    // todo 进而 从新注册到SelectionKey上面去。。。 0P_Accept 或者 OP_Read
    selectionKey.interestOps(interestOps | readInterestOp);
}
}

ok, 到这里netty的新链接接入就完成了....
连载下一篇, pipeline中的事件传播

おすすめ

転載: www.cnblogs.com/ZhuChangwu/p/11210437.html