用C解析二进制数据?

有没有关于如何在C中读取和解析二进制数据的库或指南?

我正在研究一些将在网络套接字上接收TCP数据包然后根据规范解析该二进制数据的function,并通过代码将信息转换为更有用的forms。

是否有任何图书馆可以做到这一点,甚至是执行此类事情的入门书?

在C / C ++中执行此操作的标准方法实际上是以’gwaredd’建议的方式转换为结构体

它并不像人们想象的那样不安全。 您首先转换为您期望的结构,如在他/她的示例中, 然后您测试该结构的有效性。 您必须测试最大/最小值,终止序列等。

你在什么平台上必须阅读Unix网络编程,第1卷:套接字网络API 。 买它,借它,偷它(受害者会理解,这就像偷食物或东西……),但要读它。

在阅读史蒂文斯之后,大部分内容都会更有意义。

我不得不同意这里的许多回应。 我强烈建议你避免将结构转换为传入数据的诱惑。 它似乎很有吸引力,甚至可能适用于您当前的目标,但如果代码被移植到另一个目标/环境/编译器,您将遇到麻烦。 原因如下:

Endianness :你现在使用的架构可能是big-endian,但你的下一个目标可能是little-endian。 或相反亦然。 您可以使用宏(例如ntoh和hton)来克服这个问题,但这是额外的工作,并确保每次引用该字段时调用这些宏。

对齐 :您正在使用的架构可能能够在奇数寻址偏移处加载多字节字,但许多架构不能。 如果一个4字节的字跨越4字节的对齐边界,则负载可能会产生垃圾。 即使协议本身没有未对齐的字,有时字节流本身也是未对齐的。 (例如,虽然IP标头定义将所有4字节字放在4字节边界上,但以太网标头通常会将IP标头本身推送到2字节边界。)

填充 :您的编译器可能会选择紧密打包您的结构而不填充,或者它可能会插入填充以处理目标的对齐约束。 我在相同编译器的两个版本之间看到了这种变化。 您可以使用#pragmas来强制解决问题,但#pragmas当然是特定于编译器的。

位排序 :C位域内的位排序是特定于编译器的。 另外,这些位很难为运行时代码“搞定”。 每次在结构中引用位域时,编译器都必须使用一组掩码/移位操作。 当然,你将不得不在某个时刻进行掩蔽/移动,但如果速度是一个问题,最好不要在每次参考时都这样做。 (如果空间是最重要的问题,那么请使用位域,但要小心。)

这一切并不是说“不要使用结构”。 我最喜欢的方法是声明所有相关协议数据的友好的native-endian结构,没有任何位域,也不用担心问题,然后编写一组使用struct作为中间人的对称打包/解析例程。

