NSData数据包解释

时间:2013-02-23 02:10:03

标签: ios ios6 nsdata packet

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

[10字节的标题] [可变字节长度的所选客户端的peerID] [空字节] [可变字节长度的客户端的peerID] [空字节] [4字节的int] [可变字节的客户端的peerID length] [空字节] [4个字节的整数]

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

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

转换它看起来像这样:

CNLP j14108749641238168589Ë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字符串后面的空字节填充之后的字节,并返回读取的字符串。然后将“amount”设置为方法为字符串读取的字节数(这将在返回第一个方法后成为count的新值)。然后将“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,并且在第一次迭代期间返回了32作为偏移量环。但是,在第二次迭代期间,解释的int值为909377536(从包中读取字节36340000而不是字节000003E8获得)这可能是由于此迭代期间的偏移设置为47(不能被4整除)的事实。在解释上面类别中的32位int之后,在第一种方法中将4添加到偏移量以考虑4字节(32位int)。如果我对不能被零整除的偏移的直觉是正确的,那么任何解决这个问题的建议都会非常感激。我一直在寻找解决这个问题的方法很长一段时间,也许新鲜的眼睛可能有所帮助。谢谢你的帮助!!!

1 个答案:

答案 0 :(得分:0)

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

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

半便携版本有点棘手,但是in C99看起来你可以认为int32_t是“通常的”二进制补码表示(没有陷阱表示,没有填充位),因此: / p>

// 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位字节的体系结构不起作用,但是这样的体系结构可能对通过网络传递的方式有不同的约定。

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