通过TCP套接字接收可变大小的数据

receiving variable size of data over TCP sockets

本文关键字:数据 TCP 套接字 通过      更新时间:2023-10-16

我在通过(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: %dn", sizeToPoll);
if (sizeToPoll != 0)
{
        bytesRead = recv(clientFd, buffer + receivedBytes, sizeToPoll, 0); 
}

所以,对于每一个有有效负载的发送,我都会调用receive两次。这对我很有效,但我想知道是否有更好的方法?

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

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

原因是,TCP可以根据自己对应用于所谓拥塞控制策略的底层网络条件的评估,自由地将您的头分段并按其认为合适的数量发送。在局域网上,你几乎总是把你的头放在一个数据包里,但通过互联网在世界各地尝试,你一次可能会得到更少的字节数。

答案是不要直接调用TCP的"receive"(通常为recv),而是将其抽象为一个小的实用程序函数,该函数占用了您真正必须接收的大小和一个缓冲区。进入接收和附加数据包的循环,直到所有数据到达或出现错误。

如果您需要异步并同时为多个客户端提供服务,则应用相同的主体,但您需要调查"select"调用,该调用允许您在数据到达时收到通知。

TCP/IP是用于发送数据的"原始"接口。它确实保证,如果发送了字节,它们都在那里,并且顺序正确,但对分块没有任何保证,对您发送的数据一无所知。

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

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

在前两种情况下,您都知道期望接收的字节数,因此需要缓冲接收到的任何内容,直到收到完整的消息,然后进行处理。

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

在后一种情况下,如果您有一个数据包结束符号,它可能在消息中的任何位置,所以它后面的任何东西都会缓冲下一个数据。