【问题标题】:C# TCP Read All Data - Server Side IssuesC# TCP 读取所有数据 - 服务器端问题
【发布时间】:2016-11-23 04:08:32
【问题描述】:

我正在使用 C# v4.0 (.Net Framework v4) 开发一个简单的 TCP 服务器应用程序:

我要完成这两个步骤:

  1. 客户端发送message1到服务器(客户端可以是.net或java应用)
  2. 服务器将 message2 作为对 message1 的响应 发送回客户端

我的服务器有问题,它无法正确读取 message1,除非我使用以下不适当的解决方案之一:

1) 使用只有 1 个字节的 Buffer 的 MemoryStream(有效但速度慢):

while (true)
{
    TcpClient tcpClient = tcpListener.AcceptTcpClient();
    NetworkStream networkStream = tcpClient.GetStream();
    MemoryStream memoryStream = new MemoryStream();

    int numberOfBytesRead = 0;
    byte[] buffer = new byte[1]; // works but slow in case of big messages

    do
    {
        numberOfBytesRead = networkStream.Read(buffer, 0, buffer.Length);   
        memoryStream.Write(buffer, 0, numberOfBytesRead);
    } while (networkStream.DataAvailable);

    if (memoryStream.Length > 0)
    {
        string message1 = new StreamReader(memoryStream).ReadToEnd();
        if (message1 == "message1")
        {
            using (StreamWriter streamWriter = new StreamWriter(networkStream))
            {
                string message2 = "message2";
                streamWriter.Write(message2);
                streamWriter.Flush();
            }
        }
    }
}

示例:如果 message1.Length == 12501 并且我使用 1024 的缓冲区,NetworkStream.Read() 循环仅读取 2048 字节的 message1,我认为 NetworkStream.DataAvailable 没有返回正确的值!

2) 在从 NetworkStream 读取到缓冲区后使用 Thread.Sleep(1000)(有效但速度慢):

while (true)
{
    TcpClient tcpClient = tcpListener.AcceptTcpClient();
    NetworkStream networkStream = tcpClient.GetStream();
    MemoryStream memoryStream = new MemoryStream();

    int numberOfBytesRead = 0;
    byte[] buffer = new byte[8192]; 

    do
    {
        numberOfBytesRead = networkStream.Read(buffer, 0, buffer.Length);   
        memoryStream.Write(buffer, 0, numberOfBytesRead);
        Thread.Sleep(1000); // works but receiving gets slow
    } while (networkStream.DataAvailable);

    if (memoryStream.Length > 0)
    {
        string message1 = new StreamReader(memoryStream).ReadToEnd();
        if (message1 == "message1")
        {
            using (StreamWriter streamWriter = new StreamWriter(networkStream))
            {
                string message2 = "message2";
                streamWriter.Write(message2);
                streamWriter.Flush();
            }
        }
    }
}

3) 使用 StreamReader.ReadToEnd() 并在发送消息1 后关闭客户端的套接字(有效但服务器无法使用消息2 响应客户端):

while (true)
{
    TcpClient tcpClient = tcpListener.AcceptTcpClient();
    NetworkStream networkStream = tcpClient.GetStream();
    StreamReader streamReader = new StreamReader(networkStream, true);

    string message1 = streamReader.ReadToEnd(); // blocks until client close its socket

    if (message1 == "message1")
    {
        using (StreamWriter streamWriter = new StreamWriter(networkStream))
        {
            string message2 = "message2";
            streamWriter.Write(message2); // if client close its sockets, the server cannot send this message
            streamWriter.Flush();
        }
    }
}

4) 循环使用 StreamReader.ReadLine() 并关闭客户端的套接字

