【问题标题】:recvfrom fills the buffer with zerosrecvfrom 用零填充缓冲区
【发布时间】:2017-02-18 17:22:53
【问题描述】:

我正在尝试通过 udp 实现距离矢量路由。我有以下结构:

struct cost_info { 
    uint8_t id; 
    uint8_t pad;
    uint16_t cost; 
}__attribute__((__packed__));

struct advertisement_packet { 
    uint8_t type;
    uint8_t version;
    uint16_t num_updates;
    struct cost_info data[];
}__attribute__((__packed__));

所以数据包是 1 字节类型,1 字节版本,2 字节更新计数,然后是 4*更新计数字节的数据。我正在使用灵活的数组成员,这样我就可以做

sendto(fd, pkt, sizeof(struct advertisement_packet) + pkt->num_updates * sizeof(struct cost_info), 0, addr, sizeof(struct sockaddr_in));

并在一个数据包中发送整个内容。需要发送一个数据包。 我能够使用wireshark 验证数据是否正确发送。在接收端,我做了两个接收。第一个获取前 4 个字节,因此我可以知道需要多少更新。然后我相应地调整接收器缓冲区的大小并接收 4*num_updates 字节。问题是第二次接收用零填充我的缓冲区!如果我期望 12 个字节的数据,我会得到 12 个字节的零。下次我从该套接字读取时,我按预期得到下一个数据包的开始。为什么会这样?

如果需要,这是我的接收代码。

recvfrom(i, pkt, sizeof(struct advertisement_packet),0,NULL,0);
//reallocate more space if the incoming data will need it
if (pkt->num_updates > pkt_cost_slots) {
  pkt_cost_slots *= 2;
  pkt = realloc(pkt,sizeof(struct advertisement_packet) + sizeof(struct cost_info) * pkt_cost_slots);
  assert(pkt);
}
//receive data
recvfrom(i,pkt->data,pkt->num_updates * sizeof(struct cost_info),0,NULL,0);

【问题讨论】:

    标签: c sockets udp recvfrom


    【解决方案1】:

    您不能将一个数据报的接收拆分为两个 recv() / recvfrom() 调用。以数据报为单位发送和接收操作是面向数据报的套接字的基本性质。但是,您可以读取部分或全部数据报而不将其从接收队列中删除(即“窥视”它)。 The specifications for recvfrom()这样说:

    对于基于消息的套接字,例如SOCK_RAWSOCK_DGRAMSOCK_SEQPACKET,整个消息应 在单个操作中读取。如果消息太长而无法放入 提供的缓冲区,并且 MSG_PEEK 未在 flags 参数中设置,则 多余的字节将被丢弃。

    因此,您可以通过在第一个 recvfrom() 调用中使用 MSG_PEEK 来实现您的目标,但请注意,您需要在第二个 recvfrom() 调用中接收整个数据报,而不仅仅是您第一次没有阅读的部分。

    【讨论】:

    • 试过这个。我能够获得大小,然后第二次阅读全部内容。不幸的是,即使第二次读取说它得到了所有 16 个字节,数据仍然被清零。
    • @Sandles,如果您可以在接收方进行验证,例如通过wireshark,传入数据的结构与您预期的一样,那么我最好的猜测是接收者对struct advertisement_packet和/或struct cost_info的布局与发送者有不同的想法。这会使接收到​​的数据看起来全为零。我建议通过将pkt 转换为unsigned char * 并逐字节检查接收到的实际数据。
    • 求助。我投了它,它肯定是在获取数据。我仍然需要弄清楚为什么我的结构访问返回 0,但我正在获取数据,这很重要。谢谢!!
    【解决方案2】:

    UDP 是一种数据报协议。整个数据报包被传递到第一个recvfrom 调用中。如果您的缓冲区大小不足,则数据字节将丢失。它们不会传递给后续的recvfrom

    所以解决方案是只传递一个足够大的缓冲区以容纳任何 UDP 数据报,然后将其转换为您的数据类型。

    unsigned char buffer[65535];
    struct advertisement_packet *pkt = (advertisement_packet *)buffer;
    recvfrom(i, buffer, sizeof(buffer), (sockaddr*)&addr, &addrlen);
    

    这里有一个更好的解决方案,它也执行通常的安全检查,这样您就不会被恶意攻击者欺骗进入缓冲区溢出。

    int result = 0;
    unsigned char buffer[65535];
    struct advertisement_packet *pkt = (advertisement_packet *)buffer;
    sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);
    int accepted = false;
    
    result = recvfrom(i, buffer, sizeof(buffer), (sockaddr*)&addr, &addrlen);
    
    if (result > 0)
    {
        size_t expected_size = sizeof(struct advertisement_packet) + pkt->num_updates * sizeof(struct cost_info);
    
        if (result >= expected_size)
        {
            accepted = true;
        }
        else
        {
            // packet is malformed or undersized, so let's avoid a buffer overrun
            result = -1;
        }
    }
    
    if (accepted)
    {
         // got packet and it's valid
         // continue parsing it, or make a copy of it.
    }
    

    【讨论】:

    • 这是一个好主意,因为不必重新分配内存。但是我只是尝试了它并没有解决解决方案。它收到了我的测试预期的 16 个字节,第一个字节仍然正确,其余数据仍然归零。
    猜你喜欢
    • 1970-01-01
    • 2019-03-31
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 2013-02-21
    • 1970-01-01
    • 2021-12-22
    • 1970-01-01
    相关资源
    最近更新 更多