0

我目前正在编写一个简单的 Web 服务器和一些客户端来使用它。我的客户希望能够扩展即将推出的解决方案的功能以包括 Web 客户端,但我们需要对通信进行细微控制,因此一个简单的 Web 服务器就是解决方案。

无论如何,有两个症状,我可以通过运行一堆单元测试来 100% 地重现它们。当我使用“POST”命令将一个简单的字符串上传到服务器时,问题就出现了。这不是我在现实中会做的事情,但我无法继续前进,不了解正在发生的事情。我有一个单元测试,它使用 BinaryFomatter 简单地序列化字符串“Hello World!”。我在结果字节数组数据前加上一个整数,表示流数据的长度。肯定是一个非常简单的协议,但它在所有其他情况下都可以正常工作(主要是大型对象图)。我有两种情况:

  1. 上传一个非常短的字符串(“Hello World!”)
  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;
        }
    }

我可能在这里得出错误的结论,但我没有看到流对象图有任何问题,而对序列化字符串做同样的事情是有问题的。我不明白为什么。我将不胜感激任何可以为我指明正确方向的提示。

编辑

看来问题是由以前的测试用例引起的,与发送字符串无关,这是我的第一个怀疑。有没有办法让数据在两次连续上传之间“徘徊”?但是,每次上传都会重新创建客户端套接字。

这是上传的客户端:

    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 回答