Nettyコーデックとカスタムプロトコル

Nettyコーデックとカスタムプロトコル

エンコードとデコード

基本的な紹介

  1. ネットワークアプリケーションを作成する場合、ネットワークで送信されるデータはバイナリバイトコードデータであるため、データを送信するときにエンコードし、データを受信するときにデコードする必要があります。

ここに画像の説明を挿入

コーデック(エンコーダー)には、エンコーダー(エンコーダー)とデコーダー(エンコーダー)の2つのコンポーネントがあります。データを送信するときは、データエンコーダーをバイトコードデータに変換し、データを受信するときは、データデコーダーをビジネスに変換する必要があります。データ

Nettyエンコーダー

  1. Netty自体は、StringEncoder(文字列のエンコード)、ObjectEncoder(Javaオブジェクトのエンコード)、StringDecoder(文字列のデコード)、ObjectDecoder(Javaオブジェクトのデコード)などのエンコーダーを提供します。
  2. Netty自体が提供するObjectEncoderとObjectDecoderを使用して、POJOオブジェクトやさまざまなビジネスオブジェクトをエンコードおよびデコードできます。最下層はJavaシリアル番号を使用しますが、Javaシリアル化テクノロジ自体は効率的ではなく、次の問題があります。
    • 言語を越えることはできません
    • シリアル化後、ボリュームが大きすぎます。これは、バイナリコードの5倍を超えています。
    • シリアル化のパフォーマンスが低すぎる
  3. このソリューションは、GoogleのProtobufを使用しています

Nettyで一般的に使用されるデコーダー

デコーダ 説明
LineBasedFrameDecoder デコーダーは、受信したデータを(\ nまたは\ r \ n)に従って除算し、処理のためにヘンデルに送信します
DelimiterBasedFrameDecoder メッセージの区切り文字としてカスタム特殊文字を使用する
HttpObjectDecoder HTTPデータのデコーダー
LengthFileIdBasedFrameDecoder パケットメッセージ全体を識別する長さを指定することにより、スティッキーパケットとセミパケットメッセージを自動的に処理できます。

Protobufエンコーダ

基本的な紹介

  1. Protobufは、Googleがリリースしたオープンソースプロジェクトです。フルネームはGoogle Protocol Buffersです。軽量で効率的な構造化データストレージ形式です。構造化データまたはシリアル番号のシリアル化に使用できます。データに非常に適しています。ストレージまたはRPC(リモートプロシージャ)。呼び出し)データ交換形式
  2. 参照ドキュメント:https://developers.google.com/protocol-buffers/docs/javatutorial
  3. Protobufはメッセージの形式でデータを管理します
  4. Protobufはクロスプラットフォーム、サーバーサイドをサポートし、さまざまな言語(C ++、C#、Go、Java、Pythonなど)で記述できます

使い方

  1. Protobufエンコーダーステップを使用するには、最初にxxx.protoファイルを作成し、次にprptoc.exeを使用して作成したxxx.protoをコンパイルする必要があります。コンパイル後、xxx.protoに対応するxxx.javaファイルが生成されます。 this javaファイルは、このクラスのエンコード(ProtobufEncoder)およびデコード(ProtobufDecoder)できます。

  2. .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はアウトバウンドイベントとしてエンコードされ、インバウンドイベントとしてデコードされ、いつデコードされ、いつエンコードされるか、たとえば、クライアントは私たちです。サービス終わりはエクスプレスステーションです

  1. たとえば、宅配便を宅配便ステーションに送りたい場合、最初に行う必要があるのは、宅配便を車に詰めてから、車を積み込んで宅配便ステーションに送るのを容易にすることです(ここにエンコードします。宅配便を発送する必要があります)駅でコード化する必要があります)、梱包して宅配便ステーションに送信します。宅配便ステーションは、駅に宅配便が入っていることを確認し、すべてのエクスプレスを降ろす必要があります(ここでデコード) 、ステーションでデータを受信したとき)(デコードする必要があります)
  2. たとえば、エクスプレスステーションがエクスプレスを私たちに送信する必要がある場合、エクスプレスステーションは、梱包トラックを利用して梱包してから送信する必要があります(ここでエンコードします。エクスプレスが出ていないときにエンコードする必要があります。駅)、急行が私たちの手に到着した後、次に急行を分解する必要があります(ここでデコードします、データが受信されたときにデコードする必要があります)

エンコードとデコードは一方のパーティに関連しています。クライアントと比較して、サーバーにデータを送信してエンコードする必要があり(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の貼り付けと解凍

基本的な紹介

  1. TCPはコネクション型、ストリーム指向であり、信頼性の高いサービスを提供します。トランシーバーの両端(クライアントとサーバー)にソケットのペアが必要です。送信の効率を向上させるために、クライアントがデータパケットの送信を実行します。優れた(Nagleアルゴリズム)、間隔が小さくデータ量が少ない複数のデータを1つの大きなデータパケットに結合してから受信側に送信しますが、受信側に問題があり、受信側は、ミドルで送信したパケットを区別できません。分割する必要のあるパケットの数です。
  2. 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が送信されます。サーバーから送信されたメッセージを受信すると、サーバーはメッセージを出力します。

ここに画像の説明を挿入

サーバ

ここに画像の説明を挿入

ここに画像の説明を挿入

サーバーは読み取り可能なイベントを受信すると、データを読み取ってコンソールに出力します。出力が完了すると、サーバーはデータの一部を現在のチャネルに送り返します。

ここに画像の説明を挿入

スピードテストを実行する

サーバーが受信したデータがスティッキーパケットを送信していることがわかります。

ここに画像の説明を挿入

サーバーからデータを受信するときにクライアントがスティッキーパケットも送信するという問題

ここに画像の説明を挿入

解決

  1. カスタムプロトコルとコーデックを使用して、TCPのスティッキングとアンパックの問題を解決します

カスタムプロトコルパッケージ

プロトコルパケットの長さがキーであり、サーバーはデコード時に送信した長さに応じてデコードします

ここに画像の説明を挿入

カスタムエンコーダ

ChannelでMessageProtocolオブジェクトをエンコードして送信します

ここに画像の説明を挿入

カスタムデコーダー

受信したデータはMessageProtocolオブジェクトにデコードされ、次のハンドラーによって処理されます

ここに画像の説明を挿入

クライアントとサーバーのパイプラインにカスタムコーデックを追加する

ここに画像の説明を挿入

ここに画像の説明を挿入

クライアントとサーバーは、MessageProtocolオブジェクトを使用してデータを均一に送受信します

ここに画像の説明を挿入

ここに画像の説明を挿入

試験結果

クライアントとサーバーの両方がカプセル化されたMessageProtocolに従ってデコードでき、パッケージ化とアンパックに問題はありません。

ここに画像の説明を挿入

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_44642403/article/details/109692580