尽管 php 是用 C 语言开发的, 不过令我不解的是 php 没有提供对结构体 struct 的直接支持
不过 php 提供了 pack 和 unpack 函数, 用来进行二进制数据 (binary data) 和 php 内部数据的互转:
代码如下:
- string pack ( string $format [, mixed $args [, mixed $...]] )
- //Pack given arguments into binary string according to format.
- array unpack ( string $format, string $data )
- //Unpacks from a binary string into an array according to the given format.
其中,$format 跟 perl 里的 pack 格式类似, 有如下一些(中文是我加的, 有不准确的欢迎提出):
a NUL-padded string, 即作为空字符的表示形式
A SPACE-padded string, 空格作为空字符的表示形式
h Hex string, low nibble first, 升序位顺序
H Hex string, high nibble first, 降序位顺序
c signed char, 有符号单字节
C unsigned char, 无符号单字节
- s signed short (always 16 bit, machine byte order)
- S unsigned short (always 16 bit, machine byte order)
- n unsigned short (always 16 bit, big endian byte order)
- v unsigned short (always 16 bit, little endian byte order)
- i signed integer (machine dependent size and byte order)
- I unsigned integer (machine dependent size and byte order)
- l signed long (always 32 bit, machine byte order)
- L unsigned long (always 32 bit, machine byte order)
- N unsigned long (always 32 bit, big endian byte order)
- V unsigned long (always 32 bit, little endian byte order)
- f float (machine dependent size and representation)
- d double (machine dependent size and representation)
x NUL byte, 实际使用的时候作为跳过多少字节用, 很有用
X Back up one byte, 后退 1 字节
@ NUL-fill to absolute position, 实际使用的时候作为从开头跳到某字节用, 很有用
实际使用发现: C 里的 (即字符串终止符) 在 php 里并不是终止符, 而是作为了字符串的一部分因此, 必须对进行特殊处理, 才能进行 struct 和 php 内部数据的完美互转比如 char name[10]; 如果实际数据是 62 69 61 6E 00 62 69 616E00, 在 C 语言里第 5 个位置有终止符, name 应该是 bian; 而用了 unpack 转换以后在 php 里的 name 却是 bian\0bian\0
一开始我用了 strpos 函数找到的位置, 然后进行 substr 截取.
不过很 Faint 的事情发生了, 不知道是 strpos 的 bug 还是 substr 的 bug(其实测试一下就知道, 懒得试), 有些字符串没问题, 有些字符串却只能得到空值 (即 $name == ) 很是郁闷, 后来找了个 strtok 函数, 这下没有问题了.
难为大家看了那么多, 下面写个完整的 php 读取二进制数据流 (C 语言结构体 struct 数据) 文件的示例代码:
首先是 C 的 struct 定义示例, 为了演示, 我就写个简单点的, 实际对照上面那个 $format 格式表应该没有问题:
代码如下:
- struct BIANBIAN {
- char name[10];
- char pass[33];
- int age;
- unsigned char flag;
- };
比如有个 file.dat 文件, 内容就是上面的 N 个 BIANBIAN 结构体构成的读取的 php 代码:
代码如下:
- // 下面根据 struct 确定 $format, 注意 int 类型跟机器环境有关, 我的 32 位 Linux 是 4 个长度
- $format = 'a10name/a33pass/iage/Cflag';
- // 确定一个 struct 占用多少长度字节, 如果只是读取单个结构体这是不需要的
- $length = 10 + 33 + 4 + 1;
- // 也可以用 fopen + fread + fclose, 不过 file_get_contents 因为可以 mmap, 效率更高
- $data = file_get_contents('file.dat', 'r');
- for ($i = 0, $c = strlen($data); $i < $c; $i += $length) {
- $bianbian = unpack("$format", $data);
- //reference 传递是 php 5 才支持的, 如果用 php4, 得用其他办法
- foreach ($bianbian as &$value) {
- if (is_string($value)) {
- $value = strtok($value, "\0");
- }
- }
- print_r($bianbian);
- }
- ?>
pack 应该跟 unpack 相反
顺便附上生成结构体文件的 C 语言代码:
代码如下:
- #include
- #include
- struct example
- {
- char name[10];
- char pass[33];
- int age;
- unsigned char flag;
- };
- int main()
- {
- example test;
- example read;
- FILE *fp;
- test.age = 111;
- test.flag = 10;
- strcpy(test.name, "Hello World!");
- strcpy(test.pass, "zbl110119");
- fp = fopen("file.dat", "w+");
- if (!fp)
- {
- printf("open file error!");
- return -1;
- }
- rewind(fp);
- fwrite(&test, sizeof(example), 1, fp);
- rewind(fp);
- fread(&read, sizeof(example), 1, fp);
- printf("%d, %s\n", read.age, read.name);
- fclose(fp);
- return 0;
- }
来源: https://www.php1.cn/detail/php-656fe4bf72.html