本文并非标题的具体实现。而是提供一种编程方式,习惯,一种探讨。
本文有一点点门槛,有 socket,开源协议栈学习/开发经验者阅读更佳。
直接使用 memcpy 之类的是不会得到你期望的。
所以最好的做法就是定义结构体的时候就实现对字符串的转换。
就像 Python 的
__str__
一样。
(不好意思,博主雷打不动转python!信仰上帝Python)
如果不懂 python 也没有关系,我在下面会说明 C 语言的方法。
假设你有一个 packet:
struct protocol_packet { /* 假设 open source 已经帮你把字节码翻译成 ACSII / HEX 完成*/
unsigned char Mac[16];
unsigned char IPv4Addr[64];
};
然后你你在一个 open source 代码中,有个函数,传递了接受过来的packet:
int
I_am_open_source_handler(struct protocol_packet *recv_packet){
// ...do things...
return 0;
}
这个时候你想把 packet 打印出来看看里面有什么。
我们可以这么做:
免责声明,因为个人时间有限,没有对以下纯手打抽象出来的代码实验,
但是思路不会错,所以具体细节还需要看官自己微调一下。
struct protocol_packet { /* 假设 open source 已经帮你把字节码翻译成 ACSII / HEX 完成*/
unsigned char Mac[16];
unsigned char IPv4Addr[64];
int (*packet2str)(char*, struct protocol_packet*);
int (*spacket2str)(char*, struct protocol_packet*, size_t);
};
int protocol_packet2str(char dstStr[], struct protocol_packet *recv_packet){
/** why char dstStr[]?:
* 这个和 char *dstStr, 是完全一样的,这些写只是为了方面了解含义。
* 实际上它不是数组,关键词:“指针退化”
* 我会假设你知道 *recv_packet 这么传递有助于提升运行效率;
* 如果你对 const 熟悉,我建议“像强迫症”一样给它加上 const,
* 因为这样能够说明本函数不会改变 recv_packet 的内容。
*
* 你无法直接拷贝整个 struct 成为你想要的 string,
* 因为有些成员可能是 HEX 之类的,输出时会有问题,
* 所以只能针对它内部的成员一个一个拷贝;
* 因为是用来输出的,所以这个时候你可以直接添加一些字段的含义说明在 dstStr 中
*/
unsigned char mapping_struct_Mac[16];
unsigned char mapping_struct_IPv4Addr[64];
/* 假设 open source 已经将 MAC 解析成了 FF0073E4FFFF 的形式*/
memcpy(mapping_struct_Mac, recv_packet->Mac, 16);
memcpy(mapping_struct_IPv4Addr, recv_packet->IPv4Addr, 64);
/** 现在你可以使用 snprintf() 定制你的字符串输出了
* 注意, MAC 不能使用 '%s' 的方式, 你得 for 循环 使用 %x(%X), 再注意,MAC 不是 16 byte 长度!
* 为了避免误导,因为我主要的时间都在写 Python,所以C语言没有测试可能会有错误,
* 因此下面不给实例,要你自己实现 snprintf() 的具体内容,这里是思路:
* 比如针对 MAC: 01234 */
snprint(dstStr, 5, "MAC: "); /* 有没有人能教我一些 C 语言里面计算偏移的技巧??? */
snprint(&dstStr[5], 18, "%02X:%02X:%02X:%02X:%02X:%02X",
mapping_struct_MAC[0], mapping_struct_MAC[1], mapping_struct_MAC[2],
mapping_struct_MAC[3], mapping_struct_MAC[4], mapping_struct_MAC[5]);
// ...do the final job by you....
return 0;
}
int sprotocol_packet2str(char dstStr[], struct protocol_packet *recv_packet size_t length_of_dstStr){
/** 你可以实现一个 safe 版本,比起上面,只是多了一个 dstStr 的长度检查; */
return 0;
}
这里有一处必须要注意的地方:
因为我们上面把 struct 里面的内容改了,增加了一个转化为 string 的函数指针(还有一个 option 的 safe 方法),
但是如果不给这个指针赋值,它是 NULL 的,不能乱用。
所以,使用的时候,找到 code 里面初始化 recv_packet 的地方,我假设原本 code 是这样的:
int
main(...你懂的...){
struct protocol_packet recv_packet;
// make me as daemon, come on!
while(1) { /* loop until universe collapses */
// 收到 frame -网络字节码-> theFrame
拆包func(&recv_packet, &theFrame, /*..other if needed..*/);
I_am_open_source_handler(&recv_packet);
// ..other if needed...
}
return 0;
}
那么这个位置我们只需要轻轻一改:
int
main(...你懂的...){
struct protocol_packet recv_packet;
// make me as daemon, come on!
while(1) { /* loop until universe collapses */
// 收到 frame -网络字节码-> theFrame
拆包func(&recv_packet, &theFrame, /*..other if needed..*/);
recv_packet.packet2str = protocol_packet2str; // LOOK AT ME !!!!!!!!
I_am_open_source_handler(&recv_packet);
// ..other if needed...
}
return 0;
}
终于到了最后:
现在你就可以在任何嵌套在 I_am_open_source_handler()
里面很深层次的函数里面这么使用了:
int
I_am_open_source_handler(struct protocol_packet *recv_packet){
// ...do things...
switch([...]){
case [...]:
[...]
case [...]:
i_am_a_func_in_the_HANDLER(recv_packet);
[...]
break;
}
return 0;
}
int
i_am_a_func_in_the_HANDLER(struct protocol_packet *recv_packet_p){
// 我想要在这里知道 recv_packet 内部具体有哪些值,
// OK, that's do it:
char my_add_debug_str[2048] = {'\0'}; /* 长一点总不会坏事吧 */
recv_packet_p->packet2str(my_add_debug_str, recv_packet_p); /* 注意,这里 recv_packet_p 是指针类型哦 */
printf("%s", my_add_debug_str); /* 看,这样就可以愉快地输出了 */
/* 当我知道了 protocol 里面有哪些内容之后,
* 我可能会在下面的 source code 前面的现在这里,
* 添加一些自己的代码,做一些检查,定制化等等。
*/
// ..do magic things...
// BUT, I don't care this part code!
}
当然,你可以定义一个全局函数,不修改 struct protocol_packet
里面的内容,全局函数就跟 protocol_packet2str()
里面一样写就可以了。然后需要的地方,直接调用这个全局函数。
但是这样写还要知道这个函数不是吗?
在产品代码里面,这个函数一般都不会出现在像上面的 i_am_a_func_in_the_HANDLER()
这样的具体行为的代码里面,但是,如果定义了这样一个自动转化的指针,那么每个结构体初始化的位置: struct protocol_packet recv_packet;
你都可以给它绑定函数:
recv_packet.packet2str = protocol_packet2str; // LOOK AT ME !!!!!!!!
这个绑定语句可以紧跟在初始化位置后面,不用在上面的 while 循环之类的内部绑定即可。上面是为了方面理解。
这句代码可以留着,因为给结构体的一个成员绑定一个地址并不会消耗什么资源,并且它不会有任何输出。
考虑如下的情况:
当另外一个人,完全没有接触过这个代码,或者说,
你自己几个月之后又要跟踪 packet 的状态,
然后你很不巧地发现自己当初写的结构体成员(变量)名称含义不明 – 看到 struct 内部脑海里就出现黑人问号…
那么当你看到这样一个结构体:
struct protocol_packet { /* 假设 open source 已经帮你把字节码翻译成 ACSII / HEX 完成*/
unsigned char i_am_not_named_mac_addr[16];
unsigned char i_have_a_strange_name[64];
int (*packet2str)(char*, struct protocol_packet*);
int (*spacket2str)(char*, struct protocol_packet*, size_t);
};
你就知道可以这么调用:
recv_packet_p->packet2str(debug_str, recv_packet_p);
来的得到整个结构体内部变量的值是怎么样的了,然后很轻松地把它打印显示出来。
非指针是这么调用:
recv_packet.packet2str(debug_str, &recv_packet)
;
于是你就可以很自然地写出这三行代码:
char debug_str[2048] = {'\0'};
recv_packet_p->packet2str(debug_str, recv_packet_p);
printf("%s", debug_str);
从此观察结构体内部状态就变成了一个很轻松地事情了。
一般我们可能只是要个输出,那么完全可以定义一个
void (*packet_format_print)(struct protocol_packet*);
在结构体内部,
只不过这个方法可能不足够安全,因为一些系统知识有关的原因,在这个函数内部的实现输出根据不同的平台需要做一个适配的调整。比如输出到文件/syslog?输出到默认终端?输出到 console? 是指这个意思。
在初始化结构体的时候绑定函数,就是基于对象的做法,类似 C++ 有个构造函数。
struct 可以当作一个 default public 的类来使用,所以只要自己定义好了构造函数,在 C 里面也是可以很方便的直接初始化它,而不用上述显式地去绑定函数。
但是这部分说明,对于这个字符串化结构体这个主题,相关性不大。所以这里只是一个提及。
实际上这里没有啥 reference, 但是如果你觉的上面的内容有用,有些内容不理解?,想要多了解一下?
那么这里有一些是上面内容使用到的知识点:
%s
,查看,HEX 码万万不能误用 %s
来输出;printf
不会输出到终端;快点学 python 吧!
欢迎朋友指出上面文章内容的错误之类。
或者有更好的建议。
本人水平有限,本文仅供参考。