0

我正在编写一个 C# 客户端应用程序,它将连接到用 python 编写的服务器。我的问题是关于循环接收数据。应用程序结构是关于客户端询问服务器 -> 服务器响应客户端。当消息低于实际缓冲区大小(在服务器中设置)时,一切正常。例如:服务器端缓冲区:1024,客户端缓冲区大小:256,数据长度<1kb。我使用以下代码运行我的应用程序:

int datacounter = 0;
byte[] recived = new byte[256];
StringBuilder stb = new StringBuilder();
serverStream.ReadTimeout = 1500;
try
{
    while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
    {
        Console.WriteLine("RECIVED: " + datacounter.ToString());
        stb.append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
    }
}
catch { Console.WriteLine("Timeout!"); }

然后应用程序在 4 个循环中接收数据(每个循环 256 个字节):

RECIVED: 256
RECIVED: 256
RECIVED: 256
RECIVED: 96

然后超时滴答,结束传输并将完整的数据传递给以后的分析(来自 stb 对象)。我不认为使用超时是正确的,但我不知道有任何其他方法可以做到这一点。但是,这种方式是有效的。这里我们举个例子,没有:服务器端缓冲区:1024,客户端缓冲区:256,数据长度~8kbytes(python端循环发送数据)。

RECIVED: 256
RECIVED: 256
RECIVED: 256
RECIVED: 256

然后超时滴答作响(显然数据不完整 - 得到 1kb 的 8kb)。有时循环甚至在 1 次运行后以 28 个接收字节结束,而这一切都在超时之前结束。Python 表示数据已正确发送。这是我创建套接字和 serverStream 对象的方式:

TcpClient clientSocket = new TcpClient();
clientSocket.Connect("x.y.z.x", 1234);
NetworkStream serverStream = clientSocket.GetStream();

它不是 TcpClient 故障。尝试了相同的清除套接字,创建如下:

new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

然而,它的工作原理相似。有没有办法让我的循环在没有超时的情况下工作,接收所有数据?如果可能的话,我想保持套接字同步。

4

3 回答 3

1

我认为您的接收代码在功能上没有任何问题。我进行了一个测试,只要您在超时之前继续发送而不暂停 1.5 秒,接收器就会尽可能多地发送它(例如 8 MB)。

所以看起来你的服务器发送的速度不够快。

要回答您的问题,时间并不是了解您何时收到完整信息的典型方式。确定何时接收到完整消息的一种常见、简单的方法是在发送端为完整消息的长度添加前缀(例如 4 字节 int)。然后在接收端,首先读取 4 个字节,解码到长度,然后读取更多字节。

您还可以考虑将消息终止字符串(例如 Environment.NewLine)附加到消息的末尾。这样做的好处是您可以调用 StreamReader.ReadLine(),它会阻塞直到收到完整的消息。这仅在终止不能包含在消息本身中时才有效。

如果您无法更改服务器协议,有没有其他方法可以知道您收到了完整的消息?(例如,检查消息末尾的 NewLine、XML 结束标记或其他模式。)如果没有,也许您可​​以等待服务器断开连接,否则看起来您将被迫找到正确的时间平衡。

我包括下面的测试代码,以防你想玩它。

服务器/发送方:

        IPAddress localAddr = IPAddress.Parse("127.0.0.1");
        TcpListener server = new TcpListener(localAddr, 13579);
        server.Start();
        TcpClient clientSocket = server.AcceptTcpClient();
        NetworkStream stream = clientSocket.GetStream();

        int bytesSent = 0;
        int bytesToSend = 1 << 25;
        int bufferSize = 1024;
        string testMessage = new string('X', bufferSize);
        byte[] buffer = UTF8Encoding.UTF8.GetBytes(testMessage);

        while (bytesSent < bytesToSend)
        {
            int byteToSendThisRound = Math.Min(bufferSize, bytesToSend - bytesSent);
            stream.Write(buffer, 0, byteToSendThisRound);
            bytesSent += byteToSendThisRound;
        }

客户/接收方:

        TcpClient client = new TcpClient("127.0.0.1", 13579);
        NetworkStream serverStream = client.GetStream();

        int totalBytesReceived = 0;
        int datacounter = 0;
        byte[] recived = new byte[256];
        StringBuilder stb = new StringBuilder();
        serverStream.ReadTimeout = 1500;
        try
        {
            while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
            {
                totalBytesReceived += 256;
                Console.WriteLine("RECIVED: {0}, {1}", datacounter, totalBytesReceived);
                stb.Append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
            }
        }
        catch { Console.WriteLine("Timeout!"); }
于 2014-03-28T14:11:40.750 回答
0

为什么不转储使您的代码进入 catch 分支并找出的异常?:)

catch (Exception ex) { Console.WriteLine("Timeout because of... " + ex.Message); }

--编辑对不起,我没有看到超时。您要问的问题是是否有办法在没有超时的情况下做到这一点。是的,不要设置任何超时并检查接收到的字节数是否小于缓冲区大小。

那是:

while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
    {
        Console.WriteLine("RECIVED: " + datacounter.ToString());
        stb.append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
        if(datacounter < 256) //you're good to go
           break; 
    }
于 2014-03-26T11:05:37.323 回答
0

对于其他需要帮助的人

只是为了添加到 Chi_Town_Don 的答案,请确保您stb.ToString()在循环之外使用。而且我发现除非循环中断,否则什么都不会打印出来。这样做会产生if(!serverStream.DataAvailable()){break;}奇迹。这样你就不需要传递数据包大小或其他一些复杂的条件。

于 2022-02-15T04:51:36.857 回答