我一直在努力解决这个问题,找不到我的代码无法从我也编写的 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());
}
最后一个“有效”,但考虑到套接字已经支持读取超时,在循环中放置一个硬编码的休眠看起来肯定很难看!我是否需要在 NetworkStream
的 TcpClient
上设置一些属性?问题是否出在服务器上?服务器不会关闭连接,这取决于客户端。上面也是在UI线程上下文(测试程序)里面运行的,可能跟那个有关系……
有人知道如何正确使用 NetworkStream.Read
读取数据直到没有更多数据可用吗?我想我想要的是像旧的 Win32 winsock 超时属性...ReadTimeout
等。它会尝试读取直到达到超时,然后返回 0...但是它有时似乎在数据应该可用时返回 0(或者在路上......如果可用,Read 可以返回 0 吗?)然后当数据不可用时它会无限期地阻塞在最后一次读取时......
是的,我不知所措!
最佳答案
众所周知,网络代码很难编写、测试和调试。
您通常需要考虑很多事情,例如:
您将对交换的数据使用什么“字节序”(Intel x86/x64 基于小字节序)- 使用大字节序的系统仍然可以读取小字节序的数据(并且反之亦然),但他们必须重新排列数据。在记录您的“协议(protocol)”时,只需明确您使用的是哪一个。
是否有任何在套接字上设置的“设置”会影响“流”的行为方式(例如 SO_LINGER)——如果您的代码非常敏感,您可能需要打开或关闭某些设置
现实世界中的拥塞导致流延迟如何影响您的读写逻辑
如果在客户端和服务器之间(在任一方向)交换的“消息”的大小可能不同,那么您通常需要使用一种策略以便以可靠的方式(也称为协议(protocol))交换该“消息” .
这里有几种不同的方式来处理交换:
将消息大小编码在数据之前的 header 中 - 这可能只是发送的前 2/4/8 个字节中的“数字”(取决于您的最大消息大小),或者可以是更奇特的“标题”
使用特殊的“消息结束”标记(哨兵),如果真实数据有可能与“结束标记”混淆,则对真实数据进行编码/转义
使用超时....即一段时间内没有接收到任何字节意味着没有更多的消息数据 - 但是,这可能会因超时时间短而容易出错,很容易在拥塞的流上发生错误。
在单独的“连接”上有一个“命令”和“数据” channel ......这是 FTP 协议(protocol)使用的方法(优点是数据与命令的明确分离......以牺牲第二个连接)
每种方法都有其“正确性”的优点和缺点。
下面的代码使用了“超时”方法,因为这似乎是您想要的。
参见 http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx .您可以访问 TCPClient
上的 NetworkStream
,这样您就可以更改 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());
}
作为此编写网络代码的其他变体的脚注...在执行 Read
时,您希望避免“阻塞”,您可以检查 DataAvailable
标志,然后只读取检查 .Length
属性的缓冲区中的内容,例如stm.Read(resp, 0, stm.Length);
关于c# - 在 .NET 中从 NetworkStream 读取的正确方法是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13097269/