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

时间:2014-09-12 07:56:45

标签: c++ sockets networking network-programming tcpsocket

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

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

A) send(clientFd, buffer, size, NULL)
B上的

,因为我不知道期望的大小,我总是尝试接收1096字节:

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

然而,当我这样做时:我意识到A正在发送小块数据......大约80-90字节。经过几次发送后,B将他们聚集在一起,收到的是1296.这显然已经破坏了数据并且地狱崩溃了。

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

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

在一边:

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); 
}

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

2 个答案:

答案 0 :(得分:5)

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

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

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

答案是不要直接调用TCP的'receive'(通常是recv),而是将其抽象为一个小实用程序函数,它接受你必须接收的大小和一个缓冲区。进入循环接收和附加数据包,直到所有数据都到达或发生错误。

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

答案 1 :(得分:2)

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

因此,如果通过TCP / IP发送“数据包”,并且要通过以下方法之一获得完整数据包,则必须知道:

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

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

如果收到的数量超出预期,即它会溢出到下一个数据包中,则将其拆分,处理完成的数据包,然后将剩余的数据包缓冲下来进行处理。

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