recvfrom 用零填充缓冲区
recvfrom fills the buffer with zeros
我正在尝试通过 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);
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.
}
您不能将一个数据报的接收分成两个 recv()
/ recvfrom()
调用。 datagram-oriented 套接字的基本性质是发送和接收以数据报为单位进行操作。但是,您可以读取部分或全部数据报,而无需将其从接收队列中删除(即 "peek" 处)。 The specifications for recvfrom()
这样说:
For message-based sockets, such as SOCK_RAW
,
SOCK_DGRAM
, and SOCK_SEQPACKET
, the entire message shall
be read in a single operation. If a message is too long to fit in the
supplied buffer, and MSG_PEEK
is not set in the flags argument, the
excess bytes shall be discarded.
因此,您可以通过在第一个 recvfrom()
调用中使用 MSG_PEEK
来实现您的目标,但请注意,然后您需要接收 整个 数据报第二个 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);
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.
}
您不能将一个数据报的接收分成两个 recv()
/ recvfrom()
调用。 datagram-oriented 套接字的基本性质是发送和接收以数据报为单位进行操作。但是,您可以读取部分或全部数据报,而无需将其从接收队列中删除(即 "peek" 处)。 The specifications for recvfrom()
这样说:
For message-based sockets, such as
SOCK_RAW
,SOCK_DGRAM
, andSOCK_SEQPACKET
, the entire message shall be read in a single operation. If a message is too long to fit in the supplied buffer, andMSG_PEEK
is not set in the flags argument, the excess bytes shall be discarded.
因此,您可以通过在第一个 recvfrom()
调用中使用 MSG_PEEK
来实现您的目标,但请注意,然后您需要接收 整个 数据报第二个 recvfrom()
调用,不仅仅是您第一次没有阅读的部分。