最近新到一个公司上班,所做的项目与网络通信有关,网络通信,必然涉及到收发网络包,为了检查我封装的包的数据是否正确,为了便于与通信方进行比对,我需要获得最终收发包的二进制数据。
这个通常有以下实现方式:
1、网络抓包,典型的做法是,根据通信网络设备的组网,在交换机建立port-mirroring,把运行抓包工具(Wireshark)的电脑练到这个镜像 口,把通过某些端口的网络包全抓下来进行分析;
2、平台提供了一个抓包的命令(可直接执行,不需要配置交换机什么的),但是它会把包的有效数据截断,只保留前面几个字节,因此只能看到有没有包,不能看到包的数据对不对;
3、我在代码的收发包处,以日志的形式打印出包的二进制数据。
我选的是方案3,因为,对方案1,一开始我还不知道怎么在交换机配port-mirroring(现在知道了,可以专门写篇文章介绍一下);对方案2,我不知道这是bug还是有意为之。我搜索了代码,没看到有类似的,看来只能自己实现了。
其实这个的本质就是以16进制的形式打印一段内存,就跟我们在调试时的Memory窗口,或者二进制阅读器看到的一样:
很自然的解决方案就是C++的ostringstream,唯一要处理的就是这个模块原来全是C代码,就需要在C中调用C++的东西,需要添加一个.cpp源文件,需要简单修改makefile(C/C++混合编程的makefile)。
这里我简单介绍C调用C++的实现,和如何打印二级制内存。
// C++文件,Util.cpp
#include <sstream>
#include <string>
#include <iostream>
#include <iomanip>
using namespace std;
extern "C" void XXX_ReadMemory(unsigned char * pucBuf, unsigned long ulLen);
void XXX_ReadMemory(unsigned char * pucBuf, unsigned long ulLen)
{
if(pucBuf==NULL || ulLen<=0)
return;
std::ostringstream oss;
for(unsigned long i=0; i<ulLen; ++i)
{
oss<< std::hex << std::setw(2) <<std::setfill('0') <<(unsigned char)(*(pucBuf+i)) << " ";
}
std::cout<<oss.str()<<endl;
}
// C文件,XXX_dosth.c
extern void XXX_ReadMemory(unsigned char * pucBuf, unsigned long ulLen);
void Print_Memory()
{
XXX_ReadMemory((unsigned char* )pPackage, packageLen);
}
这里Util.cpp文件由g++编译,extern “C”告诉g++编译器按照C的规则生成符号名称,按照不同调用规则,符号名称会有不同,这里是_XXX_ReadMemory(Windows下通过VS自带的工具dumpbin可以查看,Linux通过nm可以查看).
XXX_dosth.c文件由gcc编译,自然是按照C的规则生成和解析符号名称,所以只需声明extern,链接的时候,就通过_XXX_ReadMemory这个符号名称完成链接。
另外一种实现方式,还是用C语言,思路就是把每一个Byte的数据以十六进制字符串的形式写入到输出内存中(对应2个或多个字符),这样实现也挺简单:
void XXX_ReadMemory(unsigned char * pucInBuf, unsigned long ulInLen, char * pOutBuf, unsigned long ulOutLen)
{
//better set the pOutBuf to 0
if(pucInBuf==NULL || ulInLen<=0 || pOutBuf==NULL || ulOutLen<=0)
return;
char *pout = pOutBuf;
int nlen = ulOutLen - 1;
for(unsigned long i=0; i<ulInLen && nlen>3 ; ++i)
{
_snprintf_s(pout, nlen, 3, "%02x ", *(pucInBuf+i));
pout += 3;
nlen -= 3;
}
*(pOutBuf+ulOutLen-1) = '\0';
}