网络上传输不同大小数据的最佳实践

3
我希望能够通过UDP传输不同大小的数据,要发送的数据大小是不固定的。我有以下场景:
unsigned char buffer[BUFFERSIZE];
int bytes = fill_buffer(buffer, sizeof(buffer)): // Returns number of filled bytes.
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))

在上述示例中,接收方不知道要接收多少字节。我曾考虑先发送要接收的字节数,然后再发送数据。但是在这种情况下,如果数据包到达顺序错乱,我不知道会发生什么。
发送方应该是:
sendto(socket, &bytes, sizeof(bytes), 0, (struct sockaddr *)&server, sizeof(server))
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))

收件方将是:
recvfrom(socket, &bytes, sizeof(bytes), 0, NULL, NULL)
recvfrom(socket, buffer, bytes, 0, NULL, NULL)

但是数据是否可能出现乱序?


2
你需要创建一个应用层协议,它可以包括一个序列号,以便重新排序任何乱序数据。同时,请记住UDP会有丢失的数据报,因此你必须接受这样一个事实:并非所有数据都能传输成功,要么使用一个应用程序或应用层协议来请求重发丢失的数据,要么使用像TCP这样的协议,它可以为你完成所有这些工作。 - Ron Maupin
3个回答

1
与流式协议TCP不同,调用recv并不完全对应于调用send,UDP是基于数据包的协议,这意味着每个recvfrom都与一个sendto相对应。这也意味着您需要注意发送的每条消息的大小。
如果发送的UDP数据报大于可以包含在IP数据包中的大小,则UDP消息将在多个UDP数据包中分段,增加数据丢失的可能性。这是您要避免的。此外,如果您使用IPv6,则在尝试发送时会收到错误,因为IPv6不支持分段。
这与您正在做的事情有什么关系?粗略地说,这意味着您的消息不应超过约1450个字节,因此您可以将该值用作输入缓冲区的大小。然后,您可以使用recvfrom的返回值来查看实际读取了多少字节。如果您的消息大于该大小,您应将其拆分为多个消息。
与任何基于UDP的协议一样,您需要考虑消息丢失并需要重新传输的情况,或者消息无序的情况。

1
我认为如果添加消息头,您可以在单个数据报中发送两者。
发送方仅发送其拥有的有效负载数据量。
接收方始终请求最大有效负载大小,但检查标题和从recvfrom返回的内容以确定实际长度。

这里有一些初步的代码,可以说明我的想法:

struct header {
    u32 magic_number;
    u32 seq_no;
    u32 msg_type;
    u32 payload_length;
} __attribute__((__packed__));

#define MAXPAYLOAD  1024

struct message {
    struct header info;
    unsigned char payload[MAXPAYLOAD];
} __attribute__((__packed__));

void
sendone(int sockfd,const void *buf,size_t buflen)
{
    struct message msg;
    static u32 seqno = 0;

    memcpy(&msg.payload[0],buf,buflen);
    msg.info.magic_number = 0xDEADADDE;
    msg.info.seq_no = seqno++;
    msg.info.payload_length = buflen;

    sendto(sockfd,&msg,sizeof(struct header) + buflen,...);
}

ssize_t
getone(int sockfd,void *buf,size_t buflen)
{
    struct message msg;
    ssize_t rawlen;
    ssize_t paylen;
    static u32 seqno = 0;

    rawlen = recvfrom(sockfd,&msg,sizeof(struct header) + MAXPAYLOAD,...);

    paylen = msg.info.payload_length;

    if (rawlen != (sizeof(struct header) + paylen))
        // error ...

    memcpy(buf,&msg.payload[0],paylen);

    return paylen;
}

接收方可以检查魔术数和序列号,以查找损坏、丢失/丢弃的数据包等问题。
事实上,您可以通过使用sendmsgrecvmsg来获得更高的效率,因为它们允许您使用散列/聚集列表发送单个消息。 (即)数据不需要使用memcpy从消息结构中复制进出[您只需要struct header],因此更接近零拷贝缓冲。
另一个选择可能是在 recvfrom/recvmsg 中使用 MSG_PEEK 标志。我自己从未使用过这个选项,但它可能是这样的:
  1. 使用长度为 sizeof(struct header) 和标志为 MSG_PEEKrecvmsg
  2. 使用长度为 sizeof(struct header) + msg.info.payload_length 的第二个 recvmsg
这只是一个不必总是提供最大大小缓冲区的好处。由于涉及两个系统调用,它可能会慢一些。但是,它可能允许根据消息类型和/或长度从池中选择有效负载缓冲区的某些技巧。

0

实际上,这个问题的答案非常简单。

给定:

unsigned char buffer[BUFFERSIZE];
int bytes = fill_buffer(buffer, sizeof(buffer)): // Returns number of filled bytes.
sendto(socket, buffer, bytes, 0, (struct sockaddr *)&server, sizeof(server))

recvfrom 的返回值告诉我们接收了多少字节,即使我们进行了完整的读取。

int bytesReceived = recvfrom(socket, buffer, sizeof(buffer), 0, NULL, NULL);
// Process bytesReceived number of bytes in the buffer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接