7

我在通过(TCP)套接字传输数据时遇到了一个小问题。我在做什么的小背景:

我正在从 A 侧向 B 发送数据。发送的数据可以是可变长度,假设最大大小为 1096 字节。

A) send(clientFd, buffer, size, NULL)

在 B 上,由于我不知道预期的大小,我总是尝试接收 1096 字节:

B) int receivedBytes = receive(fd, msgBuff, 1096, NULL)

但是,当我这样做时:我意识到 A 正在发送小块数据......比如说大约 80-90 个字节。在几次发送之后,B 将它们组合在一起,接收到的字节数为 1096。这显然是损坏的数据和地狱般的松动。

为了解决这个问题,我将数据分成两部分:标题和数据。

struct IpcMsg
{
   long msgType;
   int devId;
   uint32_t senderId;
   uint16_t size; 
   uint8_t value[IPC_VALUES_SIZE]; 
};

A面:

A) send(clientFd, buffer, size, NULL)

在 B 上,我首先接收标头并确定要接收的有效负载的大小:然后接收剩余的有效负载。

B) int receivedBytes = receive(fd, msgBuff, sizeof(IpcMsg) - sizeof( ((IpcMsg*)0)->value ), 0);
int sizeToPoll = ((IpcMsg*)buffer)->size;
printf("Size to poll: %d\n", sizeToPoll);

if (sizeToPoll != 0)
{
        bytesRead = recv(clientFd, buffer + receivedBytes, sizeToPoll, 0); 
}

所以,对于每一个有有效载荷的发送,我最终都会调用接收两次。这对我有用,但我想知道是否有更好的方法来做到这一点?

4

2 回答 2

4

您的想法是正确的,即发送包含有关以下数据的基本信息的标头,然后是数据本身。但是,这并不总是有效:

int receivedBytes = receive(fd, msgBuff, sizeof(IpcMsg) - sizeof( ((IpcMsg*)0)->value ), 0);
int sizeToPoll = ((IpcMsg*)buffer)->size;

原因是 TCP 可以根据自己对应用于所谓的拥塞控制策略的底层网络条件的评估,自由地将您的标头分段并以它认为合适的多个块发送。在 LAN 上,您几乎总是将标头放在一个数据包中,但通过互联网在世界范围内尝试它,您一次获得的字节数可能会少得多。

答案是不要直接调用 TCP 的“接收”(通常recv),而是将其抽象为一个小型实用函数,该函数采用您真正必须接收的大小和一个缓冲区来放入它。进入循环接收和附加数据包,直到所有数据都到达或发生错误。

如果您需要异步并同时为多个客户端提供服务,则适用相同的主体,但您需要研究允许您在数据到达时收到通知的“选择”调用。

于 2014-09-12T08:32:24.073 回答
2

TCP/IP 是用于发送数据的“原始”接口。它确实保证,如果发送字节,它们都以正确的顺序存在,但不保证分块并且对您发送的数据一无所知。

因此,如果通过 TCP/IP 发送一个“数据包”以进行处理,您必须通过以下技术之一知道何时拥有完整的数据包:

  • 固定大小的数据包。在你的情况下 1096 字节
  • 首先发送/接收一个已知的“标头”,它将告诉您正在发送的数据包的大小。
  • 使用某种“数据包结束”符号。

在前两个中的任何一个中,您都知道您期望接收的字节数,因此您需要缓冲收到的任何内容,直到您获得完整的消息,然后处理它。

如果您收到的数据超出您的预期,即溢出到下一个数据包中,您将其拆分,处理完成的数据包,并将剩余的数据包缓冲以供后续处理。

在后一种情况下,你有一个数据包结束符号,它可能在你的消息中的任何地方,所以任何跟随它的东西,你都会为下一个数据包缓冲。

于 2014-09-12T10:09:55.843 回答