c 重新解释指向具有更大大小的数据类型的指针

c reinterpret pointer to datatype with bigger size

我正在尝试解释通过 TCP 连接获得的 WebSocket 帧。我想在纯 C 中执行此操作(所以没有 reinterpret_cast)。格式在 IEEE RFC 6455 中指定。我想填写以下结构:

typedef struct {
    uint8_t flags;
    uint8_t opcode;
    uint8_t isMasked;
    uint64_t payloadLength;
    uint32_t maskingKey;
    char* payloadData;
} WSFrame;

具有以下功能:

static void parseWsFrame(char *data, WSFrame *frame) {
    frame->flags = (*data) & FLAGS_MASK;
    frame->opcode = (*data) & OPCODE_MASK;
    //next byte
    data += 1;
    frame->isMasked = (*data) & IS_MASKED;
    frame->payloadLength = (*data) & PAYLOAD_MASK;

    //next byte
    data += 1;

    if (frame->payloadLength == 126) {
        frame->payloadLength = *((uint16_t *)data);
        data += 2;
    } else if (frame->payloadLength == 127) {
        frame->payloadLength = *((uint64_t *)data);
        data += 8;
    }

    if (frame->isMasked) {
        frame->maskingKey = *((uint32_t *)data);
        data += 4;
    }else{
        //still need to initialize it to shut up the compiler
        frame->maskingKey = 0;
    }
    frame->payloadData = data;
}

该代码适用于 ESP8266,因此只能使用 printfs 到串行控制台进行调试。使用这种方法,我发现代码在 frame->maskingKey = *((uint32_t *)data); 之后立即崩溃并且跳过了前两个 ifs,所以这是我第一次将指针转换为另一个指针。

数据没有[=13=]终止,但我在数据接收回调中得到了大小。在我的测试中,我试图通过已经建立的 WebSocket 发送消息 'test',并且接收到的数据长度是 10,所以:

在代码崩溃时,我希望数据从初始位置偏移 2 个字节,因此它有足够的数据来读取接下来的 4 个字节。

我很长时间没有编写任何 C 代码,所以我希望我的代码中只有一个小错误。

PS.: 我看过很多代码,它们逐字节解释值并移动值,但我看不出为什么这种方法也不起作用。

将 char* 转换为指向更大类型的指针的问题是某些体系结构不允许未对齐读取。

也就是说,例如,如果你试图通过指针读取一个uint32_t,那么指针本身的值必须是4的倍数。否则,在某些架构上,你会得到某种总线故障(例如 - 信号、陷阱、异常等)。

因为此数据是通过 TCP 传入的,并且流/协议的格式没有任何填充,所以您可能需要逐字节地将其从缓冲区中读出到局部变量中(例如 - 使用memcpy) 视情况而定。例如:

if (frame->isMasked) {
    mempcy(&frame->maskingKey, data, 4);
    data += 4;
    // TODO: handle endianness: e.g.: frame->maskingKey = ntohl(frame->maskingKey);
}else{
    //still need to initialize it to shut up the compiler
    frame->maskingKey = 0;
}

有两个问题:

  • data 可能未正确对齐 uint32_t
  • data 中的字节顺序可能与您的硬件用于整数值表示的顺序不同。 (有时称为 "endianness issue")。

要编写可靠的代码,请查看消息规范以了解字节的进入顺序。如果它们是最高有效字节在前,那么代码的可移植版本将是:

unsigned char *udata = (unsigned char *)data;
frame->maskingKey = udata[0] * 0x1000000ul
                  + udata[1] * 0x10000ul
                  + udata[2] * 0x100ul
                  + udata[3];

乍一看这可能看起来很少,但您可以制作一个将指针作为参数的内联函数,以及 returns uint32_t,这将使您的代码保持可读性。

类似的问题适用于您阅读 uint16_t