したがって、ネットワーキングについて学ぶときに直面することの1つは、同期の喪失です。これは、たとえば、特定のクライアント/サーバー相互作用のセットを「ハードコーディング」した場合に発生します。
server sends 2 byte status code
client receives 2 byte status code
client responds with 4 byte operation code
何らかの理由で、この相互作用の一部が必要に応じて正確に発生しないバグがある場合、すべてのネットワーク相互作用が同期していないため、プログラムの残りの部分は失敗します。クライアントは、実際にサーバーがintを送信しているときなど、文字列を表すと信じるバイトのセットを読み取る可能性があります。最悪の場合、クライアントとサーバーの両方が同時に入力を待機しているため、メインネットワークスレッドがデッドロックしていることに気付くかもしれません。
確かにバグがある大規模なプロジェクトでは、このスタイルでコーディングすると、これは非常に多く発生します。このため、ミドルウェアと呼ばれるものがあります。
非常に一般的で柔軟なミドルウェアパラダイムは、TLVメッセージプロトコルです。いくつかの単純なクラスを(semi-java-pseudocodeで)実装します。
TLVMessage
int type;
byte[] value;
TLVPusher implements Runnable
OutputStream out;
Queue<TLVMessage> messages;
run() {
while(true) {
//poll and write front of queue to out (INCLUDING value.length!)
}
}
TLVReader implements Runnable
InputStream in;
Queue<TLVMessage> messages;
run() {
while(true) {
//read message from in and add to queue
}
}
これで、クライアントで2つのスレッドが実行され、サーバーで2つのスレッドが実行されます。それぞれの端には独自のPusher
とがありReader
ます。注意すべき重要な点は、長さフィールドを出力ストリームに書き込むため、リーダーは常に読み取る必要のあるバイト数を知っているということです。したがって、1つのメッセージが誤ってシリアル化された場合でも、その長さは正しく、次のメッセージは常に最初のバイトから最後のバイトまで正しく読み取られます。このようにして、プログラムが同期しなくなることはありません。
TLVMessage
にオブジェクトを追加するだけで、ソケットのもう一方の端にオブジェクトpusher.queue
が到着します。reader.queue
次に、メッセージをフィールドごとに処理できます(別の3番目のスレッドで監視reader.messages.size()
します)type
。
物事がどのような順序で発生するかを心配する必要はありません。クライアントとサーバーの間で2つの方向にメッセージを渡すための堅牢なメカニズムがあります。あなたは厄介なネットワークのものを抽象化し、コーディングに取り掛かることができます。
もちろん、これをすべて行うライブラリはありますが、私の意見では、その方法と理由を理解することは常に価値があります。