• C基础 数据序列化简单使用和讨论


     前言

      C中对序列化讨论少, 因为很多传输的内容都有自己解析的轮子. 对于序列化本质是统一编码, 统一解码的方式.

    本文探讨是一种简单的序列化方案. 保证不同使用端都能解析出正确结果.

    在文章一开始, 看一个最简单的序列化代码 如下

    #include <stdio.h>
    #include <stdlib.h>
    
    #define _INT_NAME        (64)
    #define _STR_TXT        "student.struct"
    
    struct student {
        int id;
        char sex;
        int age;
        char name[_INT_NAME + 1];
        double high;
        double weight;
    };
    
    // struct student 结构体序列化到文件的方法
    static void _student_serialize(struct student* stu, FILE* txt) {
        fprintf(txt, "%d %c %d %s %lf %lf ", stu->id, stu->sex, 
            stu->age, stu->name, stu->high, stu->weight);
    }
    
    // struct student 结构体反序列化
    static void _student_deserialize(struct student* stu, FILE* txt) {
        fscanf(txt, "%d %c %d %s %lf %lf ", &stu->id, &stu->sex,
            &stu->age, stu->name, &stu->high, &stu->weight);
    }
    
    // 简单打印数据
    static void _student_print(struct student* stu) {
        static int _idx;
    
        printf("%d: %d %c %d %s %lf %lf 
    ", _idx++, stu->id,
            stu->sex, stu->age, stu->name, stu->high, stu->weight);
    }
    
    /*
     * 一种最简单的通用序列化方法
     */
    int main(int argc, char* argv[]) {
        
        FILE* txt = fopen(_STR_TXT, "wb+");
        if (NULL == txt) {
            fprintf(stderr, "fopen " _STR_TXT " error!
    ");
            return -1;
        }
    
        // 这里写入数据
        struct student stu = { 0, 0, 23, "鸣人", 172.23, 64.05 };
        _student_print(&stu);
    
        // 这里序列化并写入数据到文件
        _student_serialize(&stu, txt);
    
        // 我们读取这个文件, 先设置文件指针到文件开头
        fseek(txt, 0, SEEK_SET);
    
        // 开始读取数据
        struct student ts;
        _student_deserialize(&ts, txt);
        _student_print(&ts);
    
        fclose(txt);
        system("pause");
        return 0;
    }

    本质在  自定义编码解码,并利用 scanf和printf 对映关系

    // struct student 结构体序列化到文件的方法
    static void _student_serialize(struct student* stu, FILE* txt) {
        fprintf(txt, "%d %c %d %s %lf %lf ", stu->id, stu->sex, 
            stu->age, stu->name, stu->high, stu->weight);
    }
    
    // struct student 结构体反序列化
    static void _student_deserialize(struct student* stu, FILE* txt) {
        fscanf(txt, "%d %c %d %s %lf %lf ", &stu->id, &stu->sex,
            &stu->age, stu->name, &stu->high, &stu->weight);
    }

    运行结果 如下:

    通过这种实现, 是跨平台的. 因为C实现标准和自己定义协议支持

     "%d %c %d %s %lf %lf "

    最后我们还会讨论这种情况.

    正文

    1. 一次失败扩展 fscanf -> fread ; fprintf -> fwrite

    测试如下, 在window上测试代码 main.c

    #include <stdio.h>
    #include <stdlib.h>
    
    #define _INT_NAME        (64)
    #define _STR_TXT        "student.struct"
    
    struct student {
        int id;
        char sex;
        int age;
        char name[_INT_NAME + 1];
        double high;
        double weight;
    };
    
    // struct student 结构体序列化到文件的方法
    static void _student_serialize(struct student* stu, FILE* txt) {
        fwrite(stu, sizeof(*stu), 1, txt);
    }
    
    // struct student 结构体反序列化
    static void _student_deserialize(struct student* stu, FILE* txt) {
        fread(stu, sizeof(*stu), 1, txt);
    }
    
    // 简单打印数据
    static void _student_print(struct student* stu) {
        static int _idx;
    
        printf("%d: %d %c %d %s %lf %lf 
    ", _idx++, stu->id,
            stu->sex, stu->age, stu->name, stu->high, stu->weight);
    }
    
    /*
    * 一种最简单的通用序列化方法
    */
    int main(int argc, char* argv[]) {
        FILE* txt = fopen(_STR_TXT, "wb+");
        if (NULL == txt) {
            fprintf(stderr, "fopen " _STR_TXT " error!
    ");
            return -1;
        }
    
        // 这里写入数据
        struct student stu = { 0, 0, 23, "鸣人", 172.23, 64.05 };
        _student_print(&stu);
    
        // 这里序列化并写入数据到文件
        _student_serialize(&stu, txt);
    
        // 我们读取这个文件, 先设置文件指针到文件开头
        fseek(txt, 0, SEEK_SET);
    
        // 开始读取数据
        struct student ts;
        _student_deserialize(&ts, txt);
        _student_print(&ts);
    
        fclose(txt);
        system("pause");
        return 0;
    }
    View Code

    核心是 

    // struct student 结构体序列化到文件的方法
    static void _student_serialize(struct student* stu, FILE* txt) {
        fwrite(stu, sizeof(*stu), 1, txt);
    }
    
    // struct student 结构体反序列化
    static void _student_deserialize(struct student* stu, FILE* txt) {
        fread(stu, sizeof(*stu), 1, txt);
    }

     在 linux 上处理的代码 是 serialize.c

    #include <stdio.h>
    #include <stdlib.h>
    
    #define _INT_NAME       (64)
    #define _STR_TXT        "student.struct"
    
    struct student {
        int id; 
        char sex;
        int age;
        char name[_INT_NAME + 1]; 
        double high;
        double weight;
    };
    
    // struct student 结构体反序列化
    static void _student_deserialize(struct student* stu, FILE* txt) {
        fread(stu, sizeof(*stu), 1, txt);
    }
    
    // 简单打印数据
    static void _student_print(struct student* stu) {
        static int _idx;
    
        printf("%d: %d %c %d %s %lf %lf 
    ", _idx++, stu->id,
            stu->sex, stu->age, stu->name, stu->high, stu->weight);
    }
    
    /*
     * 处理 window 上生成的内存文件, 看是否跨平台
     */
    int main(int argc, char* argv[]) {
        // 这里就简单读取 _STR_TXT
        FILE* txt = fopen(_STR_TXT, "rt");
        if (NULL == txt) {
            fprintf(stderr, "fopen " _STR_TXT " error!
    ");
            return -1; 
        }   
        
        // 开始读取数据
        struct student ts; 
        _student_deserialize(&ts, txt);
    
        // 打印读取数据测试
        _student_print(&ts);
    
        fclose(txt);
        return 0;
    }

    编译 命令的是

    gcc -g -Wall -o serialize.out serialize.c

    将 window生成的 student.struct 文件传输到linux测试机上, 运行结果 如下:

    期间进行了各种折腾

    a. 考虑转码

    b. 考虑 fopen 创建utf8 文件

    c. 考虑代码转码

    .......

    还是以失败告终, 估计fread, fwrite是不同平台的直接内存文件. 差异大, 不适合跨平台, 但是同样平台是比较快的考虑方式.

    扩展一下, 怎么得到文件字符长度

    linux得到文件长度扩展  http://blog.csdn.net/yutianzuijin/article/details/27205121

    2. 采用 protobuf - c google一种协议.

    pbc 传输协议, 很多公司都在采用, 我看了一下, 网上实现版本比较多, 现在可能有官方版本了. 推荐一个

    cloudwn pbc https://github.com/cloudwu/pbc 

    可能是最精简的一种实现, 源码写的很好, 但是觉得有点复杂了. 为了这么功能这么搞, 没意思.

    最关键的是 pbc 需要生成中间协议文件,  占用内存也不少.

    这也是一种解决方案.

    3. 采用json协议

    这个意思很明了, 大家都通过json来处理问题 这里推荐自己写的一个json引擎

    C json实战引擎 一 , 实现解析部分

    C json实战引擎 二 , 实现构造部分

    C json实战引擎 三 , 最后实现部分辅助函数

    源码在1000行左右,欢迎使用. 这也是一种解决方案. 最近和朋友在讨论问题, 越发觉得,  计算机软件开发无非

    围绕 空间和时间来回搞,  通用还是针对.

    真实生产环境中可能会更直白些, 快些, 没bug就行, 怎么爽怎么随便, 怎么快怎么来. 哈哈.

    但是对于C,  还是有一套自己的哲学, 用最简单完成一场意外.  C/C++ 老了, 但却是美的.

    这种解决方案讲到这里了.

    4. 开始就是结束. 还是从最简单的开始. 实现一个C 序列换流程

    后面继续讲解通过 fscanf 和 fprintf 构建 C的序列化. 先看一种实现体, serialize_student.h

    #ifndef _H_SERIALIZE_THREE_SERIALIZE_STUDENT
    #define _H_SERIALIZE_THREE_SERIALIZE_STUDENT
    
    #include <assert.h>
    #include <stdio.h>
    #include <limits.h>
    
    // 1.0 定义序列换结构体
    #define _INT_NAME        (64)
    struct student {
        int id;
        char sex;
        int age;
        char name[_INT_NAME + 1];
        double high;
        double weight;
    };
    
    // 2.0 定义保存文件交换文件名 当前文件名去掉 .h
    #define    _STR_SERIALIZE_TXT_student    "serialize_student"
    
    //3.0 定义转换读取协议, printf协议后面跟' ', printf后面跟的, scanf跟的
    #define    _STR_SERIALIZE_PBC_student    "%d %c %d %s %lf %lf "
    #define _F_SERIALIZE_PRINTF_student(p) 
        p->id, p->sex, p->age, p->name, p->high, p->weight
    #define _F_SERIALIZE_SCANF_student(p) 
        &p->id, &p->sex, &p->age, p->name, &p->high, &p->weight
    
    // 3.0 定义序列换数据写入方法
    static int serialize_student_printfs(void* data, int len) {
        assert(data && len > 0);
    
        FILE* txt = fopen(_STR_SERIALIZE_TXT_student, "wb");
        if (!txt) return -1;
    
        struct student* p = data;
        for (int i = 0; i < len; ++i) {
            fprintf(txt, _STR_SERIALIZE_PBC_student, _F_SERIALIZE_PRINTF_student(p));
            ++p;
        }
    
        fclose(txt);
        return 0;
    }
    
    // 4.0 定义序列化数据读取方法
    static int serialize_student_scanfs(void* data, int len) {
        assert(data);
    
        FILE* txt = fopen(_STR_SERIALIZE_TXT_student, "rb");
        if (!txt) return -1;
    
        int ns = 0;
        struct student* p = data;
        int nz = 0;
        const char* s = _STR_SERIALIZE_PBC_student;
        while (*s) {
            if (*s == '%')
                ++nz;
            ++s;
        }
    
        while (ns < len && fscanf(txt, _STR_SERIALIZE_PBC_student, _F_SERIALIZE_SCANF_student(p)) == nz) {
            ++ns;
            ++p;
        }
    
        fclose(txt);
        return ns;
    }
    
    
    #endif // !_H_SERIALIZE_THREE_SERIALIZE_STUDENT

     这里看看注释容易明白, 这里讲解一下 头文件导入宏规则.

    _H 开头 + _项目名 + _文件名(去掉后缀)  主要为了解决项目特别多的时候联编造成宏碰撞.

    测试代码 main.c

    #include <stdlib.h>
    #include "serialize_student.h"
    
    /*
     * 实现C的序列流程操作
     */
    int main(int argc, char* argv[]) {
    
        struct student stu[] = { 
            { 0, 0, 23, "鸣人", 172.23, 64.05 },
            { 0, 0, 34, "杀生丸", 178.23, 74.00 } 
        };
    
        // 先序列化到文件
        serialize_student_printfs(stu, sizeof(stu) / sizeof(*stu));
    
        // 开始读取序列化内容
        struct student sts[2];
        serialize_student_scanfs(sts, 2);
    
        for (int i = 0; i < 2; ++i) {
            printf("%d => %s
    ", i, sts[i].name);
        }
    
        puts("你喜欢吗, ... ");
    
        system("pause");
        return 0;
    }

     运行结果是

    最后可能来点 封装, 减少以后的工作量. 可能有点复杂, 直接看代码, 能懂得就呵呵一笑而过.

    再表述后面封装之前讲一个小知识, linux 上宏调试有个小技巧 通过 gcc -E 导出 *.i 文件, 查看宏命令.

    同样 window 上 vs 需要这么 设置

    加上 /EP /P 运行时候会生成 main.i

    找到问题后再将其去掉. 编译运行.

    我们先看一个 C宏模板 序列化注册头文件 serialize-base.h

    #ifndef _H_SERIALIZE_THREE_SERIALIZE_BASE
    #define _H_SERIALIZE_THREE_SERIALIZE_BASE
    
    #include <assert.h>
    #include <stdio.h>
    #include <limits.h>
    
    /*
     * 宏模板, 为想实现序列化的结构注册函数
     * name    : 结构名称,                例如 student
     * pbc    : 定义的协议,                例如 "%d %c %d %s %lf %lf "
     * ptf    : printf 打印数据参数集,    例如 _->id, _->sex, _->age, _->name, _->high, _->weight | SERIALIZE_PTF
     * scf    : scanf 得到数据的参数集,    例如 &_->id, &_->sex, &_->age, _->name, &_->high, &_->weight | SERIALIZE_SCF
     */
    #define SERIALIZE_BASE_REGISTER(name, pbc, ptf, scf)                                
    static int serialize_printfs_##name(void* data, int len) {                            
        assert(data && len > 0);                                                        
                                                                                        
        FILE* txt = fopen("serialize_"#name, "wb");                                        
        if (!txt) return -1;                                                            
                                                                                        
        struct name* _ = (struct name*)data;                                            
        for (int i = 0; i < len; ++i) {                                                    
            fprintf(txt, pbc, ptf);                                                    
            ++_;                                                                        
        }                                                                                
                                                                                        
        fclose(txt);                                                                    
        return 0;                                                                        
    }                                                                                    
                                                                                        
    static int serialize_scanfs_##name(void* data, int len) {                            
        assert(data);                                                                    
                                                                                        
        FILE* txt = fopen("serialize_"#name, "rb");                                        
        if (!txt) return -1;                                                            
                                                                                        
        int ns = 0, nz = 0;                                                                
        struct name* _ = (struct name*)data;                                            
        const char* s = pbc;                                                            
        while (*s) {                                                                    
            if (*s == '%')                                                                
                ++nz;                                                                    
            ++s;                                                                        
        }                                                                                
                                                                                        
        while (ns < len && fscanf(txt, pbc, scf) == nz) {                            
            ++ns;                                                                        
            ++_;                                                                        
        }                                                                                
                                                                                        
        fclose(txt);                                                                    
        return ns;                                                                        
    }                                                                                    
    
    
    #endif // !_H_SERIALIZE_THREE_SERIALIZE_BASE

    后面写一个结构 来实现序列化 serialize_person.h

    #ifndef _H_SERIALIZE_THREE_SERIALIZE_PERSON
    #define _H_SERIALIZE_THREE_SERIALIZE_PERSON
    
    // 必须导入(继承) 序列化基础实现模板
    #include "serialize-base.h"
    
    // 1.0 定义序列换结构体
    struct person {
        int id;
        char sex;
        int age;
        char name[65];
        double high;
        double weight;
    };
    
    // 2.0 注册得到 ptf 结构
    #undef    SERIALIZE_PTF
    #define SERIALIZE_PBC(id, sex, age, name, high, weight) 
        _->id, _->sex, _->age, _->name, _->high, _->weight
    
    // 3.0 注册得到 sct 结构
    #undef    SERIALIZE_SCF
    #define SERIALIZE_SCF(id, sex, age, name, high, weight) 
        &_->id, &_->sex, &_->age, _->name, &_->high, &_->weight
    
    // 4.0 最后开始注册实现体
    SERIALIZE_BASE_REGISTER(
        person,
        "%d %c %d %s %lf %lf ",
        SERIALIZE_PBC(id, sex, age, name, high, weight), 
        SERIALIZE_SCF(id, sex, age, name, high, weight)
    )
    
    #endif // !_H_SERIALIZE_THREE_SERIALIZE_PERSON

    是不是很酷炫, 好测试一下 main.c

    #include <stdlib.h>
    #include "serialize_student.h"
    #include "serialize_person.h"
    
    /*
     * 实现C的序列流程操作
     */
    int main(int argc, char* argv[]) {
    
        struct student stu[] = { 
            { 0, 0, 23, "鸣人", 172.23, 64.05 },
            { 1, 0, 34, "杀生丸", 178.23, 74.00 } 
        };
    
        // 先序列化到文件
        serialize_student_printfs(stu, sizeof(stu) / sizeof(*stu));
    
        // 开始读取序列化内容
        struct student sts[2];
        serialize_student_scanfs(sts, 2);
    
        for (int i = 0; i < 2; ++i) {
            printf("%d => %s
    ", i, sts[i].name);
        }
    
        puts("你喜欢吗, ... ");
    
    
        struct person ps[] = {
            { 2, 1, 23, "日向雏田", 162.23, 51.05 },
            { 3, 1, 14, "", 158.23, 45.00 }
        };
    
        // 序列化数据
        serialize_printfs_person(ps, sizeof(ps) / sizeof(*ps));
        // 得到序列化数据
    
        struct person tps[2];
        serialize_scanfs_person(tps, 2);
    
        for (int i = 0; i < 2; ++i) {
            printf("%d => %s
    ", i, sts[i].name);
        }
    
        system("pause");
        return 0;
    }

    测试结果如下, 一切正常

     到这里基本都结束了. 主要核心就是上面注册的函数模板.

    后记

    这次后记我们在linux上测试一下 将刚生成的 serialize_person 上传到 linux平台

    测试文件 main.c

    #include <stdlib.h>
    #include "serialize_person.h"
    
    /*
     * 实现C的序列流程操作
     */
    int main(int argc, char* argv[]) {
        
        puts("Play time game, writing code");
    
        struct person tps[2];
        serialize_scanfs_person(tps, 2); 
    
        for (int i = 0; i < 2; ++i) {
            printf("%d => %s
    ", i, tps[i].name);
        }   
    
        return 0;
    }

    最终测试结果

    源码成功, 到这里基本上可以离开了.

    关于C数据序列化的简单操作就到这里了.    错误是难免的, 拜~~~

  • 相关阅读:
    Win10下访问linux的ext4分区文件并拷贝
    Zsh 无法找到自己的anaconda python
    Motrix 代替迅雷下载 aria2的配置
    Bash与python混合编程
    如何在 非系统盘安装 wsl
    Python_01
    CC2541蓝牙学习——通用I/O口中断
    自定义弹窗
    使用windbg搜索命令辅助逆向杀软穿透驱动注册表操作
    IAT Hook
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5492794.html
Copyright © 2020-2023  润新知