while (true)
{
    TcpClient tcpClient = tcpListener.AcceptTcpClient();
    NetworkStream networkStream = tcpClient.GetStream();
    StreamReader streamReader = new StreamReader(networkStream);

    StringBuilder stringBuilder = new StringBuilder();

    while (!streamReader.EndOfStream)
    {
        stringBuilder.AppendLine(streamReader.ReadLine()); // blocks until client close its socket
    }

    string message1 = stringBuilder.ToString();

    if (message1 == "message1")
    {
        using (StreamWriter streamWriter = new StreamWriter(networkStream))
        {
            string message2 = "message2";
            streamWriter.Write(message2); // if client close its sockets, the server cannot send this message
            streamWriter.Flush();
        }
    }
}

5) 为 message1 加上长度前缀(有效,但需要客户端向消息中添加额外字节,这不适用于现有的 java 客户端)

while (true)
{
    TcpClient tcpClient = tcpListener.AcceptTcpClient();
    NetworkStream networkStream = tcpClient.GetStream();
    MemoryStream memoryStream = new MemoryStream();

    byte[] bufferMessageLength = new byte[4];   // sizeof(int)
    networkStream.Read(bufferMessageLength, 0, bufferMessageLength.Length);  

    int messageLength = BitConverter.ToInt32(bufferMessageLength, 4);

    byte[] bufferMessage = new byte[messageLength]; 
    networkStream.Read(bufferMessage, 0, bufferMessage.Length); 
    memoryStream.Write(buffer, 0, bufferMessage.Length);

    if (memoryStream.Length > 0)
    {
        string message1 = new StreamReader(memoryStream).ReadToEnd();
        if (message1 == "message1")
        {
            using (StreamWriter streamWriter = new StreamWriter(networkStream))
            {
                string message2 = "message2";
                streamWriter.Write(message2);
                streamWriter.Flush();
            }
        }
    }
}

关于这些问题,在不使用上述解决方案的情况下,从客户端读取所有数据的最佳方法是什么?

【问题讨论】:

  • 这个answer 看起来有用吗?它提到了ReceiveTimeout 属性。
  • @J.J.Hakala 我试过了,但它会生成一个异常而没有返回任何接收到的数据。

标签: c# sockets tcp client-server


【解决方案1】:

而不是使用networkStream.DataAvailable 在消息的开头附加数据的大小。例如,您的消息长度是 12501 使用前 4 个字节作为消息长度。

首先定义一个从缓冲区读取数据的方法

public static void ReadStream(NetworkStream reader, byte[] data)
{
    var offset = 0;
    var remaining = data.Length;
    while (remaining > 0)
    {
        var read = reader.Read(data, offset, remaining);
        if (read <= 0)
            throw new EndOfStreamException
                (String.Format("End of stream reached with {0} bytes left to read", remaining));
        remaining -= read;
        offset += read;
    }
}

然后从流中读取数据。

var bytesRead = 0;
var offset = 0;
TcpClient tcpClient = tcpListener.AcceptTcpClient();
NetworkStream networkStream = tcpClient.GetStream();
var bufferMessageSize = new byte[4]; // int32

ReadStream(networkStream, bufferMessageSize);

var messageSize = BitConverter.ToInt32(bufferMessageSize, 4); // bytesToRead

var bufferMessage = new byte[messageSize];

ReadStream(networkStream, bufferMessage);


// Now Respond back Client here
// networkStream.Write();

【讨论】:

  • 如果您的消息长度是固定的,那么您可以跳过标题消息大小,即第 6 行和第 7 行。
【解决方案2】:

如果通信是面向线路的,那么StreamReader.ReadLine() 可能是合适的。

ReadLine() 从当前流中读取一行字符并返回 数据作为字符串。

【讨论】:

  • ReadLine() 阻塞,直到客户端关闭其套接字
猜你喜欢
  • 2020-07-15
  • 2015-08-29
  • 2012-07-04
  • 2020-01-28
  • 2021-09-05
  • 1970-01-01
  • 2016-09-16
  • 2023-04-02
  • 1970-01-01
相关资源
最近更新 更多