C# 从 .NET 中的 NetworkStream 读取的正确方法是什么
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13097269/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the correct way to read from NetworkStream in .NET
提问by Loudenvier
I've been struggling with this and can't find a reason why my code is failing to properly read from a TCP server I've also written. I'm using the TcpClientclass and its GetStream()method but something is not working as expected. Either the operation blocks indefinitely (the last read operation doesn't timeout as expected), or the data is cropped (for some reason a Read operation returns 0 and exits the loop, perhaps the server is not responding fast enough). These are three attempts at implementing this function:
我一直在为此苦苦挣扎,但找不到我的代码无法从我也编写的 TCP 服务器正确读取的原因。我正在使用TcpClient该类及其GetStream()方法,但有些事情没有按预期工作。操作无限期地阻塞(最后一次读取操作没有按预期超时),或者数据被裁剪(出于某种原因,读取操作返回 0 并退出循环,也许服务器响应不够快)。这是实现此功能的三种尝试:
// this will break from the loop without getting the entire 4804 bytes from the server
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
int bytes = stm.Read(resp, 0, resp.Length);
while (bytes > 0)
{
memStream.Write(resp, 0, bytes);
bytes = 0;
if (stm.DataAvailable)
bytes = stm.Read(resp, 0, resp.Length);
}
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
// this will block forever. It reads everything but freezes when data is exhausted
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
int bytes = stm.Read(resp, 0, resp.Length);
while (bytes > 0)
{
memStream.Write(resp, 0, bytes);
bytes = stm.Read(resp, 0, resp.Length);
}
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
// inserting a sleep inside the loop will make everything work perfectly
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
int bytes = stm.Read(resp, 0, resp.Length);
while (bytes > 0)
{
memStream.Write(resp, 0, bytes);
Thread.Sleep(20);
bytes = 0;
if (stm.DataAvailable)
bytes = stm.Read(resp, 0, resp.Length);
}
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
The last one "works", but it certainly looks ugly to put a hard-coded sleep inside the loop considering that sockets already support read timeouts! Do I need to setup some property(ies) on the TcpClientof the NetworkStream? Does the problem resides in the server? The server don't close the connections, it is up to the client to do so. The above is also running inside the UI thread context (test program), maybe it has something to do with that...
最后一个“有效”,但考虑到套接字已经支持读取超时,在循环中放置一个硬编码的睡眠确实看起来很丑!我需要设置的一些属性(IES)TcpClient的NetworkStream?问题是否出在服务器上?服务器不会关闭连接,这取决于客户端。以上也是在UI线程上下文(测试程序)中运行的,也许与此有关...
Does someone know how to properly use NetworkStream.Readto read data until no more data is available? I guess what I'm wishing for is something like the old Win32 winsock timeout properties... ReadTimeout, etc. It tries to read until the timeout is reached, and then return 0... But it sometimes seem to return 0 when data should be available (or on the way.. can Read return 0 if is available?) and it then blocks indefinitely on the last read when data is not available...
有人知道如何正确使用NetworkStream.Read读取数据直到没有更多数据可用吗?我想我想要的是像旧的 Win32 winsock timeout properties...ReadTimeout等。它会尝试读取直到达到超时,然后返回 0...但有时似乎在数据应该返回 0 时可用(或在路上。如果可用,Read 可以返回 0 吗?)然后当数据不可用时,它会在最后一次读取时无限期阻塞......
Yes, I'm at a loss!
是的,我不知所措!
采纳答案by Loudenvier
Setting the underlying socket ReceiveTimeoutproperty did the trick. You can access it like this: yourTcpClient.Client.ReceiveTimeout. You can read the docsfor more information.
设置底层套接字ReceiveTimeout属性就成功了。您可以像这样访问它:yourTcpClient.Client.ReceiveTimeout。您可以阅读文档以获取更多信息。
Now the code will only "sleep" as long as needed for some data to arrive in the socket, or it will raise an exception if no data arrives, at the beginning of a read operation, for more than 20ms. I can tweak this timeout if needed. Now I'm not paying the 20ms price in every iteration, I'm only paying it at the last read operation. Since I have the content-length of the message in the first bytes read from the server I can use it to tweak it even more and not try to read if all expected data has been already received.
现在代码只会在某些数据到达套接字需要时“休眠”,或者如果在读取操作开始时超过 20 毫秒没有数据到达,它将引发异常。如果需要,我可以调整这个超时。现在我不会在每次迭代中支付 20 毫秒的价格,我只在最后一次读取操作时支付。由于我在从服务器读取的第一个字节中具有消息的内容长度,因此我可以使用它来进一步调整它,并且如果已经收到所有预期数据,则不会尝试读取。
I find using ReceiveTimeout much easier than implementing asynchronous read... Here is the working code:
我发现使用 ReceiveTimeout 比实现异步读取容易得多......这是工作代码:
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
var bytes = 0;
client.Client.ReceiveTimeout = 20;
do
{
try
{
bytes = stm.Read(resp, 0, resp.Length);
memStream.Write(resp, 0, bytes);
}
catch (IOException ex)
{
// if the ReceiveTimeout is reached an IOException will be raised...
// with an InnerException of type SocketException and ErrorCode 10060
var socketExept = ex.InnerException as SocketException;
if (socketExept == null || socketExept.ErrorCode != 10060)
// if it's not the "expected" exception, let's not hide the error
throw ex;
// if it is the receive timeout, then reading ended
bytes = 0;
}
} while (bytes > 0);
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
回答by Furqan Safdar
As per your requirement, Thread.Sleepis perfectly fine to use because you are not sure when the data will be available so you might need to wait for the data to become available. I have slightly changed the logic of your function this might help you little further.
根据您的要求,Thread.Sleep完全可以使用,因为您不确定数据何时可用,因此您可能需要等待数据可用。我稍微改变了你函数的逻辑,这可能对你有帮助。
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
int bytes = 0;
do
{
bytes = 0;
while (!stm.DataAvailable)
Thread.Sleep(20); // some delay
bytes = stm.Read(resp, 0, resp.Length);
memStream.Write(resp, 0, bytes);
}
while (bytes > 0);
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
Hope this helps!
希望这可以帮助!
回答by Colin Smith
Networking code is notoriously difficult to write, test and debug.
众所周知,网络代码难以编写、测试和调试。
You often have lots of things to consider such as:
您通常需要考虑很多事情,例如:
what "endian" will you use for the data that is exchanged (Intel x86/x64 is based on little-endian) - systems that use big-endian can still read data that is in little-endian (and vice versa), but they have to rearrange the data. When documenting your "protocol" just make it clear which one you are using.
are there any "settings" that have been set on the sockets which can affect how the "stream" behaves (e.g. SO_LINGER) - you might need to turn certain ones on or off if your code is very sensitive
how does congestion in the real world which causes delays in the stream affect your reading/writing logic
您将使用什么“endian”来交换数据(Intel x86/x64 基于 little-endian) - 使用 big-endian 的系统仍然可以读取 little-endian 的数据(反之亦然),但它们必须重新排列数据。在记录您的“协议”时,只需明确您使用的是哪一个。
是否有任何在套接字上设置的“设置”会影响“流”的行为(例如 SO_LINGER) - 如果您的代码非常敏感,您可能需要打开或关闭某些设置
现实世界中导致流延迟的拥塞如何影响您的读/写逻辑
If the "message" being exchanged between a client and server (in either direction) can vary in size then often you need to use a strategy in order for that "message" to be exchanged in a reliable manner (aka Protocol).
如果客户端和服务器之间(在任一方向)交换的“消息”的大小可能不同,那么您通常需要使用一种策略,以便以可靠的方式(又名协议)交换该“消息”。
Here are several different ways to handle the exchange:
以下是处理交换的几种不同方法:
have the message size encoded in a header that precedes the data - this could simply be a "number" in the first 2/4/8 bytes sent (dependent on your max message size), or could be a more exotic "header"
use a special "end of message" marker (sentinel), with the real data encoded/escaped if there is the possibility of real data being confused with an "end of marker"
use a timeout....i.e. a certain period of receiving no bytes means there is no more data for the message - however, this can be error prone with short timeouts, which can easily be hit on congested streams.
have a "command" and "data" channel on separate "connections"....this is the approach the FTP protocol uses (the advantage is clear separation of data from commands...at the expense of a 2nd connection)
将消息大小编码在数据前面的标头中 - 这可能只是发送的前 2/4/8 个字节中的“数字”(取决于您的最大消息大小),或者可能是一个更奇特的“标头”
使用特殊的“消息结束”标记(哨兵),如果真实数据可能与“标记结束”混淆,则对真实数据进行编码/转义
使用超时......即一段时间没有接收字节意味着没有更多的消息数据 - 但是,这可能会因短暂的超时而容易出错,这很容易在拥挤的流上被击中。
在单独的“连接”上有一个“命令”和“数据”通道......这是FTP协议使用的方法(优点是数据与命令的明确分离......以第二个连接为代价)
Each approach has its pros and cons for "correctness".
对于“正确性”,每种方法都有其优缺点。
The code below uses the "timeout" method, as that seems to be the one you want.
下面的代码使用“超时”方法,因为这似乎是您想要的。
See http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx. You can get access to the NetworkStreamon the TCPClientso you can change the ReadTimeout.
请参阅http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx。您可以访问NetworkStream上的 ,TCPClient以便您可以更改ReadTimeout.
string SendCmd(string cmd, string ip, int port)
{
var client = new TcpClient(ip, port);
var data = Encoding.GetEncoding(1252).GetBytes(cmd);
var stm = client.GetStream();
// Set a 250 millisecond timeout for reading (instead of Infinite the default)
stm.ReadTimeout = 250;
stm.Write(data, 0, data.Length);
byte[] resp = new byte[2048];
var memStream = new MemoryStream();
int bytesread = stm.Read(resp, 0, resp.Length);
while (bytesread > 0)
{
memStream.Write(resp, 0, bytesread);
bytesread = stm.Read(resp, 0, resp.Length);
}
return Encoding.GetEncoding(1252).GetString(memStream.ToArray());
}
As a footnote for other variations on this writing network code...when doing a Readwhere you want to avoid a "block", you can check the DataAvailableflag and then ONLY read what is in the buffer checking the .Lengthproperty e.g. stm.Read(resp, 0, stm.Length);
作为此编写网络代码的其他变体的脚注......在执行Read要避免“阻止”的地方时,您可以检查DataAvailable标志,然后仅读取检查.Length属性的缓冲区中的内容,例如stm.Read(resp, 0, stm.Length);

