【问题标题】:Best practice of sending data of different sizes over the network通过网络发送不同大小数据的最佳实践
【发布时间】:2020-07-31 00:53:42
【问题描述】:

我想通过 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)

但会不会是发送的数据乱序?

【问题讨论】:

  • 您需要创建一个应用层协议,它可以包含一个序列号,以便您可以重新排序任何乱序数据。另外,请记住 UDP 丢失数据报,因此您必须接受并非所有数据都能通过的事实,拥有可以请求重新发送丢失数据的应用程序或应用程序层协议,或者使用 TCP 之类的东西可以为您完成所有这些工作。

标签: c networking 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],因此更接近于零复制缓冲。


另一种选择可能是将MSG_PEEK 标志与recvfrom/recvmsg 一起使用。我自己从来没有用过这个,但它会是这样的:

  1. recvmsg 长度为 sizeof(struct header)MSG_PEEK 标志
  2. 第二个recvmsg,长度为sizeof(struct header) + msg.info.payload_length

这只是不必总是提供最大大小的缓冲区的好处。由于它涉及 两个 系统调用,因此可能会慢一些。但是,它可能允许一些技巧,根据消息的类型和/或长度从池中选择有效负载缓冲区

【讨论】:

    【解决方案2】:

    与基于流的协议 TCP 不同,这意味着对 recv 的调用并不完全对应于对发送的调用,UDP 是基于数据包的,这意味着每个 recvfrom 都与一个 sendto 匹配。这也意味着您需要注意发送的每条消息的大小。

    如果您发送的 UDP 数据报大于 IP 数据包中可包含的数据包,则 UDP 消息将被分成多个 UDP 数据包,从而增加数据丢失的机会。这是你要避免的事情。此外,如果您使用的是 IPv6,则在尝试发送时会收到错误消息,因为 IPv6 不支持分段。

    这对你正在做的事情意味着什么?这意味着,粗略地说,您的消息不应大于大约 1450 字节,因此您可以使用该值作为输入缓冲区的大小。然后您可以使用recvfrom 的返回值来查看实际读取了多少字节。如果你的消息比这个大,你应该把它们分成多条消息。

    与任何基于 UDP 的协议一样,您需要考虑消息丢失并需要重新传输的情况,或者消息是否乱序。

    【讨论】:

      【解决方案3】:

      其实这个问题的答案很简单。

      给定:

      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
      

      【讨论】:

        猜你喜欢
        • 2011-10-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-18
        • 2010-10-14
        • 2018-07-21
        • 2010-10-08
        • 1970-01-01
        相关资源
        最近更新 更多