C++ 中的 BEncode 解析






4.67/5 (3投票s)
一个非常高效的 C++ BEncode 词法分析器。
引言
BENCODE 是一种数据编码方法,与 .torrent 文件紧密相关。本文介绍了一个可以解码该格式流的 C++ 类。
背景
实际上,我在一个正在进行的项目中需要一个 BENCODE 词法分析代码,无论我如何广泛地进行网络搜索,我所能找到的要么是非 C/C++ 代码,要么代码过于依赖我无法找到的其他文件,或者代码处理了我不需要的事情。所以,像往常一样,对我来说唯一合理的解决方案是花几个小时自己完成,按照我想要的方式。
使用代码
在进入完全直接的实际用法部分之前,我想强调一下 bencode_lexer
类的一些要点
bencode_lexer
具有流感知能力,这意味着您可以按代码读取的顺序向其提供任意大小的数据块。- 该类还充当解析上下文。换句话说,一个
bencode_lexer
实例一次只能处理一个内容流。 - 该类不是线程安全的,但由于它不应该在并行流上运行,我认为这完全可以接受。
这是一个简单的代码,它将 bencode 流的内容转储到标准输出设备。它可能在现实世界中不太有用,但它展示了如何使用该类
#include <stdio.h>
#include "bencode.h"
int g_iIdent = 0;
bool Callback(bencode_lexer::Event e, const void* pv, int cb, long lParam)
{
switch( e )
{
case bencode_lexer::eBeginDictionary:
{
for(int i=0; i < g_iIdent; ++i) fputc('\t', stdout);
fputs("dictionary {\n", stdout);
++g_iIdent;
break;
}
case bencode_lexer::eEndDictionary:
{
--g_iIdent;
for(int i=0; i < g_iIdent; ++i) fputc('\t', stdout);
fputs("}\n", stdout);
break;
}
case bencode_lexer::eBeginList:
{
for(int i=0; i < g_iIdent; ++i) fputc('\t', stdout);
fputs("list {\n", stdout);
++g_iIdent;
break;
}
case bencode_lexer::eEndList:
{
--g_iIdent;
for(int i=0; i < g_iIdent; ++i) fputc('\t', stdout);
fputs("}\n", stdout);
break;
}
case bencode_lexer::eInteger:
{
for(int i=0; i < g_iIdent; ++i) fputc('\t', stdout);
fprintf(stdout, "integer=%lu\n", *(int*)pv);
break;
}
case bencode_lexer::eString:
{
for(int i=0; i < g_iIdent; ++i) fputc('\t', stdout);
fprintf(stdout, "string=%s\n", (const char*)pv);
break;
}
}
return true;
}
int main(int argc, char* argv[])
{
if( argc < 2 )
return 1;
FILE* pf = fopen(argv[1], "rb");
if( pf )
{
bencode_lexer bed(Callback);
char chBuf[1024];
for(;;)
{
int cbRead = fread(chBuf, 1, sizeof(chBuf), pf);
if( cbRead < 1 )
break;
int cbParsed = bed.lex_chunk(chBuf, cbRead, 0);
if( cbParsed < cbRead )
break;
}
fclose(pf);
}
return 0;
}
再次说明,由于 bencode_lexer
不创建任何数据结构,因此您需要根据项目的需要构建提取的数据。
关注点
bencode_lexer
使用手写的堆栈类来管理其内部状态机。该类以一种更有限但更有效的方式模拟了 STL 的堆栈类。在实际项目中,您需要替换 ASSERT
调用中我放置的硬编码断点。