C 基礎框架開發

来源:http://www.cnblogs.com/life2refuel/archive/2016/01/16/5135899.html
-Advertisement-
Play Games

搭建一個簡單的跨平臺C開發的基礎框架, 主要是使用posix線程庫和 自己寫的一個分級 日誌庫,以後可以在這個基礎上搭建你需要的框架會容易一點. 在Window和Linux上測試通過.


引言

 有的人真的是天命所歸

             

    延安時期炸彈 投到他院子都 沒炸. 有些事無法改變 是命!

我們也快'老'了, 常回家看看.

 

前言

  扯淡結束了,今天分享的可能有點多,都很簡單,但是糅合在一起就是有點複雜. 我會具體講解一些開發中坑.

主要圍繞如何在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, 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, "	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 那麼, 今天的任務呢是在linux上安裝 .net 5 運行時 ok, 先決條件: Ubuntu 14 (openSuse 42和Ubuntu 15都失敗了... 別問我為什麼) 開始安裝: "官方文檔" 安裝 .NET Version Manager (DNVM) 安裝 .NE...
  • [前言]在張銀奎老師的《軟體調試》一書中,詳細地講解了使用記憶體的分支記錄機制——BTS機制(5.3),並且給出了示例工具CpuWhere及其源代碼。但實際運行(VMware XP_SP3 單核)並沒有體現應有的效果,無法讀取到分支記錄。查看了源代碼並沒有發現任何問題,與書中所講一致。既然軟體本身沒有...
  • 最近,有個項目突然接到總部的安全漏洞報告,查看後知道是XSS攻擊。問題描述: 在頁面上有個隱藏域: 當前頁面提交到Controller時,未對action屬性做任何處理,直接又回傳到頁面上 如果此時action被用戶惡意修改為:***""*** 此時當頁面刷新時將執行alert(1),雖然錯...
  • 在插件實例修改3增加一個聯繫人功能配置文件 1 2 3 4 5 6 7 8 9 10 11 12...
  • define的用法小結define的用法只是一種純粹的替換功能,巨集定義的替換是預處理器處理的替換。 一:簡單的巨集定義用法 格式:#define 標識符 替換內容 替換的內容可以是數字,字元,字元串,特殊字元和空格,後面是什麼內容就會替換成什麼內容。 例如: #define N 5 效...
  • 參考書籍:Head First Java1、假設某方法是別人寫在某個類裡面的2、而此時你根本就不知道這個方法是否有風險(比如伺服器出故障會使程式受到影響);3、那最好的方法應該就是,在調用這個類的方法時,加上可能發生異常的處理方案,未雨綢繆。關鍵字:try……catch,throws,throw,f...
  • Day 2308 Udp接收端09 Udp鍵盤錄入數據方式10 Udp聊天11 TCP傳輸12 TCP傳輸213 TCP練習14 TCP複製文件08 Udp接收端需求:定義一個應用程式,用於接收udp協議傳輸的數據並處理。思路:1.定義UdpSocket服務。2.定義一個數據報包,因為要存儲接收到的...
  • 手動安裝django_chartit庫1 下載壓縮包2 解壓到python安裝目錄下,文件夾名為django_chartit,並檢查文件夾下是否有setup.py文件3 在cmd中進入django_chartit文件夾下,cmd命令為 cd C:\Python27\django_chartit4 輸...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...