2011-05-26 62 views
0

我目前正在編寫一個簡單的Web服務器和一些客戶端來使用它。我的客戶希望能夠擴展即將推出的解決方案的功能以包含Web客戶端,但我們需要對通信進行微小的控制,因此簡單的Web服務器就是解決方案。WebClient.Upload和Socket.BeginReceive的問題

無論如何,有兩個症狀,我可以通過運行一堆單元測試重現它們到100%。當我使用「POST」命令將簡單的字符串上傳到服務器時,問題就出現了。這實際上並不是我在現實中會做的事情,但我不能前進而不理解正在發生的事情。我有一個單元測試,它使用BinaryFomatter簡單地序列化字符串「Hello World!」。我在結果字節數組數據前加上一個整數,表示流數據的長度。肯定的一個非常簡單的協議,但它在所有其他情況下(主要是大對象圖)都可以正常工作。我有兩個方案:

  1. 上傳一個很短的字符串(「世界,你好!」)
  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; 
     } 
    } 

我可能是drawinf這裏得出錯誤的結論,但我還沒有看到與流對象圖的任何問題,而做同樣的用序列串是有問題的。我不明白爲什麼。我將不勝感激任何可以指引我朝着正確方向的提示。

編輯

看來,這個問題是由以前的測試情況下造成的,它無關,與發送一個字符串,這是我第一次懷疑。數據可以在兩次連續上傳之間「流連忘返」嗎?儘管每次上傳都會重新創建客戶端套接字。

這是上傳的客戶端:

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

如果趕上第一個字節太少閱讀你發出另一個呼叫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; 
     } 
+0

是的,我沒有看到,剛剛已經發布了。這裏不是真正的問題(我從來沒有見過這個代碼正在執行),但感謝您指出它! – 2011-05-26 20:35:20