私は現在、単純な Web サーバーとそれを使用するいくつかのクライアントを作成しています。私の顧客は、今後のソリューションの機能を拡張して Web クライアントを含めたいと考えていますが、通信を細かく制御する必要があるため、単純な Web サーバーがソリューションになります。
とにかく、2 つの症状があり、一連の単体テストを実行することで両方を 100% 再現できます。「POST」コマンドを使用して単純な文字列をサーバーにアップロードすると、問題が発生します。現実にはやらないけど、何が起きているのか分からないと前に進めない。BinaryFomatter を使用して、文字列 "Hello World!" を単純にシリアル化する単体テストがあります。結果のバイト配列データの前に、ストリーミング データの長さを示す整数を付けます。確かに非常に単純なプロトコルですが、他のすべての状況 (主に大きなオブジェクト グラフ) では問題なく動作します。2 つのシナリオがあります。
- 非常に短い文字列 (「Hello World!」) をアップロードします。
- 大きな文字列 (数千文字) をアップロードします。
最初に他の単体テストを実行せずにその単体テストを実行すると、これは期待どおりに機能しますが、すべての単体テストを実行するたびに、これは常に 2 つの異なる方法で失敗します。
- 短い文字列は、受信ソケットをトリガーして受信するようには見えません。具体的には、Socket.BeginReceive() を呼び出すと、コールバックが呼び出されません。
- 長い文字列は期待どおりに受信をトリガーしますが、ストリームは破損します。長さのプレフィックス (4 バイト、シリアル化された Int32) に非常に大きな値が含まれています。確かに正しいものではありません。
これは、サーバー コードの興味深い部分です。
public void Receive(bool async = false, TimeSpan timeout = default(TimeSpan))
{
var asyncResult = _socket.BeginReceive(_lengthBuffer, 0, _lengthBuffer.Length, SocketFlags.None, receiveLengthCallback, this);
if (!async)
Wait(timeout == default(TimeSpan) ? Timeout : timeout);
if (IsComplete)
return;
SocketError socketError;
_socket.EndReceive(asyncResult, out socketError);
SocketError = socketError;
}
private static void receiveLengthCallback(IAsyncResult asyncResult)
{
try
{
var data = (SocketDataReceiver)asyncResult.AsyncState;
var count = data._socket.EndReceive(asyncResult);
if (count == 0)
{
// connection was closed, abort ...
data.onReceiveAborted();
return;
}
data._index += count;
if (data._index < data._lengthBuffer.Length)
{
// length only partially received, get rest ...
data._socket.BeginReceive(data._buffer, data._index, data._lengthBuffer.Length - data._index, SocketFlags.None, receiveLengthCallback, data);
return;
}
// done receiving the length prefix ...
data._length = BitConverter.ToInt32(data._lengthBuffer, 0);
data.Data = new byte[data._length]; // ERROR (this will cause an OutOfMemoryException when data._length has become corrupted
if (data._length == 0)
{
// not much to do here, cancel ...
data.onReceiveAborted();
return;
}
data._index = 0;
if (data._buffer.Length > data._length)
data._buffer = new byte[data._length];
// start reading content ...
data._socket.BeginReceive(data._buffer, data._index, data._buffer.Length - data._index, SocketFlags.None, receiveCallback, data);
}
catch (Exception ex)
{
// todo handle exception in Socket reception code
throw;
}
}
private static void receiveCallback(IAsyncResult asyncResult)
{
try
{
var data = (SocketDataReceiver)asyncResult.AsyncState;
var count = data._socket.EndReceive(asyncResult);
if (count == 0)
{
// connection was closed, abort ...
data.onReceiveAborted();
return;
}
foreach (var b in data._buffer)
{
data.Data[data._index++] = b;
if (--count == 0)
break;
}
if (data._index == data._length)
{
// all data has been received ...
data.onReceiveComplete();
return;
}
// more data is on the way ...
data._socket.BeginReceive(data._buffer, 0, data._buffer.Length, SocketFlags.None, receiveCallback, data);
}
catch (Exception ex)
{
// todo handle exception in Socket reception code
throw;
}
}
私はここで間違った結論を導き出しているかもしれませんが、オブジェクト グラフのストリーミングでは何の問題も見たことがありませんが、シリアル化された文字列で同じことを行うのは問題があります。理由がわかりません。私を正しい方向に向けるヒントがあれば幸いです。
編集
この問題は以前のテスト ケースが原因で発生したようで、最初に疑った文字列の送信とは関係ありません。2 つの連続するアップロードの間にデータが「残る」方法はありますか? ただし、クライアントソケットはアップロードごとに再作成されます。
これは、アップロードのクライアント側です。
private void upload(string documentName, object data, int timeout = -1)
{
// todo Handle errors
WebClientEx client;
using (client = new WebClientEx())
{
client.Timeout = timeout < 0 ? UploadTimeout : timeout;
try
{
var response = client.UploadData(
new Uri(ServerUri + "/" + documentName),
StreamedData.Wrap(data));
// todo Handle response
}
catch (Exception ex)
{
throw new Exception("Failed while uploading " + data + ".", ex);
}
}
GC.Collect(); // <-- this was just experimenting with getting rid of the client socket, for good measure. It has no effect on this problem though
}
乾杯
/ジョナス