0

关于我正在制作的应用程序中数据包的解释,我有一个相当复杂的问题。主机应用程序使用以下结构向客户端应用程序发送数据包:

[10 字节的头][选定的可变字节长度客户端的peerID][空字节][可变字节长度客户端的peerID][空字节][4 字节的int][可变字节长度客户端的peerID][空字节][int of 4 bytes]

以下是在此结构下生成的示例数据包:

434e4c50 00000000 006a3134 31303837 34393634 00313233 38313638 35383900 000003e8 31343130 38373439 36340000 0003e8

转换后看起来像这样:

CNLP j1410874964 1238168589 Ë1410874964 Ë

“CNLP j”是10字节的包头。“1410874964”是所选客户端的 peerID。“1238168589”是另一个客户端的 peerID。"Ë" 的 int 值为 1000。"1410874964" 是另一个客户端(在本例中为选定客户端)的 peerID。“Ë”也有一个int值1000。基本上,在这个数据包中,我传达了两件事——选定的客户端是谁以及与每个客户端关联的int值。

我的问题存在于解释端(客户端)。为了解释这种特殊类型的数据包,我使用以下方法:

    + (NSMutableDictionary *)infoFromData:(NSData *)data atOffset:(size_t) offset
{
    size_t count;

    NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:8];

    while (offset < [data length])
    {
        NSString *peerID = [data cnl_stringAtOffset:offset bytesRead:&count];
        offset += count;

        NSNumber *number = [NSNumber numberWithInteger:[data cnl_int32AtOffset:offset]];
        offset += 4;

        [info setObject:number forKey:peerID];
    }

    return info;
}

通常,这些数据包中的每一个的范围在 49 到 51 个字节之间。“offset”在之前的方法中设置,以反映包头之后的字节数加上所选播放器之后的空字节(在上述数据包的情况下,为 21)。“count”被初始化为 1。在这个特定的例子中,长度是 51。下面的方法被传递了上面的参数:

    - (NSString *)cnl_stringAtOffset:(size_t)offset bytesRead:(size_t *)amount
{
    const char *charBytes = (const char *)[self bytes];
    NSString *string = [NSString stringWithUTF8String:charBytes + offset];
    *amount = strlen(charBytes + offset) + 1;
    return string;
}

该方法应该读取数据包中的可变长度字符串,将偏移量设置为 peerID 字符串后面的空字节填充后的字节,并返回读取的字符串。然后将“数量”设置为该方法读取的字符串的字节数(这将成为返回第一个方法后的新计数值)。然后将“offset”和“count”加在一起成为新的“offset”——从这里开始解释数据包的 int 部分。上述参数传递给以下方法:

- (int)cnl_int32AtOffset:(size_t)offset
{
    const int *intBytes = (const int *)[self bytes];
    return ntohl(intBytes[offset / 4]);
}

此方法旨在返回在数据包的当前偏移值处读取的 32 位(4 字节)int 值。我认为当偏移量是一个不能被 4 整除的数字时,此方法中存在问题。在这种情况下,第一个 int 值 1000 被正确解释,并且在 while 的第一次迭代期间将 32 作为偏移量返回环形。但是,在第二次迭代期间,解释的 int 值是 909377536(通过读取数据包中的字节 36340000 而不是字节 000003E8 获得)这可能是由于此迭代期间的偏移量设置为 47(不能被 4 整除) . 在解释了上述类别中的 32 位 int 之后,在第一种方法中将偏移量添加 4 以占 4 字节(32 位 int)。如果我对不能被零整除的偏移量的直觉是正确的,非常感谢任何解决此问题的建议。很长一段时间以来,我一直在寻找解决这个问题的方法,也许新鲜的眼睛可能会有所帮助。谢谢你的帮助!!!

4

1 回答 1

0

不可移植的版本(由于许多原因未定义的行为):

return ntohl(*(const int *)([self bytes]+offset));

半便携式版本有点棘手,但在 C99中,您似乎可以假设int32_t是“通常的”二进制补码表示(没有陷阱表示,没有填充位),因此:

// The cast is necessary to prevent arithmetic on void* which is nonstandard.
const uint8_t * p = (const uint8_t *)[self bytes]+offset;
// The casts ensure the result type is big enough to hold the shifted value.
// We use uint32_t to prevent UB when shifting into the sign bit.
uint32_t n = ((uint32_t)p[0]<<24) | ((uint32_t)p[1]<<16) | ((uint32_t)p[2]<<8) | ((uint32_t)p[3]);
// Jump through some hoops to prevent UB on "negative" numbers.
// An equivalent to the third expression is -(int32_t)~n-1.
// A good compiler should be able to optimize this into nothing.
return (n <= INT32_MAX) ? (int32_t)n : -(int32_t)(UINT32_MAX-n)-1;

这不适用于没有 8 位字节的架构,但此类架构可能对如何通过网络传递事物有不同的约定。

一个好的编译器应该能够在合适的架构上将其优化为单个(可能是字节交换)负载。

于 2013-02-23T02:39:09.443 回答