What is the correct way to read from NetworkStream in .NET

前端 未结 3 1856
没有蜡笔的小新
没有蜡笔的小新 2020-11-27 04:06

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 TcpClient

3条回答
  •  迷失自我
    2020-11-27 04:30

    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

    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)

    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 NetworkStream on the TCPClient so you can change the 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 Read where you want to avoid a "block", you can check the DataAvailable flag and then ONLY read what is in the buffer checking the .Length property e.g. stm.Read(resp, 0, stm.Length);

提交回复
热议问题