【问题标题】:How to accommodate timing variability in writing to tcp socket?如何适应写入 tcp 套接字的时序变化?
【发布时间】:2016-04-07 00:46:17
【问题描述】:

作为测试,我正在将一系列字节数组从 Android 应用程序写入 tcp 套接字,并在 C++ 应用程序中读取它们。

Java

InetAddress address = InetAddress.getByName("192.168.0.2");                          
Socket socket = new Socket(address, 1300);                   
DataOutputStream out = new DataOutputStream(socket.getOutputStream())

...

if(count == 0) {
    out.write(first, 0, first.length);
} else if(count == 1) {
    out.write(second, 0, second.length);
}

C++

do {
    iResult = recv(ClientSocket, recvbuf, 3, 0);
    for (int i = 0; i < 3; i++) {
        std::cout << (int)(signed char)recvbuf[i] << std::endl;
    }
} while (iResult > 0);

就目前而言,在第一个收据上,recv[2] = -52,我认为这是一个垃圾值,因为在我收到第一个字节数组时,输出流还没有写入第二个字节数组段。

但是,当我在 ListenSocket 接受连接后暂停时:

ClientSocket = accept(ListenSocket, NULL, NULL);
std::cin.ignore();

...让发送方有时间对流进行两次写入,recv[2] = 3,这是第二个写入字节数组的第一个值。

如果我最终想要发送和接收离散数组的恒定流,我如何确定在收到一个数组的最后一个值后,缓冲区中的下一个值是下一个数组的第一个值还是是不是垃圾值?

我考虑过udp比较适合发送一系列离散的数据集,但是我需要tcp的可靠性。我想 tcp 经常以这种方式使用,但我不清楚如何缓解这个问题。

编辑: 在我编写此测试的实际应用程序中,我确实实现了长度前缀。我认为这无关紧要。即使我知道我在数据集的末尾,我也需要知道缓冲区中的下一个值是垃圾还是下一组的开头。

【问题讨论】:

  • 你不能让 TCP 做到这一点。 TCP 将所有数据视为单个字节流。你应该发送数组有多大,在接收器上读取数组有多大,然后读取那么多的东西。
  • 半离题:在iResult = recv(ClientSocket, recvbuf, 3, 0); iResult 是读取的字节数,如果有的话,或者错误时为负值。因此,在检查并处理了否定情况之后,打印结果的for 循环应该类似于:for (int i = 0; i &lt; iResult; i++) 来处理您读取数据但没有获得三个字节的情况。
  • @immibis 你的意思是说TCP不能也不用这种方式?我必须使用 udp 吗?
  • @Jayz7522 不,我是说如果您以块的形式发送数据,您将不会以块的形式接收它。如果你想要积木,你必须发明自己的积木。
  • recv 的最后一个 flags 参数可以传递给MSG_WAITALL,在这种情况下它将阻塞并确保在recv 返回之前读取3 个字节,否则将报告错误/断开连接。这可以简化您的代码,只要您可以相信 Java 端在发送 3 个字节之前没有崩溃。更一般地说,您可能不想在服务器和客户端中硬编码3,因此最初可能会发送一个固定长度的二进制编码消息长度。

标签: c++ sockets tcp winsock2


【解决方案1】:
for (int i = 0; i < 3; i++)

问题就在这里。应该是:

for (int i = 0; i < iResult; i++)

您正在打印您可能没有收到的数据。这就是“垃圾价值”的解释。

您不能假设recv() 会填满缓冲区。

您还必须在此循环之前检查 iResult 的 -1 和零,并采取适当的措施,这在每种情况下都不同。

【讨论】:

  • 看来我并没有想象中那么离题。
  • 是的,我对 recv() 返回的内容做出了错误的假设。谢谢!
【解决方案2】:

正如您所指出的,TCP 是基于流的,因此没有内置方式可以说“这是特定的数据块”。您要做的是添加自己的“消息框架”。一种简单的方法称为“长度前缀”。您首先发送数据包的大小,然后是数据包本身。然后接收者将知道他们何时获得了所有数据。

发送方

  1. 发送数据包的长度(作为已知大小——比如 32 位整数)
  2. 发送数据包

接收方

  1. 数据包读取长度
  2. 读取那么多字节的数据
  3. 处理完全接收的数据包

查看这篇文章了解更多信息:http://blog.stephencleary.com/2009/04/message-framing.html

【讨论】:

  • 谢谢。我已经在我的实际应用程序中实现了一个长度前缀(不过,我会查看这篇文章,看看它是否需要改进);但是我认为这不能解决这个问题。仅仅因为我知道我在一个块的末尾,并不意味着我知道下一个字节是下一个块的开头还是垃圾。对吗?
  • @Jayz7522 您不会收到任何垃圾邮件。
  • @Jayz7522 我认为您看到的“垃圾”是您打印出未初始化的内存。
  • @Jayz7522:关键的见解是,在这个答案中提倡的长度前缀,在每个阶段你都知道你希望读取多少数据:前四个字节,然后无论四字节数表示多少字节。对于其中的每一个,您要么循环调用recv,直到您组装了该数量的字节,或者您使用MSG_WAITALL,正如我在您的问题下的评论中所解释的那样。
猜你喜欢
  • 2013-05-25
  • 2018-02-03
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 2011-01-30
  • 2016-08-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多