WebClient.Upload 和 Socket.BeginReceive 的问题
我目前正在编写一个简单的网络服务器和一些使用它的客户端。我的客户希望能够扩展即将推出的解决方案的功能以包括 Web 客户端,但我们需要对通信进行精细控制,因此简单的 Web 服务器就是解决方案。
不管怎样,有两个症状,我可以通过运行一堆单元测试来 100% 重现它们。当我使用“POST”命令将简单的字符串上传到服务器时,问题就出现了。这并不是我在现实中会做的事情,但如果不了解正在发生的事情,我就无法继续前进。我有一个单元测试,它使用 BinaryFomatter 简单地序列化字符串“Hello World!”。我在生成的字节数组数据前面加上一个整数,指示流数据的长度。这确实是一个非常简单的协议,但它在所有其他情况下(主要是大型对象图)都工作得很好。我有两种情况:
- 上传一个非常短的字符串(“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;
}
}
我可能会在这里得出错误的结论,但我没有看到流对象图有任何问题,而对序列化字符串做同样的事情是有问题的。我不明白为什么。我将不胜感激任何可以为我指明正确方向的提示。
编辑
看来问题是由之前的测试用例引起的,与发送字符串无关,这是我的第一个怀疑。有没有办法让数据在两次连续上传之间“徘徊”?不过,每次上传都会重新创建客户端套接字。
这是上传的客户端:
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
}
Cheers
/Jonas
I'm currently writing a simple web server and some clients to use it. My customer wants to be able to extend the functionality of the upcoming solution to include web clients but we need to have minute control of the communication so a simple web server is the solution.
Anyway, there are two symptoms and I can reproduce them both to 100% by running a bunch of unit tests. The problem comes when I upload a simple string to the server, using a "POST" command. It's not really something I will do in reality but I can't move ahead not understanding what's going on. I have a unit test that simply serializes the string "Hello World!", using a BinaryFomatter. I prefix the resulting byte array data with an integer indicating the length of the streamed data. A very simple protocol for sure but it works just fine in all other situations (larg object graphs mainly). I have two scenarios:
- Upload a very short string ("Hello World!")
- Upload a large string (a few thousand characters).
When I run that unit test without first running any other unit tests this works as expected but whenever I run all my unit tests this one always fails in two different ways:
- The short string doesn't seem to trigger the receiving socket to receiver it. More specifically, when I call Socket.BeginReceive() my callback is never called.
- The long string does trigger reception as expected but the stream gets corrupted. The length prefix (4 byte, serialized Int32) contains a very large value. Certainly not the correct one.
This is the interesting part of the server code:
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;
}
}
I might be drawinf the wrong conclusions here but I haven't seen any problems with streaming object graphs whereas doing the same with serialized strings is problematic. I can't understand why. I would appreciate any hints that could point me in the right direction.
EDIT
It appears that the problem is caused by a previous test case and it has nothing to do with sending a string, which was my first suspicion. Is there a way data can "linger" between two successive uploads? The client socket is recreated for every upload though.
This is the client side of the upload:
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
}
Cheers
/Jonas
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果您在第一次读取时捕获的字节太少,则只会在这次传入错误的缓冲区时再次调用
BeginRead
。我不能 100% 确定这是这个特定问题的原因,但这是不对的:If you catch too few bytes on the first read you are issuing another call to
BeginRead
only this time you are passing in the wrong buffer. I'm not 100% sure this is the cause of this particular issue, but it's not right: