WebClient.Upload和Socket.BeginReceive的问题

时间:2011-05-26 17:05:34

标签: c# sockets serialization asynchronous beginreceive

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

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

  1. 上传一个非常短的字符串(“Hello World!”)
  2. 上传大字符串(几千个字符)。
  3. 当我在没有先运行任何其他单元测试的情况下运行该单元测试时,这可以按预期工作,但每当我运行所有单元测试时,这一次总是以两种不同的方式失败:

    1. 短字符串似乎没有触发接收套接字接收它。更具体地说,当我调用Socket.BeginReceive()时,我的回调永远不会被调用。
    2. 长字符串会按预期触发接收,但流会损坏。长度前缀(4字节,序列化Int32)包含一个非常大的值。当然不是正确的。
    3. 这是服务器代码的有趣部分:

          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
          }
      

      干杯

      /纳斯

1 个答案:

答案 0 :(得分: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;
        }