Nettyコーデックとカスタムプロトコル
エンコードとデコード
基本的な紹介
- ネットワークアプリケーションを作成する場合、ネットワークで送信されるデータはバイナリバイトコードデータであるため、データを送信するときにエンコードし、データを受信するときにデコードする必要があります。
コーデック(エンコーダー)には、エンコーダー(エンコーダー)とデコーダー(エンコーダー)の2つのコンポーネントがあります。データを送信するときは、データエンコーダーをバイトコードデータに変換し、データを受信するときは、データデコーダーをビジネスに変換する必要があります。データ
Nettyエンコーダー
- Netty自体は、StringEncoder(文字列のエンコード)、ObjectEncoder(Javaオブジェクトのエンコード)、StringDecoder(文字列のデコード)、ObjectDecoder(Javaオブジェクトのデコード)などのエンコーダーを提供します。
- Netty自体が提供するObjectEncoderとObjectDecoderを使用して、POJOオブジェクトやさまざまなビジネスオブジェクトをエンコードおよびデコードできます。最下層はJavaシリアル番号を使用しますが、Javaシリアル化テクノロジ自体は効率的ではなく、次の問題があります。
- 言語を越えることはできません
- シリアル化後、ボリュームが大きすぎます。これは、バイナリコードの5倍を超えています。
- シリアル化のパフォーマンスが低すぎる
- このソリューションは、GoogleのProtobufを使用しています
Nettyで一般的に使用されるデコーダー
デコーダ | 説明 |
---|---|
LineBasedFrameDecoder | デコーダーは、受信したデータを(\ nまたは\ r \ n)に従って除算し、処理のためにヘンデルに送信します |
DelimiterBasedFrameDecoder | メッセージの区切り文字としてカスタム特殊文字を使用する |
HttpObjectDecoder | HTTPデータのデコーダー |
LengthFileIdBasedFrameDecoder | パケットメッセージ全体を識別する長さを指定することにより、スティッキーパケットとセミパケットメッセージを自動的に処理できます。 |
Protobufエンコーダ
基本的な紹介
- Protobufは、Googleがリリースしたオープンソースプロジェクトです。フルネームはGoogle Protocol Buffersです。軽量で効率的な構造化データストレージ形式です。構造化データまたはシリアル番号のシリアル化に使用できます。データに非常に適しています。ストレージまたはRPC(リモートプロシージャ)。呼び出し)データ交換形式
- 参照ドキュメント:https://developers.google.com/protocol-buffers/docs/javatutorial
- Protobufはメッセージの形式でデータを管理します
- Protobufはクロスプラットフォーム、サーバーサイドをサポートし、さまざまな言語(C ++、C#、Go、Java、Pythonなど)で記述できます
使い方
-
Protobufエンコーダーステップを使用するには、最初にxxx.protoファイルを作成し、次にprptoc.exeを使用して作成したxxx.protoをコンパイルする必要があります。コンパイル後、xxx.protoに対応するxxx.javaファイルが生成されます。 this javaファイルは、このクラスのエンコード(ProtobufEncoder)およびデコード(ProtobufDecoder)できます。
-
.protoファイルをアイデアで書き込むときは、Protocol Buffersプラグインをダウンロードする必要があります。これにより、protoファイルをアイデアで書くときに、構文を強調表示するリマインダーが表示されます。
Protobufの例
Mavenを紹介します
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
プロトタイプタイプはJavaタイプテーブルに対応します
Student.protoを書く
syntax = "proto3";//协议版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,就是生成的文件名
//protobuf 使用message 管理数据
message Student{ //StudentPOJO中会生成一个Student内部类,它是真正发送的数据对象
int32 id = 1;//Student 类有一个属性 名字为 id 类型为int32 1表示属性的序号
string name = 2;
}
protoc.exeをダウンロード
パス:https://github.com/protocolbuffers/protobuf/releases
次のコマンドを実行します。protoc.exe--java_out=。Student.proto、StudentPOJO.javaファイルが生成されます
注:Mavenで使用されるバージョン3.6.1からダウンロードされたprotoc.exeも3.6.1である必要があります
アイデアにコピー
StudentPOJOには内部クラスがあり、Studentは実際にデータを送信するために使用されるクラスであり、多くのメソッドが含まれていることがわかります。
クライアント
パイプラインはProtobufEncoderエンコーダーに参加します
クライアントは** StudentPOJO.Student.newBuilder()を介して内部にデータを直接設定し、最後にbuild()**メソッドを使用して学生オブジェクトを取得します
サーバ
パイプラインはデコーダーに参加します。デコードタイプはStudentPOJO.Student.getDefaultInstance()です。
カスタムハンドラーは直接受信し、getメソッドを使用してデータを取得できます
Protobufの複数のタイプ
Protobufの例では、基本的にProtobufの使用方法を知っていますが、問題が見つかりました。タイプは非常に単一です。異なるタイプを渡す必要がある場合、クライアントとサーバーは多くのxxx.proto、client、およびサーバー側はパイプラインでさまざまなタイプのデコーダーを構成する必要があり、サーバー側も受信するために異なるハンドラーを使用する必要があります
MyDataInfoを書き直します
ここでは、StudentとWorkerの2つのメッセージを格納します。MyMessageを使用してこれら2つのタイプを管理します。MyMessageはenumを使用して0/1を渡し、対応するタイプを取得します。
クライアント
クライアント側では、過去にさまざまな種類のデータをランダムに送信します
サーバ
サーバーはMyMessageをデコードできます
サーバーハンドラー
ハンドラーはMyMessageを使用して受信し、タイプを判断してさまざまなタイプの印刷を実行します。
カスタムエンコーダ
基本的な紹介
エンコーダーとデコーダーをカスタマイズし、設定したルールに従ってデータをデコードおよびエンコードできます。デコーダーとエンコーダーをカスタマイズする前に、エンコードとデコードのハンドラーチェーンがどのように機能するかをもう一度理解しましょう。
私たちのハンドラーはoutboundhandlとinboundhandlに分けられ、inboundhandlはインバウンドイベント、outboundhandlはアウトバウンドイベントとしてエンコードされ、インバウンドイベントとしてデコードされ、いつデコードされ、いつエンコードされるか、たとえば、クライアントは私たちです。サービス終わりはエクスプレスステーションです
- たとえば、宅配便を宅配便ステーションに送りたい場合、最初に行う必要があるのは、宅配便を車に詰めてから、車を積み込んで宅配便ステーションに送るのを容易にすることです(ここにエンコードします。宅配便を発送する必要があります)駅でコード化する必要があります)、梱包して宅配便ステーションに送信します。宅配便ステーションは、駅に宅配便が入っていることを確認し、すべてのエクスプレスを降ろす必要があります(ここでデコード) 、ステーションでデータを受信したとき)(デコードする必要があります)
- たとえば、エクスプレスステーションがエクスプレスを私たちに送信する必要がある場合、エクスプレスステーションは、梱包トラックを利用して梱包してから送信する必要があります(ここでエンコードします。エクスプレスが出ていないときにエンコードする必要があります。駅)、急行が私たちの手に到着した後、次に急行を分解する必要があります(ここでデコードします、データが受信されたときにデコードする必要があります)
エンコードとデコードは一方のパーティに関連しています。クライアントと比較して、サーバーにデータを送信してエンコードする必要があり(outboundhandl)、サーバーから受信したデータをデコードする必要があります(inboundhandl)。サーバーと比較して、にデータを送信したいクライアントをエンコードする必要があり(outboundhandl)、クライアントから受信したデータをデコードする必要があります(inboundhandl)
上記の説明の後、なぜデコードとエンコードが必要なのか、デコードとエンコードのプロセスはすでにわかっていると思いますが、なぜエンコーダーをカスタマイズする必要があるのでしょうか?上記の私とエクスプレスステーションの例では、急行を利用するには急行駅で車を積み込む必要がありますか?私(クライアント)が車を積み込むだけで、運転手(Scoket)が車を引っ張って、運転手が車を引っ張ってくれます。エクスプレスステーション(サーバー)の順番これらの商品は荷降ろしされますが、順番が悪いと、商品が重くなり、トラックが転倒して乱雑になります。これには、クライアントがどのように配置するかというルールを策定する必要があります。サーバーがそれをどのように分解するかこれはカスタムデコーダーです。有用性
カスタムエンコーダ
カスタムエンコーダーを作成するには、MessageToByteEncoder <>を送信し、クラスが受け取るエンコードのタイプを示すジェネリックを渡す必要があります。たとえば、現在のクラスはLongタイプを受け入れてエンコードできます。書き込むwriteLong()メソッド
MessageToByteEncoderはOutboundHandlerから継承されているため、エンコードをアウトバウンドで実行する必要があることがわかります。
カスタムデコーダー
カスタムエンコーダーを作成するには、ByteToMessageDecoderを送信する必要があります。受信したデータに一貫性があるかどうかを判断する場合(TCPの解凍とディッピングで後で方法を説明します)、8文字を超える場合にのみメッセージreadLong操作を実行できます。 、次にリストアウトに追加これは次のハンドラ処理に追加されます
ByteToMessageDecoderは継承され、InboundHandlerはインバウンドを確認できるため、デコードする必要があります
データ送信
エンコーダーとデコーダーをパイプラインチャネルに追加します。データがインバウンドチャネルとアウトバウンドチャネルに入ると、処理用の対応するチャネルがあります。
このように、ctx.writeAndFlush()メソッドを呼び出す場合、ctx.writeAndFlush(Unpooled.copyLong(1123456));を使用する必要はありません。書き込みメソッドは、渡すときにctx.writeAndFlush(123456L);として直接書き込まれます。アウトバウンドハンドラーを介して、MessageToByteEncoderを通過します。writeLong()メソッドを使用してエンコードします。
TCPの貼り付けと解凍
基本的な紹介
- TCPはコネクション型、ストリーム指向であり、信頼性の高いサービスを提供します。トランシーバーの両端(クライアントとサーバー)にソケットのペアが必要です。送信の効率を向上させるために、クライアントがデータパケットの送信を実行します。優れた(Nagleアルゴリズム)、間隔が小さくデータ量が少ない複数のデータを1つの大きなデータパケットに結合してから受信側に送信しますが、受信側に問題があり、受信側は、ミドルで送信したパケットを区別できません。分割する必要のあるパケットの数です。
- TCPにはメッセージ保護境界がないため、メッセージ境界の問題は受信側で処理する必要があります。これをスティッキングおよびアンパックの問題と呼びます。
これは理想的な状態でのデータ転送です。クライアントは最初に2つのデータパケットをサーバーに送信し、D1サーバーとD2サーバーはD1とD2の独立したデータパケットを2回受け入れます。スティッキングとアンパックの問題はありません。
サーバーは一度に2つのデータパケットを受信する場合があります。D1とD2は接着されており、TCPスティッキパケットと呼ばれます。
サーバーはデータパケットを2回読み取りますが、最初は完全なD1パケットとD2の一部を読み取り、2回目はD2の一部を読み取ります。これはTCPアンパックと呼ばれます。
サーバーはデータパケットを2回読み取りますが、最初はD2の一部を読み取り、2回目は完全なD1パケットを読み取り、残りはD2を読み取ります。これはTCPアンパックとも呼ばれます。
概要:あなたがサーバーであると仮定すると、上記の状況でパッケージシミュレーションを受け取ります。それらが完全なパッケージであるかどうかを判断する方法?それらを分割してマージする方法?不完全なパッケージがビジネスロジック全体に問題を引き起こす可能性があります。TCPスティッキングとアンパックは、ネットワークプログラミングで解決する必要のある問題です。
TCPスティッキーパケットと開梱ケース
クライアント
クライアントが接続を確立すると、forループが呼び出され、サーバーにhelloが送信されます。サーバーから送信されたメッセージを受信すると、サーバーはメッセージを出力します。
サーバ
サーバーは読み取り可能なイベントを受信すると、データを読み取ってコンソールに出力します。出力が完了すると、サーバーはデータの一部を現在のチャネルに送り返します。
スピードテストを実行する
サーバーが受信したデータがスティッキーパケットを送信していることがわかります。
サーバーからデータを受信するときにクライアントがスティッキーパケットも送信するという問題
解決
- カスタムプロトコルとコーデックを使用して、TCPのスティッキングとアンパックの問題を解決します
カスタムプロトコルパッケージ
プロトコルパケットの長さがキーであり、サーバーはデコード時に送信した長さに応じてデコードします
カスタムエンコーダ
ChannelでMessageProtocolオブジェクトをエンコードして送信します
カスタムデコーダー
受信したデータはMessageProtocolオブジェクトにデコードされ、次のハンドラーによって処理されます
クライアントとサーバーのパイプラインにカスタムコーデックを追加する
クライアントとサーバーは、MessageProtocolオブジェクトを使用してデータを均一に送受信します
試験結果
クライアントとサーバーの両方がカプセル化されたMessageProtocolに従ってデコードでき、パッケージ化とアンパックに問題はありません。