我目前正在編寫一個簡單的Web服務器和一些客戶端來使用它。我的客戶希望能夠擴展即將推出的解決方案的功能以包含Web客戶端,但我們需要對通信進行微小的控制,因此簡單的Web服務器就是解決方案。WebClient.Upload和Socket.BeginReceive的問題
無論如何,有兩個症狀,我可以通過運行一堆單元測試重現它們到100%。當我使用「POST」命令將簡單的字符串上傳到服務器時,問題就出現了。這實際上並不是我在現實中會做的事情,但我不能前進而不理解正在發生的事情。我有一個單元測試,它使用BinaryFomatter簡單地序列化字符串「Hello World!」。我在結果字節數組數據前加上一個整數,表示流數據的長度。肯定的一個非常簡單的協議,但它在所有其他情況下(主要是大對象圖)都可以正常工作。我有兩個方案:
- 上傳一個很短的字符串(「世界,你好!」)
- 上傳一個大的字符串(幾千個字符)。
當我運行單元測試無需先運行任何其他單元測試,可正常工作,但每當我跑我所有的單元測試這一塊始終在兩種不同的方式失敗:
- 短串沒有按似乎不會觸發接收插座來接收它。更具體地說,當我調用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;
}
}
我可能是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
}
乾杯
/喬納斯
是的,我沒有看到,剛剛已經發布了。這裏不是真正的問題(我從來沒有見過這個代碼正在執行),但感謝您指出它! – 2011-05-26 20:35:20