zoukankan      html  css  js  c++  java
  • C中级

    引言 - 业务有点麻烦

        C 功能很强大, 同样书写起来会谨慎(拖泥带水). 不妨通过一个小问题来描述裹脚的 C
    
    需求:
        用 C 创建一个文件!
    
    难点在于
        1. 文件路径切割成 目录 + 文件名
        2. 目录 多级创建
        3. 跨平台
    这个问题也挺适合做线下面试作业.
    
    本文围绕下面几个重点讲述
        1. 获取文件更新时间
        2. 目录创建和文件删除
        3. 配置自动更新
    应对平台是 Winds cl 和 Linux gcc :)
    
        在开始之前, 先介绍一些文件辅助基础函数, 构造了两个最初的原始的 mkdir 和 mtime 函数.
    mkdir 在 shell 中用于构建目录,常很用. 对于 mtime 是 Linux 对于文件最后修改时间. 
    最初来自于下面文件详细信息结构中
    
    struct stat {
    	unsigned long   st_dev;		/* Device.  */
    	unsigned long   st_ino;		/* File serial number.  */
    	unsigned int    st_mode;	/* File mode.  */
    	unsigned int    st_nlink;	/* Link count.  */
    	unsigned int    st_uid;		/* User ID of the file's owner.  */
    	unsigned int    st_gid;		/* Group ID of the file's group. */
    	unsigned long   st_rdev;	/* Device number, if device.  */
    	unsigned long   __pad1;
    	long            st_size;	/* Size of file, in bytes.  */
    	int             st_blksize;	/* Optimal block size for I/O.  */
    	int             __pad2;
    	long            st_blocks;	/* Number 512-byte blocks allocated. */
    	long            st_atime;	/* Time of last access.  */
    	unsigned long   st_atime_nsec;
    	long		    st_mtime;	/* Time of last modification.  */
    	unsigned long	st_mtime_nsec;
    	long            st_ctime;	/* Time of last status change.  */
    	unsigned long   st_ctime_nsec;
    	unsigned int    __unused4;
    	unsigned int    __unused5;
    };
    
    在结构体中 st_atime, st_mtime, st_ctime 字段可以知道, Linux 文件有三个时间属性: 
    
    1. mtime: 文件内容最后修改时间
    2. ctime: 文件状态改变时间, 如权限, 属性被更改
    3. atime: 文件内容被访问时间, 如 cat, less 等
    
    在默认情况下, ls 显示出来的是该文件的 mtime, 即文件内容最后修改时间. 
    如果你需要查看另外两个时间, 可以使用 ls -l --time ctime 命令.
    
    消化上面小知识, 加上下面先入为主的设计思路, 就构建了代码 head 部分
    
    #ifndef _H_FILE
    #define _H_FILE
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #ifdef __GNUC__
    
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    
    //
    // mkdir - 通用的单层目录创建函数宏, 等价于 mkdir path
    // path     : 目录路径加名称
    // return   : 0表示成功, -1表示失败, 失败原因都在 errno
    // 
    #undef  mkdir
    #define mkdir(path)                                 
    mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)
    
    //
    // mtime - 得到文件最后修改时间
    // path     : 文件名称
    // return   : 返回时间戳, -1 表示错误
    //
    inline time_t mtime(const char * path) {
        struct stat ss;
        // 数据最后的修改时间
        return stat(path, &ss) ? -1 : ss.st_mtime;
    }
    
    #endif
    
    #ifdef _MSC_VER
    
    #include <io.h>
    #include <direct.h>
    #include <windows.h>
    
    // int access(const char * path, int mode /* 四个检测宏 */);
    #ifndef F_OK
    #   define  F_OK    (0)
    #endif       
    #ifndef X_OK 
    #   define  X_OK    (1)
    #endif       
    #ifndef W_OK 
    #   define  W_OK    (2)
    #endif       
    #ifndef R_OK 
    #   define  R_OK    (4)
    #endif
    
    inline time_t mtime(const char * path) {
        WIN32_FILE_ATTRIBUTE_DATA wfad;
        if (!GetFileAttributesEx(path, GetFileExInfoStandard, &wfad))
            return -1;
        // 基于 winds x64 sizeof(long) = 4
        return *(time_t *)&wfad.ftLastWriteTime;
    }
    
    #endif
    
    #endif
    
    原始的 mkdir 仿照 Winds 上面的 mkdir 接口设计. 
    
        _Check_return_ _CRT_NONSTDC_DEPRECATE(_mkdir)
        _ACRTIMP int __cdecl mkdir(
            _In_z_ char const* _Path
            );
    
    
    最终行为和 Linux 的 mkdir 创建目录命令一致(0665,即-rw-rw-r-x).
    mtime 得到文件最后修改时间. 主要用于获取时间变化促使配置自动更新.
    F_OK, X_OK, W_OK, R_OK 在 Winds 上实现 access 接口缺少的宏定义.
    更加具体可以参照
    

    file.h

    前言 - 文件辅助操作

    前言部分可能最精华, 开始步入正轨 ~
    
    //
    // mkdirs - 创建多级目录
    // path     : 目录路径
    // return   : < 0 is error, 0 is success
    //
    int 
    mkdirs(const char * path) {
        char c, * p, * s;
    
        // 参数错误直接返回
        if (!path || !*path) return -2;
        // 文件存在 or 文件一次创建成功 直接返回
        if (!access(path, F_OK) || !mkdir(path))
            return 0;
    
        // 跳过第一个 ['/'|'\'] 检查是否是多级目录
        p = (char *)path;
        while ((c = *++p) != '')
            if (c == '/' || c == '\')
                break;
        if (c == '') return -1;
    
        // 开始循环构建多级目录
        s = p = strdup(path);
        while ((c = *++p) != '') {
            if (c == '/' || c == '\') {
                *p = '';
    
                if (access(s, F_OK)) {
                    // 文件不存在, 开始创建, 创建失败直接返回错误
                    if (mkdir(s)) {
                        free(s);
                        return -1;
                    }
                }
    
                *p = c;
            }
        }
    
        // 最后善尾
        c = p[-1]; free(s);
        if (c == '/' || c == '\')
            return 0;
    
        // 剩下最后文件路径, 开始构建
        return mkdir(path) ? -1 : 0;
    }
    
    上面 mkdirs 一个具有实战意义的跨平台多级目录创建函数接口设计. 等同于 mkdir -p 操作.
    核心思路在于
        1. 直接 mkdir 成功就返回
        2. 分级 切割 路径, 循环 mkdir
    
    有了多级目录构建操作, 那顺路写个多级目录的删除操作.
    
    //
    // removes - 删除非空目录 or 文件
    // path     : 文件全路径
    // return   : < 0 is error, >=0 is success
    //
    inline int removes(const char * path) {
        char s[BUFSIZ];
    
    #ifndef STR_RMRF
    #   ifdef _MSC_VER
    #       define STR_RMRF    "rmdir /s /q "%s""
    #   else
    #       define STR_RMRF    "rm -rf '%s'"
    #   endif
    #endif
    
        // path 超过缓冲区长度, 返回异常
        if (snprintf(s, sizeof s, STR_RMRF, path) == sizeof s) 
            return -1;
        return access(path, F_OK) ? 0 : -system(s);
    }
    
    如果你不希望存在输出内容在控制台上面可以使用这种类型操作
    
    $ rm -rf '%s' >/dev/null 2>&1
    
    or
    
    > rmdir /s /q "%s" 1>>dev.nil 2>&1 ; del dev.nul
    
    这里保留了输出内容, 方便日志采集发现问题.
    

    正文 - 配置自动刷新

    生活不止眼前的苟且, 很庆幸来到这里. 但感觉作用不大.
    随后讲述的内容是如何构造一个动态刷新的配置系统. 
    
    整体的设计有如下考虑
        1. 生命周期跟随系统. 所以只有 set 和 update
        2. 线程安全
        3. set null 等同于 update delete
    可以通过代码来看这样的好处和细节.
    
    首先看整体的数据结构设计
    
    struct file {
        time_t last;            // 文件最后修改时间点
        char * path;            // 文件全路径
        unsigned hash;          // 文件路径 hash 值
    
        file_f func;            // 执行行为
        void * arg;             // 行为参数
    
        struct file * next;     // 文件下一个结点
    };
    
    static struct files {
        atom_t lock;            // 当前对象原子锁
        struct file * head;     // 当前文件对象集
    } _s;
    
    其中随后会用到原子锁操作, 更加详细的看下面库的设计.
    

    atom.h

    对于全局的 struct files _s 对象辅助函数 add 和 get 设计内涵见下面
    
    // files add 
    static void _add(const char * p, unsigned h, file_f func, void * arg) {
        struct file * fu;
        if (mtime(p) == -1) {
            RETNIL("mtime error p = %s", p);
        }
    
        fu = malloc(sizeof(struct file));
        fu->last = -1;
        fu->path = strdup(p);
        fu->hash = h;
        fu->func = func;
        fu->arg = arg;
    
        // 直接插入到头结点部分
        atom_lock(_s.lock);
        fu->next = _s.head;
        _s.head = fu;
        atom_unlock(_s.lock);
    }
    
    // files get 
    static struct file * _get(const char * p, unsigned * r) {
        struct file * fu = _s.head;
        unsigned h = *r = str_hash(p);
    
        while (fu) {
            if (fu->hash == h && strcmp(fu->path, p) == 0)
                break;
            fu = fu->next;
        }
    
        return fu;
    }
    
    线程安全的辅助直白代码. 其中 str_hash 引用的如下函数
    
    //
    // str_hash - Brian Kernighan与 Dennis Ritchie 简便快捷的 hash算法
    // str      : 字符串内容
    // return   : 返回计算后的hash值
    //
    unsigned 
    str_hash(const char * str) {
        register unsigned h = 0u;
        if (str) {
            register unsigned c;
            while ((c = *str++))
                h = h * 131u + c;
        }
        return h;
    }
    
    随后开始进入核心业务, 先是要看更新操作
    
    //
    // :0 一个和程序同生存的配置文件动态刷新机制
    // file_f - 文件更新行为
    //
    typedef void (* file_f)(FILE * c, void * arg);
    
    //
    // file_set - 文件注册触发行为
    // path     : 文件路径
    // func     : file update -> func(path -> FILE, arg), func is NULL 标记清除
    // arg      : 注入的额外参数
    // return   : void
    //
    void 
    file_set(const char * path, file_f func, void * arg) {
        unsigned h;
        assert(path && *path);
        struct file * fu = _get(path, &h);
        if (NULL == fu)
            _add(path, h, func, arg);
        else {
            atom_lock(_s.lock);
            fu->last = -1;
            fu->func = func;
            fu->arg = arg;
            atom_unlock(_s.lock);
        }
    }
    
    file_set 只负责在全局的 _s 对象中安全的插入数据, 其它业务什么都不管.
    
    后面看详细的 file_update 操作
    
    //
    // file_update - 更新注册配置解析事件
    // return   : void
    //
    void 
    file_update(void) {
        struct file * fu;
        atom_lock(_s.lock);
    
        fu = _s.head;
        while (fu) {
            struct file * next = fu->next;
    
            if (NULL == fu->func) {
                // 删除的是头结点
                if (_s.head == fu)
                    _s.head = next;
    
                free(fu->path);
                free(fu);
            } else {
                time_t last = mtime(fu->path);
                if (fu->last != last && last != -1) {
                    FILE * c = fopen(fu->path, "rb+");
                    if (NULL == c) {
                        CERR("fopen rb+ error = %s.", fu->path);
                        continue;
                    }
                    fu->last = last;
                    fu->func(c, fu->arg);
                    fclose(c);
                }
            }
    
            fu = next;
        }
        atom_unlock(_s.lock);
    }
    
    file_update 前提也是线程安全的. 其次应对两个业务 delete 和 update.
    其实从写过的角度而言. file_f 和 file_set 就已经决定了 file_update 具体设计了.
    
    开始的时候 就已经决定了 能够达到的最好结尾 ~
    
    (可能是代码写多了, 总感觉看 code 就够了, 说太多容易欲盖弥彰 :)
    

    后记 - 也许是交作业

    错误是难免的欢迎指正 ~ 我不哭我已经没有 ~ 尊严能放弃 ~
    

    在人间

  • 相关阅读:
    python多进程(二)
    PLSQL配置
    sql语句之左连结
    点击lable标出现下拉搜索框及选择功能
    angularjs前端分页自定义指令pagination
    未经整理的工作中遇到的小问题
    晒一晒工作内容.....呵呵勿喷
    配置chrome支持本地(file协议)ajax请求
    html5+angularjs+bootstrap+springmvc+mybatis模糊查询Deme
    oracle的sql积累..&..decode函数使用
  • 原文地址:https://www.cnblogs.com/life2refuel/p/8983294.html
Copyright © 2011-2022 走看看