Indy 書き込みバッファリング / 効率的な TCP 通信
-
03-07-2019 - |
質問
わかっています、私はたくさんの質問をしています...しかし、新しい Delphi 開発者として、私はこれらすべての質問につまずきます:)
これは、indy 10 を使用した TCP 通信を扱います。通信を効率的にするために、クライアント操作リクエストを 1 バイトとしてコーディングします (ほとんどのシナリオでは、もちろんその後に他のデータ バイトが続きますが、この場合は 1 バイトのみです)。問題はそれです
var Bytes : TBytes;
...
SetLength (Bytes, 1);
Bytes [0] := OpCode;
FConnection.IOHandler.Write (Bytes, 1);
ErrorCode := Connection.IOHandler.ReadByte;
そのバイトはすぐには送信されません (少なくともサーバー実行ハンドラーは呼び出されません)。たとえば「1」を「9」に変更すると、すべて問題なく動作します。Indy が送信バイトをバッファリングしていると仮定し、書き込みバッファリングを無効にしようとしました
FConnection.IOHandler.WriteBufferClose;
しかし役に立ちませんでした。単一バイトを送信し、それがすぐに送信されるようにするにはどうすればよいですか?そして、ここにもう 1 つの小さな質問を追加します。indy を使用して整数を送信する最良の方法は何ですか?残念ながら、TIdTCPServer の IOHandler には WriteInteger のような関数が見つかりません...そして
WriteLn (IntToStr (SomeIntVal))
私にはあまり効率的ではないようです。複数の書き込みコマンドを連続して使用するか、バイト配列にまとめて一度送信するかに違いはありますか?
ご回答ありがとうございます。
編集:読み取りおよび書き込み手順に関して大きな変更があるようですので、Indy 10 を使用しているというヒントを追加しました。
解決
書き込みバッファリングはデフォルトでは無効になっています。fConnection.IOHandler.WriteBufferingActive プロパティをテストすることで、書き込みバッファリングがコード内でアクティブかどうかを確認できます。
整数を送信する最良の方法については...それはあなたのプロトコルと全体的な目標によって異なります。具体的には、整数を含むほぼあらゆる種類のデータを書き込むためのオーバーロードされたメソッドがあるため、FConnection.IOHandler.Write() を使用します。
IdIOHandler からの抜粋:
// Optimal Extra Methods
//
// These methods are based on the core methods. While they can be
// overridden, they are so simple that it is rare a more optimal method can
// be implemented. Because of this they are not overrideable.
//
//
// Write Methods
//
// Only the ones that have a hope of being better optimized in descendants
// have been marked virtual
procedure Write(const AOut: string; const AEncoding: TIdEncoding = enDefault); overload; virtual;
procedure WriteLn(const AEncoding: TIdEncoding = enDefault); overload;
procedure WriteLn(const AOut: string; const AEncoding: TIdEncoding = enDefault); overload; virtual;
procedure WriteLnRFC(const AOut: string = ''; const AEncoding: TIdEncoding = enDefault); virtual;
procedure Write(AValue: TStrings; AWriteLinesCount: Boolean = False; const AEncoding: TIdEncoding = enDefault); overload; virtual;
procedure Write(AValue: Byte); overload;
procedure Write(AValue: Char; const AEncoding: TIdEncoding = enDefault); overload;
procedure Write(AValue: LongWord; AConvert: Boolean = True); overload;
procedure Write(AValue: LongInt; AConvert: Boolean = True); overload;
procedure Write(AValue: SmallInt; AConvert: Boolean = True); overload;
procedure Write(AValue: Int64; AConvert: Boolean = True); overload;
procedure Write(AStream: TStream; ASize: Int64 = 0; AWriteByteCount: Boolean = False); overload; virtual;
あなたが持っていた別の質問は、「複数の書き込みコマンドを連続して使用するのか、それともバイト配列で物事を詰めてそれを一度送信するかどうかに違いをもたらしますか?」でした。ほとんどの場合、はい、それは違いを生みます。非常に負荷の高いサーバーの場合は、バイトがどのように送受信されるかについてさらに深く関与する必要がありますが、このレベルでは、送信データを構築して送信する別のプロトコル タイプ クラスに送信を抽象化する必要があります。バーストし、整数、文字、バイト配列などの送受信に分割するのではなく、データの束を受信して完全な単位として処理する受信プロトコルを備えています。
非常に大まかな簡単な例として、次のようにします。
TmyCommand = class(TmyProtocol)
private
fCommand:Integer;
fParameter:String;
fDestinationID:String;
fSourceID:String;
fWhatever:Integer;
public
property Command:Integer read fCommand write fCommand;
...
function Serialize;
procedure Deserialize(Packet:String);
end;
function TmyCommand.Serialize:String;
begin
//you'll need to delimit these to break them apart on the other side
result := AddItem(Command) +
AddItem(Parameter) +
AddItem(DestinationID) +
AddItem(SourceID) +
AddItem(Whatever);
end;
procedure TMyCommand.Deserialize(Packet:String);
begin
Command := StrToInt(StripOutItem(Packet));
Parameter := StripOutItem(Packet);
DesintationID := StripOutItem(Packet);
SourceID := StripOutItem(Packet);
Whatever := StrToInt(StripOutItem(Packet));
end;
次に、これを次の方法で送信します。
FConnection.IOHandler.Write(myCommand.Serialize());
反対側では、Indy 経由でデータを受信し、
myCommand.Deserialize(ReceivedData);
他のヒント
私はIndyに精通していませんが、TCP_NODELAYオプションについてはそのAPIを調べたいと思うかもしれません(Indyソースツリーをそのような何かのためにgrepしたいかもしれません-" delay"の大文字小文字を区別しないでください)。 )
編集: Rob Kennedy は、私が参照していたプロパティが TIdIOHandlerSocket.UseNagle
-ありがとう!
この問題は、TCPの性質に固有のものです。 TCPは、送信されたときと同じ順序でデータ配信を保証しますが、メッセージの境界を保証しません。つまり、ソース、ターゲット、および途中のルーターのオペレーティングシステムは、接続からパケットを自由に結合したり、自由にフラグメント化したりできます。 TCP伝送は、一連の個々のパケットとしてではなく、ストリームとして見る必要があります。 したがって、個々のメッセージを区切るメカニズムを実装する必要があります(たとえば、マジックバイトで、メッセージデータでも発生する可能性がある場合はエスケープする必要があります)。または、次のメッセージの長さを送信できます。最初に、次に実際のメッセージ。
私は、あなたの場合のように、メッセージの境界が重要であるメッセージを送信する必要があるとき、単純なACK /再送信スキームと組み合わせたUDPを常に使用していました。それを考慮したいかもしれません。 UDPはコマンドメッセージにより適しています。
バッファをフラッシュする必要があるように聞こえます。これを試してください:
TIdTCPConnection.FlushWriteBuffer;
書き込みバッファが必要ない場合は、これを使用します:
TIdTCPConnection.CancelWriteBuffer;
ヘルプによると、これはまずClearWriteBufferを呼び出してバッファーをクリアし、次にCloseWriteBufferを呼び出します。
整数を送信する最良の方法(Indy 10を使用)はTIdIOHandler.Writeを使用することです(Indy 10のヘルプWriteによると、整数を含むさまざまな種類のデータを処理するためにオーバーロードされます)