0

私は現在、単純な Web サーバーとそれを使用するいくつかのクライアントを作成しています。私の顧客は、今後のソリューションの機能を拡張して Web クライアントを含めたいと考えていますが、通信を細かく制御する必要があるため、単純な Web サーバーがソリューションになります。

とにかく、2 つの症状があり、一連の単体テストを実行することで両方を 100% 再現できます。「POST」コマンドを使用して単純な文字列をサーバーにアップロードすると、問題が発生します。現実にはやらないけど、何が起きているのか分からないと前に進めない。BinaryFomatter を使用して、文字列 "Hello World!" を単純にシリアル化する単体テストがあります。結果のバイト配列データの前に、ストリーミング データの長さを示す整数を付けます。確かに非常に単純なプロトコルですが、他のすべての状況 (主に大きなオブジェクト グラフ) では問題なく動作します。2 つのシナリオがあります。

  1. 非常に短い文字列 (「Hello World!」) をアップロードします。
  2. 大きな文字列 (数千文字) をアップロードします。

最初に他の単体テストを実行せずにその単体テストを実行すると、これは期待どおりに機能しますが、すべての単体テストを実行するたびに、これは常に 2 つの異なる方法で失敗します。

  1. 短い文字列は、受信ソケットをトリガーして受信するようには見えません。具体的には、Socket.BeginReceive() を呼び出すと、コールバックが呼び出されません。
  2. 長い文字列は期待どおりに受信をトリガーしますが、ストリームは破損します。長さのプレフィックス (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
    }

乾杯

/ジョナス

4

1 に答える 1

1

最初の読み取りでキャッチするバイト数が少なすぎる場合はBeginRead、今回だけ別の呼び出しを発行していることになり、間違ったバッファーを渡しています。これがこの特定の問題の原因であると 100% 確信しているわけではありませんが、正しくありません。

        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;
        }
于 2011-05-26T19:03:59.477 に答える