簡體   English   中英

在C中解析二進制數據?

[英]Parsing Binary Data in C?

有沒有關於如何在C中讀取和解析二進制數據的庫或指南?

我正在研究一些將在網絡套接字上接收TCP數據包然后根據規范解析該二進制數據的功能,並通過代碼將信息轉換為更有用的形式。

是否有任何圖書館可以做到這一點,甚至是執行此類事情的入門書?

我不得不同意這里的許多回應。 我強烈建議你避免將結構轉換為傳入數據的誘惑。 它似乎很有吸引力,甚至可能適用於您當前的目標,但如果代碼被移植到另一個目標/環境/編譯器,您將遇到麻煩。 原因如下:

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,也沒有位域來減慢代碼速度。

在C / C ++中執行此操作的標准方法實際上是以'gwaredd'建議的方式轉換為結構體

它並不像人們想象的那樣不安全。 您首先轉換為您期望的結構,如在他/她的示例中, 然后您測試該結構的有效性。 您必須測試最大/最小值,終止序列等。

你在什么平台上必須閱讀Unix網絡編程,第1卷:套接字網絡API 買它,借它,偷它(受害者會理解,這就像偷食物或東西......),但要讀它。

在閱讀史蒂文斯之后,大部分內容都會更有意義。

讓我重申你的問題,看看我是否理解得當。 您正在尋找將對數據包進行正式描述的軟件,然后生成一個“解碼器”來解析這些數據包?

如果是這樣,該字段中的引用是PADS 介紹它的一篇好文章是PADS:用於處理Ad Hoc數據的領域專用語言 PADS非常完整,但遺憾的是非自由許可。

有可能的替代方案(我沒有提到非C解決方案)。 顯然,沒有一個可以被視為完全生產就緒:

如果你讀法語,我在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,這在網絡協議中很常見(主要是出於歷史原因)。 你當然可以聰明地做一些指針算法並且不需要臨時,但我發現這種方式使它更清晰,更容易理解。 在調試時,在這種原語中具有最大透明度是一件好事。

下一步是開始定義特定於協議的消息,並編寫讀/寫原語以進行匹配。 在這個級別,考慮代碼生成; 如果您的協議以一般的機器可讀格式描述,您可以從中生成讀/寫功能,這可以節省很多麻煩。 如果協議格式足夠聰明 ,這會更難,但通常是可行的並且強烈建議。

您可能對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 你不會后悔的!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM