2017-02-18 3 views
0

Я пытаюсь реализовать маршрутизацию расстояния по udp. У меня есть следующие: Структурыrecvfrom заполняет буфер нулями

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 * COUNT обновление байт данных. Я использую элементы гибкого массива, чтобы я мог просто сделать

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

и отправить все это в один пакет. Требуется отправка одного пакета. Я могу проверить с помощью wirehark, что данные отправляются правильно. На принимающей стороне я получаю два приема. Первые получают первые 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); 

ответ

1

Вы не можете разделить получение дейтаграммы в течение двух recv()/recvfrom() вызовов. Это фундаментальная природа сокетов, ориентированных на дейтаграмму, которые отправляют и получают работу в единицах дейтаграмм. Тем не менее, вы можете прочитать часть или всю дейтаграмму, не удаляя ее из очереди приема (т. Е. «Заглядывать» на нее). The specifications for recvfrom() это следующим образом:

Для сообщений на основе сокетов, таких как SOCK_RAW, SOCK_DGRAM и SOCK_SEQPACKET все сообщение должно быть считаны в одной операции. Если сообщение слишком длинное, чтобы вставить в буфер , а MSG_PEEK не установлен в аргументе flags, избыточные байты должны быть отброшены.

Таким образом, вы можете достичь своей цели с помощью MSG_PEEK на первом recvfrom() вызова, но учтите, что тогда вам нужно получить всю дейтаграмму во втором recvfrom() вызова, а не только ту часть, которую вы не читали первый раз.

+0

Пробовал это. Я смог получить размер, а затем прочитать полную сумму во второй раз. к сожалению, хотя второе чтение сказало, что оно получило все 16 байтов, данные были обнулены. – Sandles

+0

@ Скрепки, если вы можете проверить на принимающей стороне, например. через wirehark, что входящие данные структурированы так, как вы ожидаете, тогда я думаю, что у получателя есть другое представление о макете 'struct advertising_packet' и/или' struct cost_info', чем это делает отправитель. Это может заставить * * отобразить *, что полученные данные были равны нулю. Я предлагаю проверить фактические данные, полученные путем литья 'pkt' в' unsigned char * 'и пропуская его побайтно. –

+0

Welp. Я бросил его, и он, безусловно, получает данные. Мне все еще нужно выяснить, почему мой доступ к структуре возвращается 0, но я получаю данные, и это важно. Спасибо!! – Sandles

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

Это хорошая идея, поскольку не требуется перераспределять память. Однако я просто попробовал, и это не исправило решение. Он получил ожидаемые 16 байт для моих тестов, и первый байт по-прежнему был правильным, а остальные данные по-прежнему были обнулены. – Sandles

Смежные вопросы