引言

 有的人实在是天意所归

             
json 1

   
张家界时代炸弹 投到她院子都 没炸. 有些事不能更改 是命!

俺们也快’老’了, 常回家看看.

 

前言

  扯淡甘休了,后天享受的也许有点多,都很简短,可是糅合在联合就是有点复杂. 我会具体讲解一些开发中坑.

首要围绕怎么在Linux和Window 上搭建C基础开发框架, 并且写一个协助多用户分级的日志库. sclog.

  

急需材料

1.Linux 用的code linux_sc_console

2.window 用的 项目 代码 sc_console_start

 

下载上面源码.其实源码都同样,只是放在分裂平台下运行测试,一切正常. 那里回答一个难点,为啥C程序员那么喜欢造轮子.

因为C自由,自由就觉着着温馨洋洋得意就好. 如果质量还足以那就更好了. 说白了笑容可掬就好.(当然,C中尚无一块天下的框架,导致群雄割据,小明东奔西跑.)

迎接互换增强.

 

正文

1.先从Linux 环境说起来

  这我们刚起

1.1 首先看下边结构

json 2

从下面 结构中我们得以见见 那一个 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  => 多线程日志 实现

 */    

那边 简单表达了瞬间,文件尊崇意义. 前面会向来贴代码, 有些东西倒霉说, 因为不和谐雕刻看开源代码, 很难不难说领悟. 前边

会对一部分细节和不小心的坑说雀巢(Nestle)下. 那些框架 实战意义值得学习, 当然因现实作业可以再优化.

上边看看 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[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 - 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 查看运行结果

率先编译

json 3

翻看缓冲文件 __lid__ 保存logid

json 4

后边具体变化日志文件如下

json 5

一如既往大家看看window上结果如下

json 6

结果都一般,前面将第一介绍怎么着在window上搭建开发环境

 

4.在window 上搭建 sc_console 开发项目,自己生成模板

第一看目录结构

 

json 7

表面规范文件结果如下

json 8

第一大家看 pthread 配置 前面早已说过了,现在有两利于要注意

第一方面关于 ptread.h 源码修改 删除 一个 关于 time结构体顶牛.

前边添加一个库引用处理代码

json 9

末端在对应生成的实践文书中导入相应的pthread动态库例如如下

json 10

到此地基础的就能运作了, 现在是各样配置 具体操作 那里有个公文 须要计算如下

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 

*/

绳趋尺步上面配置 具体 截图看上边

json 11

json 12

json 13

依据那些操作将

json 14

三种组曼彻斯特布署四遍,基本都ok了那个框架就搭建好了.

到此地 扩充一下再 Release 揭橥模块下怎么调试, 请根据下边做

设置在Release格局下调试的点子:
1.工程项目上右键 -> 属性
2.c++ -> 常规 -〉调试音讯格式    选  程序数据库(/Zi)或(/ZI),
注意:如果是库的话,只可以(Zi)
3.c++ -> 优化 -〉优化            选  禁止(/Od)
4.连接器 -〉调试 -〉生成调试音讯 选  是 (/DEBUG)

5.在优化里 关闭全程序优化

json 15

到此处基本就得了了,欢迎喜欢C的校友试试.

 

后记

  有荒唐是在所难免的,未来准备逐步屏弃跨平台操作, 简单的window来,复杂的Linux来. 对于跨平台冗余代码相比较多,而且强扭的瓜不甜.

与此同时别说跨平台了,跨编译器都很恶心. 而且像云风这种老鸟都不写跨平台代码,自己那种菜鸟更不敢写了. 欢迎大家试试. 上边只是以此sc_console.

中最基础的末尾会若是 配置读取,json引擎,csv引擎代码,还有局地特定平台的代码成效等等. 有不当会及时修正. 

偶尔觉得写代码如故很风趣的.

 

相关文章

网站地图xml地图