C 基础框架开发
引言
有的人真的是天命所归
延安时期炸弹 投到他院子都 没炸. 有些事无法改变 是命!
我们也快'老'了, 常回家看看.
前言
扯淡结束了,今天分享的可能有点多,都很简单,但是糅合在一起就是有点复杂. 我会具体讲解一些开发中坑.
主要围绕如何在Linux和Window 上搭建C基础开发框架, 并且写一个支持多用户分级的日志库. sclog.
需要材料
1.Linux 用的code linux_sc_console
2.window 用的 项目 代码 sc_console_start
下载上面源码.其实源码都一样,只是放在不同平台下运行测试,一切正常. 这里回答一个问题,为什么C程序员那么喜欢造轮子.
因为C自由,自由就以为着自己开心就好. 如果性能还可以那就更好了. 说白了开心就好.(当然,C中没有一同天下的框架,导致群雄割据,小明东奔西跑.)
欢迎交流提高.
正文
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, )) \
Sleep() #define ATOM_UNLOCK(v) \
ATOM_SET(v, ) //否则 如果是 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, )) \
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 * )
#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) < ) \
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,,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) /* 别说了,都重新开始吧 */
# 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 = { };
return __ut._cs[] == ;
} /**
* 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 - ;
tm.tm_mon = wtm.wMonth - ; //window的计数更好写
tm.tm_mday = wtm.wDay;
tm.tm_hour = wtm.wHour;
tm.tm_min = wtm.wMinute;
tm.tm_sec = wtm.wSecond;
tm.tm_isdst = -; //不考虑夏令时
clock = mktime(&tm);
tv->tv_sec = (long)clock; //32位使用,接口已经老了
tv->tv_usec = wtm.wMilliseconds * ; 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 = ; //默认的全局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, ); //原子自增
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 = * (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[n]; 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 - );
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[] == 'F' || format[] == '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 ;
} // -------------------------下面测试 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 = ; i < ; ++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(); //等待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 = ; i < ; ++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(); //等待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 ;
}
上面第一部分是 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的同学试试.
后记
有错误是难免的,以后准备逐步放弃跨平台操作, 简单的window来,复杂的Linux来. 对于跨平台冗余代码比较多,而且强扭的瓜不甜.
而且别说跨平台了,跨编译器都很恶心. 而且像云风那种老鸟都不写跨平台代码,自己这种菜鸟更不敢写了. 欢迎大家试试. 上面只是这个sc_console.
中最基础的后面会假如 配置读取,json引擎,csv引擎代码,还有一些特定平台的代码功能等等. 有错误会立马改正.
有时觉得写代码还是很有意思的.
C 基础框架开发的更多相关文章
- 准备.Net转前端开发-WPF界面框架那些事,搭建基础框架
题外话 最近都没怎么写博客,主要是最近在看WPF方面的书<wpf-4-unleashed.pdf>,挑了比较重要的几个章节学习了下WPF基础技术.另外,也把这本书推荐给目前正在从事WPF开 ...
- PHP 设计模式 笔记与总结(2)开发 PSR-0 的基础框架
[PSR-0 规范的三项约定]: ① 命名空间必须与绝对路径一致 ② 类名的首字母必须大写 ③ 除入口文件外,其他".php"必须只有一个类(不能有可执行的代码) [开发符合 PS ...
- PHP扩展开发(1)-创建基础框架
生成PHP扩展开发的基础框架. 一.Linux下 $>cd ~/{php源码}/ext $>./ext_skel --extname=simple Creating direc ...
- .Net基础体系和跨框架开发普及
.net体系经过十几年发展,发生了很多变化.特别是在最近两年,随着开源和跨平台的发展,衍生出很多概念,像标准库,可移植库,.Net Core等,相信有不少同学对他们之间的关系是有一些困惑的,这里我从基 ...
- 如莲开发平台(MIS基础框架、Java技术、B/S结构)
关于 「如莲」是一套MIS类系统基础框架,主要用于各类“管理信息系统”的开发,也适合做网站后台开发.可省去开发时的框架搭建.规范约定.权限管理等基础工作,直接专注于业务功能实现. 「如 ...
- LayIM.AspNetCore Middleware 开发日记(三)基础框架搭建
前言 在上一篇中简单讲了一些基础知识,例如Asp.Net Core Middleware 的使用,DI的简单使用以及嵌入式资源的使用方法等.本篇就是结合基础知识来构建一个基础框架出来. 那么框架有什么 ...
- (转).Net基础体系和跨框架开发普及
在园子里看到了一篇关于.net体系及框架开发的文章,感触颇深,身为一个.net程序员,发现自己在这方面的跟进和理解远远不够.转到自己这里,分享的同时方便日后查看. 原文链接: http://www.c ...
- 快速接入 Android BLE 开发的基础框架
代码地址如下:http://www.demodashi.com/demo/12092.html ** Android BLE基础操作框架,基于回调,操作简单.包含扫描.多连接.广播包解析.服务读写及通 ...
- IOS开发 基础框架(Fondation Framework)的线程安全
有一种误解,认为基础框架(Foundation framework)是线程安全的,而Application Kit是非线程安全的.不幸的是,这是一个总的概括,从而造成一点误导.每个框架都包含了线程安全 ...
随机推荐
- 【题解】Atcoder ARC#90 F-Number of Digits
Atcoder刷不动的每日一题... 首先注意到一个事实:随着 \(l, r\) 的增大,\(f(r) - f(l)\) 会越来越小.考虑暴力处理出小数据的情况,我们可以发现对于左端点 \(f(l) ...
- BZOJ4570:[SCOI2016]妖怪——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=4570 邱老师是妖怪爱好者,他有n只妖怪,每只妖怪有攻击力atk和防御力dnf两种属性.邱老师立志成 ...
- UVA.548 Tree(二叉树 DFS)
UVA.548 Tree(二叉树 DFS) 题意分析 给出一棵树的中序遍历和后序遍历,从所有叶子节点中找到一个使得其到根节点的权值最小.若有多个,输出叶子节点本身权值小的那个节点. 先递归建树,然后D ...
- redux样板代码简化写法
直接使用redux,要写很多样板代码,大量的actiontype,actionCreator.一个异步的方法要写三个actiontype,三个actionCreator,十分繁琐.下面是本人使用的一种 ...
- best code #54 div 2 A 水
A problem of sorting Accepts: 443 Submissions: 1696 Time Limit: 2000/1000 MS (Java/Others) Memory Li ...
- 宽度搜索(BFS)中求最短路径问题理解记录
借助ACM1242题深入理解迷宫类最短路径搜索并记录路径长度的问题及解决方法:这是初次接触优先队列,尤其是不知道该怎样去记忆在结构体重自定义大小比较的符号方向,很容易混淆符号向哪是从大到小排列,向哪是 ...
- Moodle插件开发系列——XMLDB编辑器
Moodle插件开发系列——XMLDB编辑器 位置:网站管理>开发> XML编辑器 l XML编辑器是制作install.xml文件的工具,而install.xml是指定Moodle建立 ...
- linux内核的配置
以2.6.35.7版本的内核为例 总结:.config决定了Make时的条件编译与连接..config文件由两次配置第一次make XX_defconfig 第二次menuconfig. 1.分析源码 ...
- 常见一个新的maven web工程
使用Eclipse创建一个新的maven Web应用工程,步骤如下: 1.在Elipse中新建一个maven工程,点击next: 2.选择工程路径(此处使用默认的),点击next: 3.选择Arche ...
- 51Nod 1080
#include "bits/stdc++.h" using namespace std; #define LL long long #define INF 0x3f3f3f3f3 ...