recvfrom用零填充缓冲区

时间:2017-02-18 17:22:54

标签: c sockets udp recvfrom

我正在尝试通过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);

2 个答案:

答案 0 :(得分:1)

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.
}

答案 1 :(得分:1)

您无法通过两次recv() / recvfrom()来电分割一个数据报。面向数据报的套接字的基本特性是以数据报为单位进行发送和接收操作。但是,您可以读取部分或全部数据报,而无需将其从接收队列中删除(即#34;查看"在此处)。 The specifications for recvfrom()这样说:

  

对于基于消息的套接字,例如SOCK_RAW,   SOCK_DGRAMSOCK_SEQPACKET,整个信息应该是。MSG_PEEK   可以在一次操作中读取。如果邮件太长而无法容纳   提供缓冲区,并且在flags参数中没有设置MSG_PEEK   多余的字节应该被丢弃。

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