C 基础框架开发
2016-01-16 21:17
387 查看
[b]引言[/b]
有的人真的是天命所归
延安时期炸弹 投到他院子都 没炸. 有些事无法改变 是命!
我们也快'老'了, 常回家看看.
[b]前言[/b]
扯淡结束了,今天分享的可能有点多,都很简单,但是糅合在一起就是有点复杂. 我会具体讲解一些开发中坑.
主要围绕如何在Linux和Window 上搭建C基础开发框架, 并且写一个支持多用户分级的日志库. sclog.
需要材料
1.Linux 用的code linux_sc_console
2.window 用的 项目 代码 sc_console_start
下载上面源码.其实源码都一样,只是放在不同平台下运行测试,一切正常. 这里回答一个问题,为什么C程序员那么喜欢造轮子.
因为C自由,自由就以为着自己开心就好. 如果性能还可以那就更好了. 说白了开心就好.(当然,C中没有一同天下的框架,导致群雄割据,小明东奔西跑.)
欢迎交流提高.
[b]正文[/b]
1.先从Linux 环境说起来
那我们刚起
1.1 首先看下面结构
从上面 结构中我们可以看出 这个 sc_console 项目在 Linux中文件结构,简单介绍一下
这里 简单说明了一下,文件主要意义. 后面会直接贴代码, 有些东西不好说, 因为不自己琢磨看开源代码, 很难简单说明白. 后面
会对一些细节和不注意的坑说明一下. 这个框架 实战意义值得学习, 当然因具体业务可以再优化.
下面看看 Makefile 文件内容,来了解 编译的具体细节.
这里我再细细说来,毕竟简单我也喜欢说
-g -Wall 表示 让 gcc开启强警告和插入调试代码
-I./module/schead/include 表示gcc 编译的时候包含这个文件,文件路径采用的相对路径.
-c 生成编译后的机器码.
后面意思是 需要 sc_console.out 但是依赖 main.o 和 schead.o 和 sclog.o
而main.o 依赖 main.c 等等
后面
clean是第二条命令不会执行.
但是可以通过 make clean 来执行这条命令,
后面 删除 log 和 __*是删除生成的日志和持久数据文件. 大家可以试试效果很好.
到这里 Linux上编译已经通过了. 下面直接上代码 . 一个个的来
2.2 首先看原子操作类 scatom.h
这些原子操作,在我前面讲解 云风的字符串详细提过,这里简单说一下 为什么 会有 LONG*
这是这两种原子操作机制不一样. Linux上 __sync 是 在编译器层次实现的, 而 window的 Interlock 是在 函数库层实现的.
差距很大,这里强转LONG* 是一种伪装操作.
2.3 再看 schead.h
这里需要说明的一下是
这两个函数都是为了在window上模拟 Linux 行为. 首先 gettimeofday 在window 没有这个功能,获取当前时间.
对于 安全的localtime 对于 不同平台实现不一样吧,这里觉得window设计的好.上面对于实现了大小端代码也特别巧妙.
2.4 schead.c 具体实现, 这些还是有一点看头,以后可能只关注Linux,window太罗嗦了
上面函数基本都是线程安全的, 实现也都比较简单. 大家可以自行练习.
2.5 sclog.h 关于C日志库的接口设计 多用户安全跨平台的日志库
关于这个宏
主要为了 下面这种宏拼接字符串用的
第一个%s输出 运行时间量用的.
这个日志库的使用流程是先初始化,后就可以用了,初始化调用 int sl_pecific_init(const char* mod, const char* reqip); 添加模块名称和请求ip.
2.6 关于 sclog.c 的具体实现
我们对 __sl_end 函数解析一下 主要做有两部分工作比较特殊,第一部分
将 __logid 变量持久化.保存在一个文件中,算作一个唯一标识吧.
第二部分是为了解决 sl_start中使用了线程私有数据,在退出时候释放, 对于线程私有数据设置为NULL,表示处理释放的时候跳过释放的函数操作.
还有在sl_start 中有一段创建目录的代码
也是偷懒的写法,不同平台创建多层文件接口不一样,写起来麻烦,自己用shell 苟合了一个. 启动的时候用,还凑合着吧.
2.7 最后测试文件 main.c
上面第一部分是 window上测试 posix线程框架的跳过,后面是测试当前整个框架的一切正常.
3 查看运行结果
首先编译
查看缓冲文件 __lid__ 保存logid
后面具体生成日志文件如下
同样我们看看window上结果如下
结果都相似,后面将着重介绍如何在window上搭建开发环境
4.在window 上搭建 sc_console 开发项目,自己生成模板
首先看目录结构
外部正式文件结果如下
首先我们看 pthread 配置 前面已经说过了,现在有两方便要注意
第一方面关于 ptread.h 源码修改 删除 一个 关于 time结构体冲突.
后面添加一个库引用处理代码
后面在对应生成的执行文件中导入相应的pthread动态库例如如下
到这里基础的就能运行了, 现在是挨个配置 具体操作 这里有个文件 要求总结如下
help.txt
按照上面配置 具体 截图看下面
按照这个操作将
四种组合都配置一遍,基本都ok了这个框架就搭建好了.
到这里 扩展一下再 Release 发布模块下怎么调试, 请按照下面做
设置在Release模式下调试的方法:
1.工程项目上右键 -> 属性
2.c++ -> 常规 -〉调试信息格式 选 程序数据库(/Zi)或(/ZI), 注意:如果是库的话,只能(Zi)
3.c++ -> 优化 -〉优化 选 禁止(/Od)
4.连接器 -〉调试 -〉生成调试信息 选 是 (/DEBUG)
5.在优化里 关闭全程序优化
到这里基本就结束了,欢迎喜欢C的同学试试.
[b]后记[/b]
[b] 有错误是难免的,以后准备逐步放弃跨平台操作, 简单的window来,复杂的Linux来. 对于跨平台冗余代码比较多,而且强扭的瓜不甜.[/b]
而且别说跨平台了,跨编译器都很恶心. 而且像云风那种老鸟都不写跨平台代码,自己这种菜鸟更不敢写了. 欢迎大家试试. 上面只是这个sc_console.
中最基础的后面会假如 配置读取,json引擎,csv引擎代码,还有一些特定平台的代码功能等等. 有错误会立马改正.
有时觉得写代码还是很有意思的.
有的人真的是天命所归
延安时期炸弹 投到他院子都 没炸. 有些事无法改变 是命!
我们也快'老'了, 常回家看看.
[b]前言[/b]
扯淡结束了,今天分享的可能有点多,都很简单,但是糅合在一起就是有点复杂. 我会具体讲解一些开发中坑.
主要围绕如何在Linux和Window 上搭建C基础开发框架, 并且写一个支持多用户分级的日志库. sclog.
需要材料
1.Linux 用的code linux_sc_console
2.window 用的 项目 代码 sc_console_start
下载上面源码.其实源码都一样,只是放在不同平台下运行测试,一切正常. 这里回答一个问题,为什么C程序员那么喜欢造轮子.
因为C自由,自由就以为着自己开心就好. 如果性能还可以那就更好了. 说白了开心就好.(当然,C中没有一同天下的框架,导致群雄割据,小明东奔西跑.)
欢迎交流提高.
[b]正文[/b]
1.先从Linux 环境说起来
那我们刚起
1.1 首先看下面结构
从上面 结构中我们可以看出 这个 sc_console 项目在 Linux中文件结构,简单介绍一下
/* Makefile => 编译文件 main => 存放 主 main.c 的目录 main.c => 主业务,主要测试代码 module/schead/ => 都是结构目录 include => schead模块中保存头文件目录 // main 放主业务, module存放主模块,每个模块单独一个文件夹 scatom.h => 原子操作头文件 schead.h => C中一些跨平台帮助操作宏,头文件 schead.c => 对schead.h一些特定接口实现,例如大小端判断 sclog.h => 分级多用户日志库 头文件 sclog.c => 多线程日志 实现 */
这里 简单说明了一下,文件主要意义. 后面会直接贴代码, 有些东西不好说, 因为不自己琢磨看开源代码, 很难简单说明白. 后面
会对一些细节和不注意的坑说明一下. 这个框架 实战意义值得学习, 当然因具体业务可以再优化.
下面看看 Makefile 文件内容,来了解 编译的具体细节.
CC=gcc DEBUG=-g -Wall -D_DEBUG #指定pthread线程库 PTHREAD=-lpthread #指定一些目录 DIR=-I./module/schead/include #具体运行函数 RUN=$(CC) $(DEBUG) -o $@ $^ $(PTHREAD) $(DIR) RUNO=$(CC) $(DEBUG) -c -o $@ $^ $(DIR) # 主要生成的产品 sc_console.out:main.o schead.o sclog.o $(RUN) main.o:./main/main.c $(RUNO) schead.o:./module/schead/schead.c $(RUNO) sclog.o:./module/schead/sclog.c $(RUNO) #删除命令 clean: rm -rf *.i *.s *.o *.out __* log ; ls -hl
这里我再细细说来,毕竟简单我也喜欢说
-g -Wall 表示 让 gcc开启强警告和插入调试代码
-I./module/schead/include 表示gcc 编译的时候包含这个文件,文件路径采用的相对路径.
-c 生成编译后的机器码.
后面意思是 需要 sc_console.out 但是依赖 main.o 和 schead.o 和 sclog.o
而main.o 依赖 main.c 等等
后面
clean是第二条命令不会执行.
但是可以通过 make clean 来执行这条命令,
后面 删除 log 和 __*是删除生成的日志和持久数据文件. 大家可以试试效果很好.
到这里 Linux上编译已经通过了. 下面直接上代码 . 一个个的来
2.2 首先看原子操作类 scatom.h
#ifndef _SC_ATOM #define _SC_ATOM /* * 作者 : wz * * 描述 : 简单的原子操作,目前只考虑 VS(CL) 小端机 和 gcc * 推荐用 posix 线程库 */ // 如果 是 VS 编译器 #if defined(_MSC_VER) #include <Windows.h> //忽略 warning C4047: “==”:“void *”与“LONG”的间接级别不同 #pragma warning(disable:4047) // v 和 a 多 long 这样数据 #define ATOM_FETCH_ADD(v, a) \ InterlockedExchangeAdd((LONG*)&(v), (LONG)(a)) #define ATOM_ADD_FETCH(v, a) \ InterlockedAdd((LONG*)&(v), (LONG)(a)) #define ATOM_SET(v, a) \ InterlockedExchange((LONG*)&(v), (LONG)(a)) #define ATOM_CMP(v, c, a) \ (c == InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c)) /* 对于 InterlockedCompareExchange(v, c, a) 等价于下面 long tmp = v ; v == a ? v = c : ; return tmp; 咱么的 ATOM_FETCH_CMP(v, c, a) 等价于下面 long tmp = v ; v == c ? v = a : ; return tmp; */ #define ATOM_FETCH_CMP(v, c, a) \ InterlockedCompareExchange((LONG*)&(v), (LONG)(a), (LONG)c) #define ATOM_LOCK(v) \ while(ATOM_SET(v, 1)) \ Sleep(0) #define ATOM_UNLOCK(v) \ ATOM_SET(v, 0) //否则 如果是 gcc 编译器 #elif defined(__GNUC__) #include <unistd.h> /* type tmp = v ; v += a ; return tmp ; type 可以是 8,16,32,84 的 int/uint */ #define ATOM_FETCH_ADD(v, a) \ __sync_fetch_add_add(&(v), (a)) /* v += a ; return v; */ #define ATOM_ADD_FETCH(v, a) \ __sync_add_and_fetch(&(v), (a)) /* type tmp = v ; v = a; return tmp; */ #define ATOM_SET(v, a) \ __sync_lock_test_and_set(&(v), (a)) /* bool b = v == c; b ? v=a : ; return b; */ #define ATOM_CMP(v, c, a) \ __sync_bool_compare_and_swap(&(v), (c), (a)) /* type tmp = v ; v == c ? v = a : ; return v; */ #define ATOM_FETCH_CMP(v, c, a) \ __sync_val_compare_and_swap(&(v), (c), (a)) /* 加锁等待,知道 ATOM_SET 返回合适的值 _INT_USLEEP 是操作系统等待纳秒数,可以优化,看具体操作系统 使用方式 int lock; ATOM_LOCK(lock); //to do think ... ATOM_UNLOCK(lock); */ #define _INT_USLEEP (2) #define ATOM_LOCK(v) \ while(ATOM_SET(v, 1)) \ usleep(_INT_USLEEP) /* 对ATOM_LOCK 解锁, 当然 直接调用相当于 v = 0; */ #define ATOM_UNLOCK(v) \ __sync_lock_release(&(v)) #endif /*!_MSC_VER && !__GNUC__ */ #endif /*!_SC_ATOM*/
这些原子操作,在我前面讲解 云风的字符串详细提过,这里简单说一下 为什么 会有 LONG*
这是这两种原子操作机制不一样. Linux上 __sync 是 在编译器层次实现的, 而 window的 Interlock 是在 函数库层实现的.
差距很大,这里强转LONG* 是一种伪装操作.
2.3 再看 schead.h
#ifndef _H_CHEAD #define _H_CHEAD #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <errno.h> #include <string.h> #include <time.h> #include <stdint.h> #include <stddef.h> /* * 1.0 错误定义宏 用于判断返回值状态的状态码 _RF表示返回标志 * 使用举例 : int flag = scconf_get("pursue"); if(flag != _RT_OK){ sclog_error("get config %s error! flag = %d.", "pursue", flag); exit(EXIT_FAILURE); } * 这里是内部 使用的通用返回值 标志 */ #define _RT_OK (0) //结果正确的返回宏 #define _RT_EB (-1) //错误基类型,所有错误都可用它,在不清楚的情况下 #define _RT_EP (-2) //参数错误 #define _RT_EM (-3) //内存分配错误 #define _RT_EC (-4) //文件已经读取完毕或表示链接关闭 #define _RT_EF (-5) //文件打开失败 /* * 2.0 如果定义了 __GNUC__ 就假定是 使用gcc 编译器,为Linux平台 * 否则 认为是 Window 平台,不可否认宏是丑陋的 */ #if defined(__GNUC__) //下面是依赖 Linux 实现,等待毫秒数 #include <unistd.h> #include <sys/time.h> #define SLEEPMS(m) \ usleep(m * 1000) #else // 这里创建等待函数 以毫秒为单位 , 需要依赖操作系统实现 #include <Windows.h> #include <direct.h> // 加载多余的头文件在 编译阶段会去掉 #define inline __inline //附加一个内联函数宏 #define rmdir _rmdir /** * Linux sys/time.h 中获取时间函数在Windows上一种移植实现 **tv : 返回结果包含秒数和微秒数 **tz : 包含的时区,在window上这个变量没有用不返回 ** : 默认返回0 **/ extern int gettimeofday(struct timeval* tv, void* tz); //为了解决 不通用功能 #define localtime_r(t, tm) localtime_s(tm, t) #define SLEEPMS(m) \ Sleep(m) #endif /*__GNUC__ 跨平台的代码都很丑陋 */ //3.0 浮点数据判断宏帮助, __开头表示不希望你使用的宏 #define __DIFF(x, y) ((x)-(y)) //两个表达式做差宏 #define __IF_X(x, z) ((x)<z&&(x)>-z) //判断宏,z必须是宏常量 #define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判断x和y是否在误差范围内相等 //3.1 float判断定义的宏 #define _FLOAT_ZERO (0.000001f) //float 0的误差判断值 #define EQ_FLOAT_ZERO(x) __IF_X(x,_FLOAT_ZERO) //float 判断x是否为零是返回true #define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判断表达式x与y是否相等 //3.2 double判断定义的宏 #define _DOUBLE_ZERO (0.000000000001) //double 0误差判断值 #define EQ_DOUBLE_ZERO(x) __IF_X(x,_DOUBLE_ZERO) //double 判断x是否为零是返回true #define EQ_DOUBLE(x,y) EQ(x, y, _DOUBLE_ZERO) //判断表达式x与y是否相等 //4.0 控制台打印错误信息, fmt必须是双引号括起来的宏 #ifndef CERR #define CERR(fmt, ...) \ fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\ __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__) #endif/* !CERR */ //4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量 #ifndef CERR_EXIT #define CERR_EXIT(fmt,...) \ CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE) #endif/* !ERR */ #ifndef IF_CERR /* *4.2 if 的 代码检测 * * 举例: * IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!"); * 遇到问题打印日志直接退出,可以认为是一种简单模板 * code : 要检测的代码 * fmt : 必须是""括起来的字符串宏 * ... : 后面的参数,参照printf */ #define IF_CERR(code, fmt, ...) \ if((code) < 0) \ CERR_EXIT(fmt, ##__VA_ARGS__) #endif //!IF_CERR //5.0 获取数组长度,只能是数组类型或""字符串常量,后者包含'\0' #ifndef LEN #define LEN(arr) \ (sizeof(arr)/sizeof(*(arr))) #endif/* !ARRLEN */ //6.0 程序清空屏幕函数 #ifndef CONSOLE_CLEAR #ifndef _WIN32 #define CONSOLE_CLEAR() \ system("printf '\ec'") #else #define CONSOLE_CLEAR() \ system("cls") #endif/* _WIN32 */ #endif /*!CONSOLE_CLEAR*/ //7.0 置空操作 #ifndef BZERO //v必须是个变量 #define BZERO(v) \ memset(&v,0,sizeof(v)) #endif/* !BZERO */ //9.0 scanf 健壮的 #ifndef SAFETY_SCANF #define SAFETY_SCANF(scanf_code,...) \ while(printf(__VA_ARGS__),scanf_code){\ while(getchar()!='\n');\ puts("输入出错,请按照提示重新操作!");\ }\ while(getchar()!='\n') #endif /*!SAFETY_SCANF*/ //10.0 简单的time帮助宏 #ifndef TIME_PRINT #define TIME_PRINT(code) {\ clock_t __st,__et;\ __st=clock();\ code\ __et=clock();\ printf("当前代码块运行时间是:%lf秒\n",(0.0+__et-__st)/CLOCKS_PER_SEC);\ } #endif /*!TIME_PRINT*/ //11.0 等待的宏 这里 已经处理好了 #define _STR_PAUSEMSG "请按任意键继续. . ." extern void sh_pause(void); #ifndef INIT_PAUSE # ifdef _DEBUG # define INIT_PAUSE() atexit(sh_pause) # else # define INIT_PAUSE() (void)316 /* 别说了,都重新开始吧 */ # endif #endif/* !INIT_PAUSE */ //12.0 判断是大端序还是小端序,大端序返回true extern bool sh_isbig(void); /** * sh_free - 简单的释放内存函数,对free再封装了一下 **可以避免野指针 **pobj:指向待释放内存的指针(void*) **/ extern void sh_free(void** pobj); /** * 获取 当前时间串,并塞入tstr中长度并返回 ** 使用举例 char tstr[64]; puts(gettimes(tstr, LEN(tstr))); **tstr : 保存最后生成的最后串 **len : tstr数组的长度 ** : 返回tstr首地址 **/ extern int sh_times(char tstr[], int len); #endif/* ! _H_CHEAD */
这里需要说明的一下是
/** * Linux sys/time.h 中获取时间函数在Windows上一种移植实现 **tv : 返回结果包含秒数和微秒数 **tz : 包含的时区,在window上这个变量没有用不返回 ** : 默认返回0 **/ extern int gettimeofday(struct timeval* tv, void* tz); //为了解决 不通用功能 #define localtime_r(t, tm) localtime_s(tm, t)
这两个函数都是为了在window上模拟 Linux 行为. 首先 gettimeofday 在window 没有这个功能,获取当前时间.
对于 安全的localtime 对于 不同平台实现不一样吧,这里觉得window设计的好.上面对于实现了大小端代码也特别巧妙.
2.4 schead.c 具体实现, 这些还是有一点看头,以后可能只关注Linux,window太罗嗦了
#include <schead.h> //简单通用的等待函数 void sh_pause(void) { rewind(stdin); printf(_STR_PAUSEMSG); getchar(); } //12.0 判断是大端序还是小端序,大端序返回true bool sh_isbig(void) { static union { unsigned short _s; unsigned char _cs[sizeof(unsigned short)]; } __ut = { 1 }; return __ut._cs[0] == 0; } /** * sh_free - 简单的释放内存函数,对free再封装了一下 **可以避免野指针 **@pobj:指向待释放内存的指针(void*) **/ void sh_free(void** pobj) { if (pobj == NULL || *pobj == NULL) return; free(*pobj); *pobj = NULL; } #if defined(_MSC_VER) /** * Linux sys/time.h 中获取时间函数在Windows上一种移植实现 **tv : 返回结果包含秒数和微秒数 **tz : 包含的时区,在window上这个变量没有用不返回 ** : 默认返回0 **/ int gettimeofday(struct timeval* tv, void* tz) { time_t clock; struct tm tm; SYSTEMTIME wtm; GetLocalTime(&wtm); tm.tm_year = wtm.wYear - 1900; tm.tm_mon = wtm.wMonth - 1; //window的计数更好写 tm.tm_mday = wtm.wDay; tm.tm_hour = wtm.wHour; tm.tm_min = wtm.wMinute; tm.tm_sec = wtm.wSecond; tm.tm_isdst = -1; //不考虑夏令时 clock = mktime(&tm); tv->tv_sec = (long)clock; //32位使用,接口已经老了 tv->tv_usec = wtm.wMilliseconds * 1000; return _RT_OK; } #endif /** * 获取 当前时间串,并塞入tstr中C长度并返回 ** 使用举例 char tstr[64]; puts(gettimes(tstr, LEN(tstr))); **tstr : 保存最后生成的最后串 **len : tstr数组的长度 ** : 返回tstr首地址 **/ int sh_times(char tstr[], int len) { struct tm st; time_t t = time(NULL); localtime_r(&t, &st); return (int)strftime(tstr, len, "%F %X", &st); }
上面函数基本都是线程安全的, 实现也都比较简单. 大家可以自行练习.
2.5 sclog.h 关于C日志库的接口设计 多用户安全跨平台的日志库
#ifndef _H_SCLOG #define _H_SCLOG //-------------------------------------------------------------------------------------------| // 第一部分 共用的参数宏 //-------------------------------------------------------------------------------------------| // //关于日志切分,需要用第三方插件例如crontab , 或者下次我自己写一个监测程序. #define _INT_LITTLE (64) //保存时间或IP长度 #define _INT_LOG (1024<<3) //最多8k日志 #define _STR_SCLOG_PATH "log" //日志相对路径目录,如果不需要需要配置成"" #define _STR_SCLOG_LOG "sc.log" //普通log日志 DEBUG,INFO,NOTICE,WARNING,FATAL都会输出 #define _STR_SCLOG_WFLOG "sc.log.wf" //级别比较高的日志输出 FATAL和WARNING /** * fstr : 为标识串 例如 _STR_SCLOG_FATAL, 必须是双引号括起来的串 ** ** 拼接一个 printf 输出格式串 **/ #define SCLOG_PUTS(fstr) \ "%s][" fstr "][%s:%d:%s][logid:%u][reqip:%s][mod:%s]" #define _STR_SCLOG_FATAL "FATAL" //错误,后端使用 #define _STR_SCLOG_WARNING "WARNING" //警告,前端使用错误,用这个 #define _STR_SCLOG_NOTICE "NOTICE" //系统使用,一般标记一条请求完成,使用这个日志 #define _STR_SCLOG_INFO "INFO" //普通的日志打印 #define _STR_SCLOG_TRACE "TRACE" #define _STR_SCLOG_DEBUG "DEBUG" //测试用的日志打印,在发布版这些日志会被清除掉 /** * fstr : 只能是 _STR_SCLOG_* 开头的宏 ** fmt : 必须是""括起来的宏.单独输出的格式宏 ** ... : 对映fmt参数集 ** ** 拼接这里使用的宏,为sl_printf 打造一个模板,这里存在一个坑,在Window \n表示 CRLF, Unix就是LF **/ #define SCLOG_PRINTF(fstr, fmt, ...) \ sl_printf(SCLOG_PUTS(fstr) fmt "\n", sl_get_times(), __FILE__, __LINE__, __func__, \ sl_get_logid(), sl_get_reqip(), sl_get_mod(), ##__VA_ARGS__) /** * FATAL... 日志打印宏 ** fmt : 输出的格式串,需要""包裹起来 ** ... : 后面的参数,服务于fmt **/ #define SL_FATAL(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_FATAL, fmt, ##__VA_ARGS__) #define SL_WARNING(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_WARNING, fmt, ##__VA_ARGS__) #define SL_NOTICE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_NOTICE, fmt, ##__VA_ARGS__) #define SL_INFO(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_INFO, fmt, ##__VA_ARGS__) // 发布状态下,关闭SL_DEBUG 宏,需要重新编译,没有改成运行时的判断,这个框架主要围绕单机部分多服务器 #if defined(_DEBUG) # define SL_TRACE(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_TRACE, fmt, ##__VA_ARGS__) # define SL_DEBUG(fmt, ...) SCLOG_PRINTF(_STR_SCLOG_DEBUG, fmt, ##__VA_ARGS__) #else # define SL_TRACE(fmt, ...) (void)0x123 /* 人生难道就是123*/ # define SL_DEBUG(fmt, ...) (void)0xa91 /* 爱过哎 */ #endif //-------------------------------------------------------------------------------------------| // 第二部分 对日志信息体操作的get和set,这里隐藏了信息体的实现 //-------------------------------------------------------------------------------------------| /** * 线程的私有数据初始化 ** ** mod : 当前线程名称 ** reqip : 请求的ip ** return : _RT_OK 表示正常,_RF_EM内存分配错误 **/ extern int sl_pecific_init(const char* mod, const char* reqip); /** * 重新设置线程计时时间 ** 正常返回 _RT_OK, _RT_EM表示内存没有分配 **/ int sl_set_timev(void); /** * 获取日志信息体的唯一的logid **/ unsigned sl_get_logid(void); /** * 获取日志信息体的请求ip串,返回NULL表示没有初始化 **/ const char* sl_get_reqip(void); /** * 获取日志信息体的时间串,返回NULL表示没有初始化 **/ const char* sl_get_times(void); /** * 获取日志信息体的名称,返回NULL表示没有初始化 **/ const char* sl_get_mod(void); //-------------------------------------------------------------------------------------------| // 第三部分 对日志系统具体的输出输入接口部分 //-------------------------------------------------------------------------------------------| /** * 日志系统首次使用初始化,找对对映日志文件路径,创建指定路径 **返回值具体见 schead.h 中定义的错误类型 **/ extern int sl_start(void); /** * 这个函数不希望你使用,是一个内部限定死的日志输出内容.推荐使用相应的宏 **打印相应级别的日志到对映的文件中. ** ** format : 必须是""号括起来的宏,开头必须是 [FALTAL:%s]后端错误 ** [WARNING:%s]前端错误, [NOTICE:%s]系统使用, [INFO:%s]普通信息, ** [DEBUG:%s] 开发测试用 ** ** return : 返回输出内容长度 **/ int sl_printf(const char* format, ...); #endif // !_H_SCLOG
关于这个宏
/** * fstr : 为标识串 例如 _STR_SCLOG_FATAL, 必须是双引号括起来的串 ** ** 拼接一个 printf 输出格式串 **/ #define SCLOG_PUTS(fstr) \ "%s][" fstr "][%s:%d:%s][logid:%u][reqip:%s][mod:%s]"
主要为了 下面这种宏拼接字符串用的
#define _STR_SCLOG_FATAL "FATAL" //错误,后端使用
第一个%s输出 运行时间量用的.
这个日志库的使用流程是先初始化,后就可以用了,初始化调用 int sl_pecific_init(const char* mod, const char* reqip); 添加模块名称和请求ip.
2.6 关于 sclog.c 的具体实现
#include <sclog.h> #include <schead.h> #include <scatom.h> #include <pthread.h> #include <stdarg.h> //-------------------------------------------------------------------------------------------| // 第二部分 对日志信息体操作的get和set,这里隐藏了信息体的实现 //-------------------------------------------------------------------------------------------| //线程私有数据 __lkey, __lonce为了__lkey能够正常初始化 static pthread_key_t __lkey; static pthread_once_t __lonce = PTHREAD_ONCE_INIT; static unsigned __logid = 0; //默认的全局logid,唯一标识 //内部简单的释放函数,服务于pthread_key_create 防止线程资源泄露 static void __slinfo_destroy(void* slinfo) { //printf("pthread 0x%p:0x%p destroy!\n", pthread_self().p, slinfo); free(slinfo); } static void __gkey(void) { pthread_key_create(&__lkey, __slinfo_destroy); } struct slinfo { unsigned logid; //请求的logid,唯一id char reqip[_INT_LITTLE]; //请求方ip char times[_INT_LITTLE]; //当前时间串 struct timeval timev; //处理时间,保存值,统一用毫秒 char mod[_INT_LITTLE]; //当前线程的模块名称,不能超过_INT_LITTLE - 1 }; /** * 线程的私有数据初始化 ** ** mod : 当前线程名称 ** reqip : 请求的ip ** return : _RT_OK 表示正常,_RF_EM内存分配错误 **/ int sl_pecific_init(const char* mod, const char* reqip) { struct slinfo* pl; //保证 __gkey只被执行一次 pthread_once(&__lonce, __gkey); if((pl = pthread_getspecific(__lkey)) == NULL){ //重新构建 if ((pl = malloc(sizeof(struct slinfo))) == NULL) return _RT_EM; //printf("pthread 0x%p:0x%p create!\n", pthread_self().p,pl); } gettimeofday(&pl->timev, NULL); pl->logid = ATOM_ADD_FETCH(__logid, 1); //原子自增 strcpy(pl->mod, mod); //复制一些数据 strcpy(pl->reqip, reqip); //设置私有变量 pthread_setspecific(__lkey, pl); return _RT_OK; } /** * 重新设置线程计时时间 ** 正常返回 _RT_OK, _RT_EM表示内存没有分配 **/ int sl_set_timev(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) return _RT_EM; gettimeofday(&pl->timev, NULL); return _RT_OK; } /** * 获取日志信息体的唯一的logid **/ unsigned sl_get_logid(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回0表示没有找见 return 0u; return pl->logid; } /** * 获取日志信息体的请求ip串,返回NULL表示没有初始化 **/ const char* sl_get_reqip(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回NULL表示没有找见 return NULL; return pl->reqip; } /** * 获取日志信息体的时间串,返回NULL表示没有初始化 **/ const char* sl_get_times(void) { struct timeval et; //记录时间 unsigned td; struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回NULL表示没有找见 return NULL; gettimeofday(&et, NULL); //同一用微秒记 td = 1000000 * (et.tv_sec - pl->timev.tv_sec) + et.tv_usec - pl->timev.tv_usec; snprintf(pl->times, LEN(pl->times), "%u", td); return pl->times; } /** * 获取日志信息体的名称,返回NULL表示没有初始化 **/ const char* sl_get_mod(void) { struct slinfo* pl = pthread_getspecific(__lkey); if (NULL == pl) //返回NULL表示没有找见 return NULL; return pl->mod; } //-------------------------------------------------------------------------------------------| // 第三部分 对日志系统具体的输出输入接口部分 //-------------------------------------------------------------------------------------------| //错误重定向宏 具体应用 于 "mkdir -p \"" _STR_SCLOG_PATH "\" >" _STR_TOOUT " 2>" _STR_TOERR #define _STR_TOOUT "__out__" #define _STR_TOERR "__err__" #define _STR_LOGID "__lid__" //保存logid,持久化 static struct { //内部用的私有变量 FILE* log; FILE* wf; bool isdir; //标志是否创建了目录 } __slmain; /** * 日志关闭时候执行,这个接口,关闭打开的文件句柄 **/ static void __sl_end(void) { FILE* lid; void* pl; // 在简单地方多做安全操作值得,在核心地方用算法优化的才能稳固 if (!__slmain.isdir) return; //重置当前系统打开文件结构体 fclose(__slmain.log); fclose(__slmain.wf); BZERO(__slmain); //写入文件 lid = fopen(_STR_LOGID, "w"); if (NULL != lid) { fprintf(lid, "%u", __logid); fclose(lid); } //主动释放私有变量,其实主进程 相当于一个线程是不合理的!还是不同的生存周期的 pl = pthread_getspecific(__lkey); __slinfo_destroy(pl); pthread_setspecific(__lkey, NULL); } /** * 日志系统首次使用初始化,找对对映日志文件路径,创建指定路径 **返回值具体见 schead.h 中定义的错误类型 **/ int sl_start(void) { FILE *lid; //单例只执行一次 if (!__slmain.isdir) { __slmain.isdir = true; //先多级创建目录,简易不借助宏实现跨平台,system返回值是很复杂,默认成功! system("mkdir -p \"" _STR_SCLOG_PATH "\" >" _STR_TOOUT " 2>" _STR_TOERR); rmdir("-p"); remove(_STR_TOOUT); remove(_STR_TOERR); } if (NULL == __slmain.log) { __slmain.log = fopen(_STR_SCLOG_PATH "/" _STR_SCLOG_LOG, "a+"); if (NULL == __slmain.log) CERR_EXIT("__slmain.log fopen %s error!", _STR_SCLOG_LOG); } //继续打开 wf 文件 if (NULL == __slmain.wf) { __slmain.wf = fopen(_STR_SCLOG_PATH "/" _STR_SCLOG_WFLOG, "a+"); if (NULL == __slmain.wf) { fclose(__slmain.log); //其实这都没有必要,图个心安 CERR_EXIT("__slmain.log fopen %s error!", _STR_SCLOG_WFLOG); } } //读取文件内容 if ((lid = fopen(_STR_LOGID, "r")) != NULL) { //读取文件内容,持久化 fscanf(lid, "%u", &__logid); } //这里可以单独开启一个线程或进程,处理日志整理但是 这个模块可以让运维做,按照规则搞 sl_pecific_init("main thread","0.0.0.0"); //注册退出操作 atexit(__sl_end); return _RT_OK; } int sl_printf(const char* format, ...) { char tstr[_INT_LITTLE];// [%s] => [2016-01-08 23:59:59] int len; va_list ap; char logs[_INT_LOG]; //这个不是一个好的设计,最新c 中支持 int a ; if (!__slmain.isdir) { CERR("%s fopen %s | %s error!",_STR_SCLOG_PATH, _STR_SCLOG_LOG, _STR_SCLOG_WFLOG); return _RT_EF; } //初始化参数 sh_times(tstr, _INT_LITTLE - 1); len = snprintf(logs, LEN(logs), "[%s ", tstr); va_start(ap, format); vsnprintf(logs + len, LEN(logs) - len, format, ap); va_end(ap); // 写普通文件 log fputs(logs, __slmain.log); //把锁机制去掉了,fputs就是线程安全的 // 写警告文件 wf if (format[4] == 'F' || format[4] == 'W') { //当为FATAL或WARNING需要些写入到警告文件中 fputs(logs, __slmain.wf); } return _RT_OK; }
我们对 __sl_end 函数解析一下 主要做有两部分工作比较特殊,第一部分
//写入文件 lid = fopen(_STR_LOGID, "w"); if (NULL != lid) { fprintf(lid, "%u", __logid); fclose(lid); }
将 __logid 变量持久化.保存在一个文件中,算作一个唯一标识吧.
第二部分是为了解决 sl_start中使用了线程私有数据,在退出时候释放, 对于线程私有数据设置为NULL,表示处理释放的时候跳过释放的函数操作.
//主动释放私有变量,其实主进程 相当于一个线程是不合理的!还是不同的生存周期的 pl = pthread_getspecific(__lkey); __slinfo_destroy(pl); pthread_setspecific(__lkey, NULL);
还有在sl_start 中有一段创建目录的代码
//单例只执行一次 if (!__slmain.isdir) { __slmain.isdir = true; //先多级创建目录,简易不借助宏实现跨平台,system返回值是很复杂,默认成功! system("mkdir -p \"" _STR_SCLOG_PATH "\" >" _STR_TOOUT " 2>" _STR_TOERR); rmdir("-p"); remove(_STR_TOOUT); remove(_STR_TOERR); }
也是偷懒的写法,不同平台创建多层文件接口不一样,写起来麻烦,自己用shell 苟合了一个. 启动的时候用,还凑合着吧.
2.7 最后测试文件 main.c
#include <stdio.h> #include <stdlib.h> #include <pthread.h> //简单测试 pthread线程库 static void* __run(void* arg) { puts("你好!"); return NULL; } int main_pthread_test(int argc, char* argv[]) { pthread_t tid; //开始跑起来 pthread_create(&tid, NULL, __run, NULL); //等待结束 pthread_join(tid, NULL); system("pause"); return 0; } // -------------------------下面测试 sclog.h 接口功能 #include <schead.h> #include <sclog.h> static void* test_one(void* arg) { sl_pecific_init("test_one", "8.8.8.8"); SL_TRACE("test_one log test start!"); for (int i = 0; i < 100; ++i) { SL_FATAL("pthread test one fatal is at %d, It's %s.",i, "OK"); SL_WARNING("pthread test one warning is at %d, It's %s.", i, "OK"); SL_INFO("pthread test one info is at %d, It's %s.", i, "OK"); SL_DEBUG("pthread test one debug is at %d, It's %s.", i, "OK"); SLEEPMS(1); //等待1s } SL_TRACE("test_one log test end!"); return NULL; } // 线程二测试函数 static void* test_two(void* arg) { //线程分离,自回收 pthread_detach(pthread_self()); sl_pecific_init("test_two", "8.8.8.8"); SL_TRACE("test_two log test start!"); for (int i = 0; i < 3; ++i) { SL_FATAL("pthread test two fatal is at %d, It's %s.", i, "OK"); SL_WARNING("pthread test two warning is at %d, It's %s.", i, "OK"); SL_INFO("pthread test two info is at %d, It's %s.", i, "OK"); SL_DEBUG("pthread test two debug is at %d, It's %s.", i, "OK"); SLEEPMS(2); //等待1s } SL_TRACE("test_two SL_TRACE test end!"); return NULL; } int main(int argc, char* argv[]) { pthread_t tone, ttwo; //注册等待函数 INIT_PAUSE(); sl_start(); SL_NOTICE("main log test start!"); pthread_create(&tone, NULL, test_one, NULL); pthread_create(&ttwo, NULL, test_two, NULL); pthread_join(tone, NULL); SL_NOTICE("main log test end!"); return 0; }
上面第一部分是 window上测试 posix线程框架的跳过,后面是测试当前整个框架的一切正常.
3 查看运行结果
首先编译
查看缓冲文件 __lid__ 保存logid
后面具体生成日志文件如下
同样我们看看window上结果如下
结果都相似,后面将着重介绍如何在window上搭建开发环境
4.在window 上搭建 sc_console 开发项目,自己生成模板
首先看目录结构
外部正式文件结果如下
首先我们看 pthread 配置 前面已经说过了,现在有两方便要注意
第一方面关于 ptread.h 源码修改 删除 一个 关于 time结构体冲突.
后面添加一个库引用处理代码
后面在对应生成的执行文件中导入相应的pthread动态库例如如下
到这里基础的就能运行了, 现在是挨个配置 具体操作 这里有个文件 要求总结如下
help.txt
/* wz 这里关于这个系统使用的一些注意事项主要是对于 VS的操作的, 对于GCC还需要单搞,这些代码都具备跨平台的能力,但是需要配置,需要你熟悉! 了解下面操作的原因,熟悉它,为了项目管理C开发大型项目约束太多了,都需要从头来! 1.设置 VS的 项目右键属性 -> VC++ 目录 1.1. 添加 包含目录 $(ProjectDir)main $(ProjectDir)module $(ProjectDir)module/pthread $(ProjectDir)module/pthread/inlcude $(ProjectDir)module/schead $(ProjectDir)module/schead/inlcude 2. lib 库添加 2.1 添加 pthread 模块lib 引用, 看 引用目录 $(ProjectDir)/pthread/lib/x86 2.2 对于 x64 那就添加为 $(ProjectDir)/pthread/lib/x64 3. dll 库的添加 3.1 添加 dll 目前这个需要手工操作,目前不智能,VS 对C++支持的好缓慢, M$确实很坑 找到相应的 生成的exe目录下添加 对应的 dll, x86 => $(ProjectDir)/pthread/dll/x86 x64 => $(ProjectDir)/pthread/dll/x64 4. 添加部分宏 C/C++ -> 预处理器 -> 预处理器定义 _CRT_SECURE_NO_WARNINGS */
按照上面配置 具体 截图看下面
按照这个操作将
四种组合都配置一遍,基本都ok了这个框架就搭建好了.
到这里 扩展一下再 Release 发布模块下怎么调试, 请按照下面做
设置在Release模式下调试的方法:
1.工程项目上右键 -> 属性
2.c++ -> 常规 -〉调试信息格式 选 程序数据库(/Zi)或(/ZI), 注意:如果是库的话,只能(Zi)
3.c++ -> 优化 -〉优化 选 禁止(/Od)
4.连接器 -〉调试 -〉生成调试信息 选 是 (/DEBUG)
5.在优化里 关闭全程序优化
到这里基本就结束了,欢迎喜欢C的同学试试.
[b]后记[/b]
[b] 有错误是难免的,以后准备逐步放弃跨平台操作, 简单的window来,复杂的Linux来. 对于跨平台冗余代码比较多,而且强扭的瓜不甜.[/b]
而且别说跨平台了,跨编译器都很恶心. 而且像云风那种老鸟都不写跨平台代码,自己这种菜鸟更不敢写了. 欢迎大家试试. 上面只是这个sc_console.
中最基础的后面会假如 配置读取,json引擎,csv引擎代码,还有一些特定平台的代码功能等等. 有错误会立马改正.
有时觉得写代码还是很有意思的.
相关文章推荐
- Cloud Native概念
- XML相关
- JavaMail如何保证邮件发送成功
- 新工作 Day10 周六
- Sicily 12986. An Odd Sum
- 【LeetCode-263】Ugly Number(C++)
- 题目1447:最短路
- BZOJ 2882 工艺 (字符串最小循环同构)
- 「锵锵三人行」只言片语
- HDOJ 2092 整数解
- 算法-蓝桥杯习题(5-1)
- VS的反汇编
- Git项目分支分配
- .zshrc 编辑
- 消息队列NetMQ使用介绍
- 用组合模式、策略模式、观察者模式结合来实现一个MVC
- 子元素浮动父容器高度不能自适应的CSS解决方法
- Could not connect to SMTP host: smtp.***.com, port: 465, response: -1
- .Xresources 编辑 urxvt 的设置
- lnmp架构