Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在.NET中读取NetworkStream的正确方法是什么_C#_.net_Sockets_Tcpclient - Fatal编程技术网

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

C# 在.NET中读取NetworkStream的正确方法是什么,c#,.net,sockets,tcpclient,C#,.net,Sockets,Tcpclient,我一直在努力解决这个问题,但找不到代码无法从我编写的TCP服务器正确读取的原因。我正在使用TcpClient类及其GetStream()方法,但有些东西没有按预期工作。要么操作无限期地阻塞(最后一次读取操作没有按预期超时),要么数据被裁剪(出于某种原因,读取操作返回0并退出循环,可能是服务器响应不够快)。以下是实现此功能的三种尝试: // this will break from the loop without getting the entire 4804 bytes from the se

我一直在努力解决这个问题,但找不到代码无法从我编写的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());
}
最后一个“有效”,但考虑到套接字已经支持读取超时,将硬编码睡眠放在循环中显然看起来很难看!是否需要在
网络流的
TcpClient
上设置一些属性?问题是否存在于服务器中?服务器不关闭连接,这取决于客户端。以上内容也在UI线程上下文(测试程序)中运行,可能与此有关

是否有人知道如何正确使用
NetworkStream.Read
来读取数据,直到没有更多数据可用?我想我想要的是像旧的Win32 winsock超时属性一样的东西<代码>读取超时
等。它尝试读取,直到达到超时,然后返回0。。。但有时它似乎在数据应该可用时返回0(或者在途中..如果可用,读取是否返回0?),然后在数据不可用时在最后一次读取时无限期阻塞


是的,我不知所措

根据您的要求,
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());
}

希望这有帮助

众所周知,网络代码难以编写、测试和调试

你经常有很多事情要考虑,例如:

  • 对于交换的数据,您将使用什么“endian”(英特尔x86/x64基于little endian)-使用big endian的系统仍然可以读取little endian中的数据(反之亦然),但它们必须重新排列数据。在记录你的“协议”时,只需明确你使用的是哪一个

  • 是否在套接字上设置了任何可能影响“流”行为的“设置”(例如SO_LINGER)-如果代码非常敏感,可能需要打开或关闭某些设置

  • 在现实世界中,导致流延迟的拥塞如何影响您的读/写逻辑

如果客户机和服务器之间交换的“消息”(在任一方向)的大小可能不同,那么通常需要使用一种策略,以便以可靠的方式交换“消息”(也称为协议)

以下是处理交换的几种不同方法:

  • 将消息大小编码在数据之前的头中-这可能只是发送的前2/4/8字节中的一个“数字”(取决于最大消息大小),也可能是一个更奇特的“头”

  • 使用特殊的“消息结束”标记(sentinel),如果真实数据可能与“消息结束”标记混淆,则对真实数据进行编码/转义

  • 使用超时…,即。没有接收字节的特定时间段意味着消息没有更多的数据-但是,这可能会由于短超时而容易出错,这很容易在拥挤的流中受到影响

  • 在单独的“连接”上有一个“命令”和“数据”通道…这是FTP协议使用的方法(优点是数据与命令明显分离…以牺牲第二个连接为代价)

每种方法都有其“正确性”的优缺点

下面的代码使用“timeout”方法,因为这似乎就是您想要的方法

看。您可以访问
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());
}

作为此写入网络代码的其他变体的脚注…当在希望避免“阻塞”的位置执行
读取
时,您可以检查
DataAvailable
标志,然后仅读取缓冲区中的内容,检查
.Length
属性,例如
stm.Read(resp,0,stm.Length)

设置基础套接字
ReceiveTimeout
属性就成功了。您可以这样访问它:
yourTcpClient.Client.ReceiveTimeout
。有关更多信息,请阅读

现在,只要某些数据需要到达套接字,代码就只会“休眠”,或者如果在读取操作开始时没有数据到达超过20ms,代码就会引发异常。如果需要,我可以调整此超时。现在,我不是在每次迭代中都支付20ms的代价,我只是在最后一次读取操作中支付。因为我有从服务器读取的第一个字节中的消息内容长度,所以我可以使用它来进一步调整它,如果已经收到所有预期的数据,则不尝试读取

我发现使用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());
}

大小未知,可能在MB范围内。。。正如我在问题上所说的,第三种选择确实正确地阅读了所有内容,但我不能接受在那里睡觉的需要,我也不能解释它。我知道它的作用:通过睡眠,我给数据到达套接字的时间,但如果数据在此之前到达,我不想睡眠20毫秒。我知道如何通过非阻塞读取和我自己的重叠IO来实现这一点。。。但我认为我不需要在.NET中使用它!您使用什么方法让读数知道它是否读取了所有数据,即正确的数量/大小?典型的方法是使用fir中编码的大小标头