Сокеты: получение данных UDP в буфер с выравниванием по словам, используя recvfrom без memcpy?

0

Я допускаю полное незнание, когда дело доходит до программирования сокетов.

На самом деле я просто пытаюсь написать очень простой тестовый жгут для немного аппаратного обеспечения с рабочим UDP-интерфейсом на нем. Испытательный жгут должен иметь возможность выдавать UDP-пакеты аппаратным средствам и получать от него UDP-пакеты, возвращая полученные UDP-пакеты обратно на устройство с возможным повторением.

Критической точкой является то, что устройство ожидает данных как 32-битные слова. Это означает, что фактическое содержимое данных пакета UDP должно быть выровнено по словам, а также на стороне приема в моей тестовой жгуте. Мне нужно обработать буфер данных в виде 32-разрядного буфера с выравниванием по словам.

Между тем размер заголовка UDP означает, что, выходя из аппаратного обеспечения, перед фактическими данными есть два байта заполнения в поле перед данными, потому что когда вы добавляете во все различные заголовки, вы получаете начало смещение данных, которое не является 32-битным выравниванием по слову - оно отключается полусловом.

То, что, как я думал, будет работать, - это определение буфера с выравниванием по словам в моей функции приема UDP, а затем переход к recvfrom указателю, переданному в char, а затем смещение на 2 (что соответствует несоосности полуслова). В этой ситуации фактические слова данных должны выходить выравниваться в буфер, возвращенный пользователю, - заполнение, входящее во второй полуслот первого слова буфера. Но это происходит в функции recvfrom. Это как если бы recvfrom решил разместить начало буфера данных на 32-битной границе слова, что абсолютно не нужно делать.

Это вообще его внутреннее поведение? Если это так, то кажется, что в буквальном смысле нет возможности сделать абсурдную и неэффективную memcpy; не кажется мне особенно заслуживающим доверия, что нет других решений. Итак, как я могу заставить его правильно копировать слова данных на границах слов?

Здесь функция приема. Обратите внимание, что если активные строки заменяются прокомментированными строками, функция не выполняет segfault, поэтому я могу определенно изолировать их как проблему. (Выполнение этого всего лишь шаг отладки - это не помогает с проблемой, потому что если это будет сделано, то позже, когда я пойду, чтобы прочитать слова, это не работает по ожидаемым причинам)

bool EthernetSoftwareIF::receiveUDP(rx_entry_t &rxdata)
{        
        uint32_t *data_w = new uint32_t[350]; // need a word-aligned buffer
        //char *data = (char *)data_w; 
        char *data = ((char *)data_w)+sizeof(uint16_t); // adjust for 2-byte padding

    #ifdef SIMULATION
            // simulation mode
        int len = sizeof(this->remoteServAddr);
        int bytecount = this->socket_if.recvfrom(data, sizeof(data_w)-sizeof(uint16_t), MSG_DONTWAIT, (sockaddr*)&(this->remoteServAddr), &len );
                //int bytecount = this->socket_if.recvfrom(data, sizeof(data_w), MSG_DONTWAIT, (sockaddr*)&(this->remoteServAddr), &len );
    #else
        socklen_t len = sizeof(this->remoteServAddr);
        int bytecount = recvfrom(this->udp_socket, data, sizeof(data_w)-sizeof(uint16_t), MSG_DONTWAIT, (sockaddr*)&(this->remoteServAddr), &len );
                //int bytecount = recvfrom(this->udp_socket, data, sizeof(data_w), MSG_DONTWAIT, (sockaddr*)&(this->remoteServAddr), &len );
    #endif

    if (bytecount < 0)
    {
        #ifdef SIMULATION
            printf("EthernetSoftwareIF::receiveUDP: error during packet reception (error code: %d).\n",this->socket_if.lasterror());
        #else
            printf("EthernetSoftwareIF::receiveUDP: error during packet reception.\n");
        #endif

        return false;
    }

    rxdata.uiBytes = bytecount;
    rxdata.uiSourceIP = htonl(this->remoteServAddr.sin_addr.s_addr);
    rxdata.uiSourcePort = htons(this->remoteServAddr.sin_port);
    rxdata.uiDestPort = REMOTE_SERVER_PORT;
        rxdata.pData = (void*)data_w;

    return true; 
 }  

(в ответ на сообщение immibis)

да, я знаю, что это не дает мне заголовки, и мне не нужно их видеть. Но проблема в том, что как только вы пройдете все заголовки, начало буфера не выравнивается по словам. Размер заголовков таков, что последний байт данных в заголовках находится на границе полуслова, а не на границе слова.