typedef struct _MyProtocolData { Bool myBitA; // Using a "Bool" type wastes a lot of space, but it's fast. Bool myBitB; Word32 myWord; // You have a list of base types like Word32, right? } MyProtocolData; Void myProtocolParse(const Byte *pProtocol, MyProtocolData *pData) { // Somewhere, your code has to pick out the bits. Best to just do it one place. pData->myBitA = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_A_MASK >> MY_BIT_A_SHIFT; pData->myBitB = *(pProtocol + MY_BITS_OFFSET) & MY_BIT_B_MASK >> MY_BIT_B_SHIFT; // Endianness and Alignment issues go away when you fetch byte-at-a-time. // Here, I'm assuming the protocol is big-endian. // You could also write a library of "word fetchers" for different sizes and endiannesses. pData->myWord = *(pProtocol + MY_WORD_OFFSET + 0) << 24; pData->myWord += *(pProtocol + MY_WORD_OFFSET + 1) << 16; pData->myWord += *(pProtocol + MY_WORD_OFFSET + 2) << 8; pData->myWord += *(pProtocol + MY_WORD_OFFSET + 3); // You could return something useful, like the end of the protocol or an error code. } Void myProtocolPack(const MyProtocolData *pData, Byte *pProtocol) { // Exercise for the reader! :) } 

现在,其余的代码只是在友好的快速struct对象中操作数据,并且只在必须与字节流接口时才调用pack / parse。 不需要ntoh或hton,也没有位域来减慢代码速度。

让我重申你的问题,看看我是否理解得当。 您正在寻找能够正式描述数据包的软件,然后会产生一个“解码器”来解析这些数据包吗?

如果是这样,该字段中的引用是PADS 。 介绍它的一篇好文章是PADS:一种用于处理Ad Hoc数据的特定领域语言 。 PADS非常完整,但遗憾的是非许可证。

有可能的替代方案(我没有提到非C解决方案)。 显然,没有一个可以被视为完全生产就绪:

  • binpac
  • PacketTypes
  • DataScript

如果你读法语,我在Générationdedécodeursdeformats binaires中总结了这些问题。

根据我的经验,最好的方法是首先编写一组原语,从二进制缓冲区读取/写入某种类型的单个值。 这为您提供了高可见性,以及处理任何字节序问题的非常简单的方法:只需使函数正确执行即可。

然后,您可以为每个协议消息定义struct ,并为每个消息编写pack / unpack(有些人称之为序列化/反序列化)函数。

作为基本情况,提取单个8位整数的原语可能如下所示(假设主机上有8位char ,您可以添加一层自定义类型以确保它也是如此):

 const void * read_uint8(const void *buffer, unsigned char *value) { const unsigned char *vptr = buffer; *value = *buffer++; return buffer; } 

在这里,我选择通过引用返回值,并返回更新的指针。 这是一个品味问题,您当然可以返回值并通过引用更新指针。 读取函数更新指针,使这些可链接,这是设计的关键部分。

现在,我们可以编写一个类似的函数来读取16位无符号数量:

 const void * read_uint16(const void *buffer, unsigned short *value) { unsigned char lo, hi; buffer = read_uint8(buffer, &hi); buffer = read_uint8(buffer, &lo); *value = (hi << 8) | lo; return buffer; } 

在这里,我假设传入数据是big-endian,这在网络协议中很常见(主要是出于历史原因)。 你当然可以聪明地做一些指针算术并且不需要临时,但我发现这种方式使它更清晰,更容易理解。 在调试时,在这种原语中具有最大透明度是一件好事。

下一步是开始定义特定于协议的消息,并编写读/写原语以进行匹配。 在那个级别,考虑代码生成; 如果您的协议以某种通用的机器可读格式描述,您可以从中生成读/写function,这可以节省很多麻烦。 如果协议格式足够聪明,那就更难了 ,但通常可行并且强烈推荐。

您可能对Google Protocol Buffers感兴趣,它基本上是一个序列化框架。 它主要用于C ++ / Java / Python(这些是Google支持的语言),但是一直在努力将其移植到其他语言,包括C语言。 (我根本没有使用过C端口,但是我负责其中一个C#端口。)

您实际上不需要在C中解析二进制数据,只需将指针转换为您认为应该是的任何指针。

 struct SomeDataFormat { .... } SomeDataFormat* pParsedData = (SomeDataFormat*) pBuffer; 

只要警惕端序问题,类型大小,读取缓冲区末尾等等

解析/格式化二进制结构是在C中比在更高级/托管语言中更容易做的事情之一。 您只需定义一个与您要处理的格式相对应的结构,结构就是解析器/格式化程序。 这是有效的,因为C中的结构表示精确的内存布局(当然,它已经是二进制)。 另见kervin和gwaredd的回复。

我真的不明白你在找什么样的图书馆? 将采用任何二进制输入并将其解析为未知格式的通用库? 我不确定任何语言都可以存在这样的库。 我想你需要详细说明你的问题。

编辑
好的,所以在看完Jon的答案后,似乎有一个库,很好的库,它更像是代码生成工具。 但是正如许多人所说的那样只是将数据转换为适当的数据结构,并且需要适当的谨慎,即使用打包结构并处理字节序问题。 使用C这样的工具只是一种矫枉过正。

基本上关于转换为struct工作的建议,但请注意,数字可以在不同的体系结构上以不同的方式表示。

为了处理字节序问题,引入了网络字节顺序 – 通常的做法是在发送数据之前将数字从主机字节顺序转换为网络字节顺序,并在接收时转换回主机顺序。 请参阅函数htonlhtonsntohlntohs

真的考虑科尔文的建议 – 读UNP 。 你不会后悔的!