【问题标题】:TcpClient's NetworkStream mixing my data's orderTcpClient 的 NetworkStream 混合了我的数据顺序
【发布时间】:2016-01-11 08:31:36
【问题描述】:

我在 tcp 上实现了一个简单的协议,它发送内容的大小,然后是内容,这是一种非常简单的方法,可以在正确检测消息结束的同时跨应用程序发送字符串和字节数组,以下是相关代码:

public static class StreamExtension
{
    /// <summary>
    /// Sends a byte packet that can be read with "ReadBytePacket" on the 
    /// receiver's side
    /// </summary>
    /// <param name="target"></param>
    /// <param name="content"></param>
    public static void SendBytePacket(this NetworkStream target, byte[] content)
    {
        long packetSize = content.Length;

        target.Write(BitConverter.GetBytes(packetSize), 0, 8);

        target.Write(content, 0, content.Length); 
    }

    /// <summary>
    /// Reads a byte packet sent from the SendBytePacket function
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    public static byte[] ReadBytePacket(this NetworkStream target)
    {
        //Reads the packet size
        long size = target.ReadPacketSize();

        //Reads "size" bytes from the stream
        return target.ReadPacketBody(size);
    }

    /// <summary>
    /// Reads the packets header, which contains the size of the packet body
    /// </summary>
    /// <param name="target"></param>
    /// <returns></returns>
    private static long ReadPacketSize(this NetworkStream target)
    {
        byte[] packetSizeBytes = new byte[8];
        for (int totalRead = 0; totalRead < 8;)
        {
            totalRead += target.Read(packetSizeBytes, totalRead, 8 - totalRead);
        }
        return BitConverter.ToInt64(packetSizeBytes, 0);
    }

    /// <summary>
    /// Sends a string over the NetworkStream that can be read with 
    /// "ReadStringPacket" function on the receiver's side
    /// </summary>
    /// <param name="target"></param>
    /// <param name="content"></param>
    public static void SendStringPacket(this NetworkStream target, string content)
    {
        var bytes = Encoding.ASCII.GetBytes(content);

        target.SendBytePacket(bytes);
    }

    /// <summary>
    /// Reads a string packet sent from the SendStringPacket function
    /// </summary>
    /// <returns></returns>
    public static string ReadStringPacket(this NetworkStream target)
    {
        //Reads the packet size
        long size = target.ReadPacketSize();

        //Reads "size" bytes from the stream
        byte[] resultBytes = target.ReadPacketBody(size);

        //Decodes the string from the received bytes
        return Encoding.ASCII.GetString(resultBytes, 0, (int) size);
    }

    /// <summary>
    /// Reads the packet body, which is a sequence of "packetSize" bytes
    /// </summary>
    /// <param name="target"></param>
    /// <param name="packetSize"></param>
    /// <returns></returns>
    private static byte[] ReadPacketBody(this NetworkStream target, long packetSize)
    {
        //Reads "size" bytes from the stream
        byte[] resultBytes = new byte[packetSize];

        for (int byteCount = 0; byteCount < packetSize;)
        {
            byteCount += target.Read(resultBytes, byteCount, (int)packetSize - byteCount);
        }

        return resultBytes;
    }
}

现在,问题是,每当我用一个非常长的字符串调用“SendStringPacket”时,接收端的某些字符串块会出现故障,这是来自失败测试的代码:

[TestFixture]
public class StreamExtensionTest
{
    private Thread _receiver;
    private string _results = null;

    private Thread CreateReceiver()
    {
        return new Thread(() =>
        {
            var listener = new TcpListener(IPAddress.Any, 5000);

            listener.Start();

            //blocking call to wait for connections
            var client = listener.AcceptTcpClient();
            client.ReceiveTimeout = 200;

            //Connected
            var stream = client.GetStream();

            _results = stream.ReadStringPacket();


            stream.Close();
            client.Close();
            listener.Stop();
        });
    }

    [Test]
    public void StringPackets()
    {
        var testCases = new string[]
        {
            //"",
            //"This is my simple test case",
            //"http://stackoverflow.com/questions/13097269/what-is-the-correct-way-to-read-from-networkstream-in-net",
            //"{[:]}",
            //System.IO.File.ReadAllText(TestEnvironmentHelper.ScreenVariablesMapPath + "ScreenVariables.map"),
            //"%1003",
            System.IO.File.ReadAllText(TestEnvironmentHelper.TestTextsPath + "1003")
        };

        foreach (var testCase in testCases)
        {
            _receiver = CreateReceiver();
            _receiver.Start();
            Thread.Sleep(50);

            var Client = new TcpClient("127.0.0.1", 5000);
            Client.ReceiveTimeout = 5000;
            var stream = Client.GetStream();

            stream.SendStringPacket(testCase);

            Thread.Sleep(3000);
            stream.Close();
            Client.Close();

            //_receiver sets the _results member
            Assert.AreEqual(testCase.Length, _results.Length);
            System.IO.File.WriteAllText(TestEnvironmentHelper.TestTextsPath + "result.txt", _results);
            Assert.AreEqual(testCase, _results);
        }
    }
}

在接收线程上我使用了 TcpListener,那么我的字符串怎么会被打乱呢?来自 TcpClient/Listener 的 NetworkStream 不是为了保持秩序吗?

【问题讨论】:

  • 您没有向我们展示ReadPacketBody,但我已经可以在ReadPacketSize 中发现一个错误,并且它可能存在相同的问题 - 您忽略了来自Read 的返回值,它告诉您实际读取了多少字节。可以是 1 和您要求的字节数之间的任何数字。如果您需要读取一定数量的字节,则需要继续循环直到接收到该数量的字节。
  • 谢谢,刚刚修复了那个并添加了 ReadPacketBody 的代码,但测试仍然失败,但在同一索引中出现错误
  • 尝试在target.Write()之后调用target.Flush()
  • @Lorek target.Flush() 让我更加困惑了,它改变了书面测试文件的内容,一些块被替换为一系列 \0 字符,随后调用 target.Flush() Thread.Sleep(200) 导致输出恢复到未调用 Flush 时的输出。我现在还添加了完整的测试代码
  • new TcpClient("127.0.0.1", 5000) 将客户端端点绑定到该 IP 和端口。它不会连接到侦听该 IP 和端口的服务器。还是我错过了你打电话给Connect("127.0.0.1", 5000)的地方?

标签: c# network-programming tcpclient


【解决方案1】:

在你初始化 TcpClient 的地方,试试这个:

        var Client = new TcpClient();
        Client.Connect("127.0.0.1", 5000);
        var stream = Client.GetStream();

看看有没有帮助。

另外,请确保在Write() 之后调用Flush()

【讨论】:

  • 结果相同
  • 这真的很奇怪。我稍后会尝试构建它。我只是现在没有时间。如果我有什么想法,我会告诉你的。
  • 我发现我的测试使用错误的 dll 运行,并且在我清理并构建一切正常后,但现在我不确定哪个是修复更改,我在发布后立即更改了代码当我阅读第一条评论时,问题中的代码可能已经在工作了
猜你喜欢
  • 2017-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多