搭建一個簡單的跨平臺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, "