ソースコード分析を作成するパイプラインハンドラーHandlerContext
ChannelPipelineスケジューリングハンドラーソースコード分析
ソースコード分析の目的
Netty ChannelPipeline、ChannelHandlerおよびChannelHandlerContextは非常にコアコンポーネントです。ソースコードから、Nettyがこれらの3つのコアコンポーネントをどのように設計したかを分析し、それらの作成方法と調整方法を分析します。
ChannelPipeline
| ChannelHandler
| ChannelHandlerContext
はじめに
1.1 3つの関係
-
ServerSocketが新しい接続を作成するたびに、ターゲットクライアントに対応するSocketが作成されます。
-
新しく作成された各ソケットには、新しいChannelPipeline(以下、パイプラインと呼びます)が割り当てられます
-
各ChannelPipelineには、複数のChannelHandlerContext(以下、コンテキストと呼びます)が含まれています
-
これらのコンテキストは、addLastメソッドを呼び出すときに追加するChannelHandler(以降、ハンドラーといいます)をラップするために使用されます。
- 上の図では、ChannelSocketとChannelPipelineは1対1の関連付けであり、パイプライン内の複数のコンテキストがリンクリストを形成し、コンテキストはハンドラーのカプセル化にすぎません。
- リクエストが来ると、リクエストはソケットに対応するパイプラインに入り、パイプラインのすべてのハンドラーを渡します、はい、デザインモードのフィルターモードです。
1.2 ChannelPipelineの機能と設計
1)パイプラインのインターフェース設計の
ソースコードの一部
私たちは、反復処理可能なインタフェースは、彼が呼び出すことができるデータを表し、インバウンド、アウトバウンド継承し、このインターフェイスを見ることができる方法は、インバウンドとアウトバウンドの方法を、しかしまた、内部のリストをトラバース基本的には、彼の代表的な方法のいくつかを見るために、これらはすべて、LinkedListと同様に、ハンドラーリンクリストの挿入、追加、削除、および置換操作用です。一方、それはチャンネル(即ちソケット)を返すことができる
。1)パイプライン・インターフェース文書内のマップが提供され、
データ・フロー・パイプラインがスタックにプッシュされ、スタックが流れるパイプラインであります
上の画像の説明:
-
これはハンドラーのリストです。ハンドラーは、インバウンドイベントとアウトバウンドイベントを処理またはインターセプトするために使用されます。パイプラインは、ユーザーがイベントの処理方法とハンドラーがパイプラインで相互作用する方法を制御できるように、高度な形式のフィルターを実装します。
-
上の図は、パイプラインでI / Oイベントを処理する一般的なハンドラーの方法を示しています。IOイベントは、inboundHandlerまたはoutBoundHandlerによって
ChannelHandlerContext.fireChannelRead
処理され、メソッドを呼び出すことによって最も近いハンドラーに転送されます。
-
図に示すように、受信イベントは受信ハンドラーによってボトムアップ方向で処理されます。インバウンドハンドラーは通常、図の下部にあるI / Oスレッドによって生成されたインバウンドデータを処理します。受信データは通常、たとえばSocketChannel.read(ByteBuffer)から取得されます。
-
通常、パイプラインには複数のハンドラーがあります。たとえば、一般的なサーバーには、各チャネルのパイプラインに次のハンドラーがあります。
プロトコルデコーダーバイナリデータをJavaオブジェクトに変換します。
プロトコルエンコーダー-Javaオブジェクトをバイナリデータに変換します。
ビジネスロジックハンドラー-実際のビジネスロジック(データベースアクセスなど)を実行します。 -
ビジネスプログラムはスレッドをブロックできません。IOの速度に影響し、次にNettyプログラム全体のパフォーマンスに影響します。ビジネスプログラムが高速の場合は、IOスレッドに配置できます。そうでない場合は、非同期に実行する必要があります。または、ハンドラーを追加するときにスレッドプールを追加します。
例:
//次のタスクが実行されても、IOスレッドはブロックされません。実行されるスレッドは、グループスレッドプール
pipe.addLast(group、 "handler"、new MyBusinessLogicHandler()から取得されます) );
またはtaskQueueまたはscheduleTaskQueueに入れます
1.3 ChannelHandlerの機能と設計
- ソースコード
public interface ChannelHandler {
//当把 ChannelHandler 添加到 pipeline 时被调用
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
//当从 pipeline 中移除时调用
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
// 当处理过程中在 pipeline 发生异常时调用
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
ChannelHandlerの役割は、IOイベントを処理またはIOイベントをインターセプトし、それを次のハンドラーChannelHandlerに転送することです。
ハンドラーがイベントを処理するとき、それはインバウンドとアウトバウンドに分けられます。両方向の操作は異なるため、NettyはChannelHandlerを継承する2つのサブインターフェースを定義します
2)ChannelInboundHandler
インバウンドイベントインターフェース
-
channelActiveは、チャネルがアクティブなときに使用されます。
-
channelReadは、チャネルからデータを読み取るときに呼び出されます。
-
プログラマーはいくつかのメソッドを書き直す必要があります。関心のあるイベントが発生すると、イベントにNettyが対応するメソッドを呼び出すため、ビジネスロジックをメソッドに実装する必要があります。
3)ChannelOutboundHandler
送信イベントインターフェイス
- bindメソッド。チャネルがローカルアドレスにバインドするように要求されたときに呼び出されます。
- チャネルが閉じたときに呼び出されるcloseメソッド
- Outbound操作はすべて、データを接続および書き出すための同様の方法です。
4)ChannelDuplexHandler
送信イベントと受信イベントを処理する
- ChannelDuplexHandlerは、インバウンドインターフェイスを間接的に実装し、アウトバウンドインターフェイスを直接実装します。
- これは、受信イベントと送信イベントの両方を処理できる一般的なクラスです。
1.4 ChannelHandlerContextの役割と設計
- ChannelHandlerContext UML図
ChannelHandlerContextは、アウトバウンドメソッド呼び出しインターフェースとインバウンドメソッド呼び出しインターフェースを継承します
1)ChannelOutboundInvoker
及びChannelInboundInvoker
ソースの部分
- これらの2つのインボーカーは、インバウンドまたはアウトバウンドメソッド用です。インバウンドまたはアウトバウンドハンドラーの外側のレイヤーにレイヤーをラップして、メソッドの前後に特定の操作をインターセプトおよび実行する目的を達成します。
2)ChannelHandlerContext
ソースコードの一部
- ChannelHandlerContextは2つのメソッドを継承するだけでなく、独自のメソッドのいくつかを定義します
- これらのメソッドは、コンテキストコンテキスト内の対応するチャネル、エグゼキューター、ハンドラー、パイプライン、メモリアロケーター、および関連するハンドラーが削除されているかどうかを取得できます。
- コンテキストは、ハンドラーに関連するすべてをラップするため、パイプラインでハンドラーを簡単に操作できます。
ChannelPipeline
| ChannelHandler
| ChannelHandlerContext
作成プロセス
作成プロセスを確認するには、3つのステップがあります。
-
ChannelSocketが作成されると、同時にパイプラインが作成されます。
-
ユーザーまたはシステムがパイプラインのadd ***メソッドを内部的に呼び出してハンドラーを追加すると、このハンドラーをラップするコンテキストが作成されます。
-
これらのコンテキストは、パイプラインで二重にリンクされたリストを形成します。
2.1 Socketは、Socketの作成時に作成されます。AbstractChannelのコンストラクターでは、SocketChannelの抽象親クラスです。
protected AbstractChannel(Channel parent) {
this.parent = parent; //断点测试
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
デバッグすると、コードがここで実行されることがわかり、トレースを続行できます
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise = new VoidChannelPromise(channel, true);
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
説明:
1)パイプライン操作チャネルに使用されるチャネルフィールドにチャネルを割り当てます。
2)フューチャーを作成し、非同期コールバックを約束します。
3)インバウンドtailContext
を作成し、インバウンドタイプとアウトバウンドタイプを作成しheadContext
ます
4)最後に、2つのコンテキストを相互に接続して、二重リンクリストを形成します。
5)tailContextとHeadContextは非常に重要であり、パイプライン内のすべてのイベントがそれらを通過します。
2.2 addにハンドラープロセッサを追加するときにコンテキストを作成する** DefaultChannelPipelineのaddLastメソッドによって作成されたコンテキストを確認すると、コードは次のようになります。
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
if (handlers == null) { //断点
throw new NullPointerException("handlers");
}
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
デバッグを続行
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);
if (!registered) {
newCtx.setAddPending();
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
newCtx.setAddPending();
executor.execute(new Runnable() {
@Override
public void run() {
callHandlerAdded0(newCtx);
}
});
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
解説
- ハンドラーをパイプラインに追加します。パラメーターはスレッドプール、名前はnull、ハンドラーは私たちまたはシステムによって渡されたハンドラーです。Nettyは、複数のスレッドがセキュリティの問題を引き起こすのを防ぐために、このコードを同期しました。手順は次のとおりです。
- ハンドラーインスタンスが共有されているかどうかを確認し、共有されていないかどうか、また、別のパイプラインによって既に使用されている場合は、例外をスローします。
- 呼び出し
newContext(group, filterName(name, handler), handler)
方法を、作成コンテキストを。このことから、ハンドラーを追加するたびに、関連付けられたコンテキストが作成されることがわかります。 - addLastメソッドを呼び出して、リンクリストにコンテキストを追加します。
- チャネルがselecorに登録されていない場合は、このコンテキストをこのパイプラインの保留中のタスクに追加します。登録が完了すると、callHandlerAdded0メソッドが呼び出されます(デフォルトでは何も実行されません。ユーザーはこのメソッドを実装できます)。
- この時点で、3つのオブジェクトの作成プロセスについては、ほとんど同じことがわかります。最初に述べたように、ChannelSocketが作成されるたびに、バインドされたパイプラインが作成され、1対1の関係が作成され、パイプラインの作成時にテールノードとテールノードが作成されます。ヘッドノードは、最初のリンクリストを形成します。tailはインバウンドインバウンドタイプハンドラーであり、headはインバウンドタイプとアウトバウンドタイプの両方のハンドラーです。パイプラインのaddLastメソッドが呼び出されると、指定されたハンドラーに従ってコンテキストが作成され、リンクされたリストの末尾(末尾の前)に挿入されます。
ChannelPipeline
ハンドラーのスケジュール方法のソースコード分析
DefaultChannelPipelineがこれらのfireメソッドを実装する方法
1.1 DefaultChannelPipeline
ソースコード
public class DefaultChannelPipeline implements ChannelPipeline {
@Override
public final ChannelPipeline fireChannelActive() {
AbstractChannelHandlerContext.invokeChannelActive(head);
return this;
}
@Override
public final ChannelPipeline fireChannelInactive() {
AbstractChannelHandlerContext.invokeChannelInactive(head);
return this;
}
@Override
public final ChannelPipeline fireExceptionCaught(Throwable cause) {
AbstractChannelHandlerContext.invokeExceptionCaught(head, cause);
return this;
}
@Override
public final ChannelPipeline fireUserEventTriggered(Object event) {
AbstractChannelHandlerContext.invokeUserEventTriggered(head, event);
return this;
}
@Override
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
@Override
public final ChannelPipeline fireChannelReadComplete() {
AbstractChannelHandlerContext.invokeChannelReadComplete(head);
return this;
}
@Override
public final ChannelPipeline fireChannelWritabilityChanged() {
AbstractChannelHandlerContext.invokeChannelWritabilityChanged(head);
return this;
}
}
説明:
これらのメソッドはすべてインバウンド・メソッド、つまりインバウンド・イベントであり、静的メソッドはインバウンド・タイプのヘッド・ハンドラーとも呼ばれていることがわかります。これらの静的メソッドは、ヘッドのChannelInboundInvokerインターフェースのメソッドを呼び出してから、ハンドラーの実際のメソッドを呼び出します
1.2 pieplineの発信Fireメソッド実装のソースコードを確認する
public class DefaultChannelPipeline implements ChannelPipeline {
@Override
public final ChannelFuture bind(SocketAddress localAddress) {
return tail.bind(localAddress);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
return tail.connect(remoteAddress);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
return tail.connect(remoteAddress, localAddress);
}
@Override
public final ChannelFuture disconnect() {
return tail.disconnect();
}
@Override
public final ChannelFuture close() {
return tail.close();
}
@Override
public final ChannelFuture deregister() {
return tail.deregister();
}
@Override
public final ChannelPipeline flush() {
tail.flush();
return this;
}
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return tail.bind(localAddress, promise);
}
@Override
public final ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, promise);
}
@Override
public final ChannelFuture connect(
SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
return tail.connect(remoteAddress, localAddress, promise);
}
@Override
public final ChannelFuture disconnect(ChannelPromise promise) {
return tail.disconnect(promise);
}
}
説明:
- これらはすべてアウトバウンド実装ですが、アウトバウンドイベントであるため、アウトバウンドタイプのテールハンドラーが処理のために呼び出されます。
- アウトバウンドはテールから始まり、インバウンドはヘッドから始まります。アウトバウンドは末尾から内側から書き込まれるため、前のハンドラーを処理して、エンコードなどのハンドラーを見逃さないようにすることができます。逆に、インバウンドはもちろんヘッドから内部への入力なので、後続のハンドラーはこれらの入力のデータを処理できます。デコードなど。したがって、ヘッドはアウトバウンドインターフェースも実装しますが、ヘッドからアウトバウンドタスクを開始しません
2.スケジュールの方法については、画像を使用して以下を表現します。
説明:
- パイプラインはまず、Contextの静的メソッドfireXXXを呼び出し、Contextに渡します
- 次に、静的メソッドがコンテキストの呼び出しメソッドを呼び出し、呼び出しメソッドが、コンテキストに含まれるハンドラーの実際のXXXメソッドを内部的に呼び出します。呼び出しが終了した後、引き続き渡す必要がある場合は、コンテキストのfireXXX2メソッドを呼び出し、ループを繰り返します。責任の連鎖モデル