套接字:使用recvfrom将UDP数据转换为字对齐缓冲区而不使用memcpy?

时间:2014-02-24 00:58:12

标签: c++ c sockets udp

我承认在编程套接字时完全无知。

实际上,我只是试图为一些具有工作UDP接口的硬件编写一个非常简单的测试工具。测试工具应该能够向硬件发出UDP数据包,并从中接收UDP数据包,将收到的UDP数据包直接返回到设备,并可能重复。

关键点是设备期望数据为32位字。这意味着UDP数据包的实际数据内容需要字对齐,并且在我的测试工具的接收端,我需要将数据缓冲区处理为32位字对齐缓冲区。

同时,UDP标头的大小意味着,从硬件出来,在实际数据之前的数据字段前面有2个字节的填充,因为当你添加所有各种标题时你最终得到一个数据偏移的开始,它不是32位字对齐的 - 它被半字关闭。

我认为可行的是在我的UDP接收函数中定义一个字对齐的缓冲区,然后传递给一个转换为char的指针,然后偏移2(对应于半字未对齐)。在那种情况下,实际数据字应该在返回给用户的缓冲区中对齐 - 填充进入缓冲区第一个字的后半字。但它在recvfrom函数中有段错误。好像recvfrom被确定为将数据缓冲区的起始位置放在32位字边界上,这是绝对不能做到的。

一般来说,这是它的内部行为吗?如果是这样的话,似乎一个字面意思就是没有选择做一个荒谬而低效的memcpy;在我看来,除了其他解决方案之外,我并不特别可信。那么如何才能让它在字边界上正确复制数据字呢?

这是接收功能。请注意,如果活动行被注释行替换,则函数不会出现段错误,因此我绝对可以将这些行隔离为问题。 (这样做只是一个调试步骤 - 它没有帮助解决问题,因为如果这样做了,那么以后当我去阅读它没有按预期的原因工作时)< / p>

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>

因此,我想要做的是确保我设置的接收缓冲区在其分配的空间的开头是字对齐的,但是半字填充是数据的第一部分将来自接收到的udp数据包放入该缓冲区的后半字,以便后续实际的数据第一个字位于分配的接收缓冲区的第二个字中,我可以直接读出它的字 - 对齐边界。因此,数据缓冲区一旦填充,应该如下所示:

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

这有意义吗?


2014年2月26日更新

我一直在对recvfrom行进行一些实验,以及如何设置缓冲区data_w和缓冲区指针数据。似乎很清楚的是:

如果您在recvfrom的 buffer 参数中提供的缓冲区指针指向已分配缓冲区的 start ,则recvfrom正常进行。但是,如果为其指定一个偏离分配缓冲区起始地址的指针,则结果为不可预测。指定偏移量和缓冲区长度的各种不同方法导致了截然不同的结果。

所以在我的情况下,如果我给recvfrom一个指向data_w的指针,(我可以转换为任何类型,似乎),然后recvfrom成功。但是,如果数据来自于铸造类型和偏移,那么recvfrom会以各种不同的和表面上不相关的方式中断。

我不明白recvfrom如何可能对从外部声明的缓冲区的偏移敏感,但我所见的事实是事实。也许有人可以对recvfrom的内部结构有所了解,可以解释这种行为。

同时,如果确实如此,那么结论似乎是:如果你需要读取在整个UDP数据包内的字边界上对齐的数据 - 因此至少有2个字节的填充数据包的数据部分的开头 - 您别无选择,只能使用memcpy重新对齐数据。这似乎有点难以置信 - 当然还有其他选择可以不需要在两个不同的缓冲区之间进行操作?

2 个答案:

答案 0 :(得分:1)

recvfrom没有为您提供标题。它只将数据包中的数据复制到缓冲区中。如果缓冲区是字对齐的,那么数据的开头将是字对齐的。

使用套接字时,您根本不需要关心UDP(或IP或以太网)标头。

答案 1 :(得分:0)

好吧,这样硬件会生成 <32位字> <32位字>的UDP payloaad ...我正确理解了吗?

如何做类似的事情:

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];

还是其中一种类型的细腻对等? 然后,将recvptr传递到recvfrom,然后使用有效负载访问有效负载,因为rxbuffer是32位对齐的有效负载。

按标准划分的类型修剪并不是完全安全的,但实际上几乎在任何地方都是可以的(通过并集进行操作意味着它甚至可以与gcc和-fstrict-aliasing一起使用)。