Таким образом, буфер данных будет начинаться с границы полуслова. Это означает, что первое полуслое в буфере данных должно заполняться заполнением, так что фактические данные в буфере выравниваются по словам. Затем мне нужно прочитать слова из буфера, начиная с первого реального слова. То, что я увижу в пакете UDP, который я действительно получаю, это:

<halfword_padding><first_word><word>...<last_word>

Поэтому я хочу убедиться, что буфер приема, который я настроил recvfrom для записи, выравнивается по слову в начале его выделенного пространства, но это дополнение полуслова, которое является первой частью данных, которые придут из полученного пакета udp помещается во второе полуслое в этом буфере, так что последующее фактическое первое слово моих данных будет во втором слове выделенного буфера приема, и я могу читать прямо из него на границах, выровненных по словам, Таким образом, буфер данных, после заполнения, должен выглядеть так:

<first_halfword(ignored)><halfword_padding><first_word><word>...<last_word>

Имеет ли это смысл?


Обновление 26 февраля 2014 года

Я делал некоторые эксперименты с линиями recvfrom и как я настраивал буферы data_w и данные указателя буфера. Кажется, что это ясно:

Если указатель буфера, который вы указываете в аргументе буфера recvfrom указывает на начало вашего выделенного буфера, то recvfrom работает нормально. Однако, если вы укажете ему указатель со смещением от начального адреса выделенного буфера, результат непредсказуем. Различные способы определения смещений и длины буфера приводили к совершенно другим результатам.

Поэтому в моей ситуации, если я даю recvfrom указатель на data_w (который я могу использовать для любого типа, похоже), то recvfrom преуспевает. Но если данные производятся от cast-to-type и offset, то recvfrom разрывает всевозможные разные и поверхностно несвязанные способы.

Я не понимаю, как возможно, что recvfrom может быть чувствительным к смещению в буфер, объявленный снаружи, но факты, которые я видел, - это факты. Может быть, кто-то может пролить свет на внутренности recvfrom, которые могут объяснить это поведение.

Между тем, если это действительно так, то кажется, что вывод заключается в следующем: если вам нужно прочитать данные, которые были выровнены по границам слов в общем пакете UDP, и, таким образом, было не менее 2 байтов заполнения в начале часть данных пакета - у вас нет выбора, кроме как использовать memcpy для перераспределения данных. Это немного сложно поверить - наверняка есть другие доступные варианты, которые не требуют взлома вещей между двумя разными буферами?

  • 3
    Я не знаю, почему вы беспокоитесь о заголовках udp. Ваше приложение их не видит. Данные, считанные в ваш буфер, являются полезной нагрузкой, а не необработанным пакетом udp.
  • 2
    Кроме того, sizeof(data_w) это нонсенс. Это не ваш размер буфера; это размер указателя. Таким образом, вы читаете только 4 (или 8, в 64-битной системе) данных.
Показать ещё 3 комментария
Теги:
sockets
udp

2 ответа

1

recvfrom не дает вам заголовки. Он копирует только данные из пакета в ваш буфер. Если ваш буфер выравнивается по словам, то начало данных будет выравниваться по словам.

При использовании сокетов вам не нужно заботиться о UDP (или IP или Ethernet) заголовках.

  • 0
    Привет, да, я знаю, что это не дает мне заголовки, и мне не нужно их видеть. Но проблема в том, что как только вы пройдете все заголовки, начало буфера не выравнивается по словам. Размер заголовков таков, что последний байт данных в заголовках находится на границе полуслова, а не на границе слова. Таким образом, буфер данных будет начинаться с границы полуслова.
  • 0
    Извините, у вас есть искаженный комментарий выше, потому что кажется, что система комментариев не допускает возврат каретки; он интерпретирует их как отправку комментария, а затем мои попытки отредактировать комментарий закончились (очевидно, у вас есть только 5 минут для редактирования комментария). В любом случае, см. Полный ответ ниже, написанный как «ответ», потому что в поле комментария не хватило места.
Показать ещё 7 комментариев
0

Итак, аппаратное обеспечение генерирует UDP payloaad из <pad> <pad> <32-bit word> <32-bit word>... Я правильно понимаю?

Как насчет просто сделать что-то вроде:

union tp {
    uint32_t w;
    uint8_t b[4];
};

union tp rxbuffer[number of words + 1];
uint32_t *payload_ptr = &rxbuffer[1].w;
uint8_t * recvptr = &rxbuffer[0].b[2];

Или один из его эквивалентов типа Punny? Затем вы передаете recvptr в recvfrom и получаете доступ к полезной нагрузке, используя payload_ptr, который выровнен по 32 битам, поскольку rxbuffer равен.

Пуннинг типов не является полностью безопасным в соответствии со стандартом, но практически везде на практике (выполнение с помощью объединения означает, что он работает даже с gcc и -fstrict-aliasing).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню