c# - 在 .NET 中从 NetworkStream 读取的正确方法是什么

标签 c# .net sockets tcpclient

我一直在努力解决这个问题,找不到我的代码无法从我也编写的 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());
}

最后一个“有效”,但考虑到套接字已经支持读取超时,在循环中放置一个硬编码的休眠看起来肯定很难看!我是否需要在 NetworkStreamTcpClient 上设置一些属性?问题是否出在服务器上?服务器不会关闭连接,这取决于客户端。上面也是在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/

相关文章:

c# - WPF 数据网格 : How to stay on the same row after pressing Enter?

c# - 通过文本框和按钮搜索 mysql 数据库并将输出重定向到另一个页面

python - 无法使用pysockets从服务器接收消息

c# - 在system.net.socket中将HTTP header 发送到客户端的实际方法

c - 查询选择系统调用

c# - 每次解析具有不同依赖实现的同一个类的多个实例

c# - StringBuilder 的默认容量

c# - 为什么对象(根)类不冲突多重继承

c# - `XmlConvert.ToDateTime(String)` 和 `XmlConvert.ToString(DateTime)` 输出不一致

.net - Thrift、.NET、Cassandra - 这是正确的组合吗?