引言 - 一切纔剛剛開始node
structc 是 C 結構基礎庫. 簡單可複用. linux
structc - https://github.com/wangzhione/structcc++
以前也描述過幾回 structc, 文字多代碼風格少. 最近加班很少, 準備詳細解說哈其思考初衷.git
0.0 總體結構github
structc
├── extern
├── LICENSE
├── Makefile
├── README.md
├── structc
└── structc.sln
structc.sln : winds 項目管理文件 visual studio shell
structc : 項目總體源碼和素材文件目錄編程
README.md : 項目介紹 Markdownjson
Makefile : linux 編譯文件 make ubuntu
LICENSE : MIT 開源協議網絡
extern : 項目引入的外部庫目錄
extern ├── jemalloc ├── jemalloc-vc141-Release-static.lib ├── libuv.lib ├── pthread.h ├── pthread_lib.lib ├── sched.h ├── semaphore.h ├── strings.h ├── uv └── uv.h
以上就是咱們看到 structc 項目總體結構.
0.1 外部庫
當前很謹慎的引入兩個半外部庫. 最大程度會靜態庫編譯連接運行. 榮我慢慢細說.
1. jemalloc - https://github.com/jemalloc/jemalloc
jemalloc 是 c 構建底層高性能 malloc 庫. 也被稱爲系統編程末期最後免費午飯. 整個 structc
malloc 全權交給 je_malloc 抗壓. 其中 winds 編譯靜態庫部分, 項目自己也有細說 -
https://github.com/jemalloc/jemalloc/tree/dev/msvc
How to build jemalloc for Windows ================================= 1. Install Cygwin with at least the following packages: * autoconf * autogen * gawk * grep * sed 2. Install Visual Studio 2015 or 2017 with Visual C++ 3. Add Cygwin\bin to the PATH environment variable 4. Open "x64 Native Tools Command Prompt for VS 2017" (note: x86/x64 doesn't matter at this point) 5. Generate header files: sh -c "CC=cl ./autogen.sh" 6. Now the project can be opened and built in Visual Studio: msvc\jemalloc_vc2017.sln
( 注: vs 使用最新版本. 網址打不開那就FQ. 後面其也同樣, 時刻保證最新 2018/10/10 ~ )
對於 linux 編譯安裝參照下面腳本
# 開發環境安裝 sudo apt install gcc gdb autogen autoconf # jemalloc 安裝 cd wget https://github.com/jemalloc/jemalloc/releases/download/5.1.0/jemalloc-5.1.0.tar.bz2 tar -jxvf jemalloc-5.1.0.tar.bz2 cd jemalloc-5.1.0 sh autogen.sh make -j4 sudo make install sudo ldconfig cd rm -rf jemalloc-5.1.0 jemalloc-5.1.0.tar.bz2
當 jemalloc 構建好了. 設計 alloc 層引入到 structc 框架中, 用戶取代系統 malloc...
alloc.h - https://github.com/wangzhione/structc/blob/master/structc/system/alloc.h
#ifndef _H_ALLOC #define _H_ALLOC #include <stdlib.h> #include <string.h> // :) 高效內存分配, 莫名傷感 ~ // _MSC_VER -> Winds CL // __GNUC__ -> Linux GCC // #ifdef _MSC_VER // // CPU 檢測 x64 or x86 // ISX64 defined 表示 x64 不然 x86 // # if defined(_M_ARM64) || defined(_M_X64) # define ISX64 # endif // // _M_PPC 爲 PowerPC 平臺定義, 如今已不支持 // so winds 能夠認爲都是小端平臺 // # if defined(_M_PPC) # define ISBENIAN # endif #elif __GNUC__ # if defined(__x86_64__) # define ISX64 # endif // // 大小端檢測 : ISBENIAN defined 表示大端 // # if defined(__BIG_ENDIAN__) || defined(__BIG_ENDIAN_BITFIELD) # define ISBENIAN # endif #else # error BUILD ( ̄︶ ̄) S #endif // OFF_ALLOC - 關閉全局 free / malloc 配置 #ifndef OFF_ALLOC # undef free # define free free_ # undef strdup # define strdup strdup_ # undef malloc # define malloc malloc_ # undef calloc # define calloc calloc_ # undef realloc # define realloc realloc_ #endif//OFF_ALLOC // // free_ - free 包裝函數 // ptr : 內存首地址 // return : void // extern void free_(void * ptr); // // malloc_ - malloc 包裝, 封裝一些特殊業務 // size : 分配的內存字節 // return : 返回可以使用的內存地址. // extern void * malloc_(size_t size); // // strdup_ - strdup 包裝函數 // s : '\0' 結尾 C 字符串 // return : 拷貝後新的 C 字符串 // extern char * strdup_(const char * s); // // calloc_ - calloc 包裝, 封裝一些特殊業務 // num : 數量 // size : 大小 // return : 返回可用內存地址, 而且置0 // extern void * calloc_(size_t num, size_t size); // // realloc_ - realoc 包裝函數, 封裝一些特殊業務 // ptr : 內存首地址, NULL 等同於 malloc // size : 從新分配的內存大小 // return : 返回從新分配好的新地址內容 // extern void * realloc_(void * ptr, size_t size); #endif//_H_STDEXIT
alloc.c - https://github.com/wangzhione/structc/blob/master/structc/system/alloc.c
#include <stdio.h> #define OFF_ALLOC #include "alloc.h" #define JEMALLOC_NO_DEMANGLE #include <jemalloc/jemalloc.h> // // free_ - free 包裝函數 // ptr : 內存首地址 // return : void // inline void free_(void * ptr) { je_free(ptr); } // 簡單內存不足檢測處理 static inline void * mcheck(void * ptr, size_t size) { if (NULL == ptr) { fprintf(stderr, "out of memory trying to allocate %zu\n", size); fflush(stderr); abort(); } return ptr; } // // malloc_ - malloc 包裝, 封裝一些特殊業務 // size : 分配的內存字節 // return : 返回可以使用的內存地址. // inline void * malloc_(size_t size) { void * ptr = je_malloc(size); return mcheck(ptr, size); } // // strdup_ - strdup 包裝函數 // s : '\0' 結尾 C 字符串 // return : 拷貝後新的 C 字符串 // inline char * strdup_(const char * s) { if (s) { size_t n = strlen(s) + 1; char * ptr = malloc_(n); return memcpy(ptr, s, n); } return NULL; } // // calloc_ - calloc 包裝, 封裝一些特殊業務 // num : 數量 // size : 大小 // return : 返回可用內存地址, 而且置0 // inline void * calloc_(size_t num, size_t size) { void * ptr = je_calloc(num, size); return mcheck(ptr, size); } // // realloc_ - realoc 包裝函數, 封裝一些特殊業務 // ptr : 內存首地址, NULL 等同於 malloc // size : 從新分配的內存大小 // return : 返回從新分配好的新地址內容 // inline void * realloc_(void * ptr, size_t size) { void * ntr = je_realloc(ptr, size); return mcheck(ntr, size); }
包裝了一層. 從 alloc.h 中 OFF_ALLOC 宏能夠看出, 具有支持插拔能力 ~
2. libuv - https://github.com/libuv/libuv
libuv 用 c 寫的高性能單線程網絡 io 庫. 但願經過它來支撐網絡層. winds 編譯靜態庫
參照 libuv 項目首頁燥起來就行. 其中 gyp 安裝了這個版本, 其它隨波逐流 ~
gyp - https://github.com/adblockplus/gyp
linux 編譯安裝腳本
# libuv 安裝 cd wget https://github.com/libuv/libuv/archive/v1.23.1.zip unzip v1.23.1.zip cd libuv-1.23.1 sh autogen.sh ./configure make -j4 sudo make install sudo ldconfig cd # # 注意 uv 頭文件, 所有導入到系統 include 目錄下面 # rm -rf libuv-1.23.1 v1.23.1.zip
注意要將編譯後 include 完整拷貝到安裝目錄 include下. 這樣 uv 頭文件全, 往後會用到.
libuv 開箱即用, 不太須要什麼基礎封裝.
3. pthread - https://github.com/GerHobbelt/pthread-win32
這是最後那半個, 爲 winds 引入 POSIX thread 模型. 編譯起來很簡單(前提我們 VS 玩的熟).
扯點閒篇. linux 和 winds 相輔相成, 對立而統一. 一個是一切從頭碼, 一個開始就已經註冊將來.
描述比較粗, 但大概這意思. (兩個都不 eary, 玩好久纔敢入門見岳父岳母) . 這裏包裝了一層
thread.h - https://github.com/wangzhione/structc/blob/master/structc/system/thread.h
#ifndef _H_THREAD #define _H_THREAD #include "struct.h" #include <pthread.h> #include <semaphore.h> // // pthread_end - 等待啓動線程結束 // tid : 線程id // return : void // inline void pthread_end(pthread_t tid) { pthread_join(tid, NULL); } // // pthread_run - 異步啓動線程 // id : &tid 線程id地址 // frun : 運行的主體 // arg : 運行參數 // return : 返回線程構建結果, 0 is success // #define pthread_run(id, frun, arg) \ pthread_run_(&(id), (node_f)(frun), (void *)(intptr_t)(arg)) inline int pthread_run_(pthread_t * id, node_f frun, void * arg) { return pthread_create(id, NULL, (start_f)frun, arg); } // // pthread_async - 異步啓動分離線程 // frun : 運行的主體 // arg : 運行參數 // return : 返回 0 is success // #define pthread_async(frun, arg) \ pthread_async_((node_f)(frun), (void *)(intptr_t)(arg)) inline int pthread_async_(node_f frun, void * arg) { int ret; pthread_t tid; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); ret = pthread_create(&tid, &attr, (start_f)frun, arg); pthread_attr_destroy(&attr); return ret; } #endif//_H_THREAD
利用現代編譯器兼容性構建了 pthread 兩種啓動宏, 後續寫 pthread create 相關代碼會駕輕就熟!
到此咱們大一統治線程模型就定下來了. 還順帶引出了一個很重要輔助頭文件.
struct.h - https://github.com/wangzhione/structc/blob/master/structc/struct/struct.h
#ifndef _H_STRUCT #define _H_STRUCT #include <math.h> #include "alloc.h" #include <ctype.h> #include <float.h> #include <stdio.h> #include <errno.h> #include <assert.h> #include <stdarg.h> #include <stdint.h> #include <stddef.h> #include <limits.h> #include <stdbool.h> #include <inttypes.h> // // enum Flag int - 函數返回值全局狀態碼 // >= 0 標識 Success 狀態, < 0 標識 Error 狀態 // enum { SBase = +0, // 正確基礎類型 EBase = -1, // 錯誤基礎類型 EParam = -2, // 輸入參數錯誤 EFd = -3, // 文件打開失敗 EClose = -4, // 文件操做關閉 EAccess = -5, // 沒有操做權限 EAlloc = -6, // 內存操做錯誤 EParse = -7, // 協議解析錯誤 ESmall = -8, // 太小基礎錯誤 EBig = -9, // 過大基礎錯誤 ETimeout = -10, // 操做超時錯誤 }; // // DCODE - DEBUG 模式下的測試宏 // DCODE({ // puts("debug start..."); // }); // #ifndef DCODE # ifdef _DEBUG # define DCODE(code) do code while(0) # else # define DCODE(code) # endif // ! _DEBUG #endif // ! DCODE // // icmp_f - 比較行爲的類型 // : int add_cmp(const void * now, const void * node) // typedef int (* icmp_f)(); // // vnew_f - 根據規則構建對象 // : void * rtree_new(void * node) // typedef void * (* vnew_f)(); // // node_f - 銷燬當前對象節點 // : void list_die(void * node); // typedef void (* node_f)(void * node); // // start_f - pthread create func // : int * run(int * arg) // typedef void * (* start_f)(void * arg); // // each_f - each 循環操做, arg 外部參數, node 是內部結點 // : int dict_echo(struct dict * node, void * arg) { return 0; } // typedef int (* each_f)(void * node, void * arg); // // CERR - 打印錯誤信息 // EXIT - 打印錯誤信息, 並 exit // IF - 條件判斷異常退出的輔助宏 // #define CERR(fmt, ...) \ fprintf(stderr, "[%s:%s:%d][%d:%s]" fmt "\n", \ __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #define EXIT(fmt, ...) \ do { \ CERR(fmt, ##__VA_ARGS__); \ exit(EXIT_FAILURE); \ } while(0) #define IF(cond) \ if ((cond)) EXIT(#cond) // // RETURN - 打印錯誤信息, 並 return 返回指定結果 // val : return的東西, 當須要 return void; 時候填 ',' 就過 or NIL // fmt : 雙引號包裹的格式化字符串 // ... : fmt中對應的參數 // return : val // #define RETURN(val, fmt, ...) \ do { \ CERR(fmt, ##__VA_ARGS__); \ return val; \ } while(0) #define NIL #define RETNIL(fmt, ...) \ RETURN(NIL , fmt, ##__VA_ARGS__) #define RETNUL(fmt, ...) \ RETURN(NULL, fmt, ##__VA_ARGS__) #endif//_H_STRUCT
做者嘗試寫 structc 項目時第一個源文件 : )
0.2 IDE 弱議
winds 沒得選, 最新最全的 visual studio best version 有才能統治一切. 這裏主要說
的是 linux 上面咱們的選擇. 最開始我是 vi + make + gcc + gdb 開發和編譯的.
Makefile - https://github.com/wangzhione/structc/blob/master/Makefile
# 編譯的目錄結構 # Release : make # Debug : make D=-D_DEBUG # Clean : make clean
make 是編譯發佈, make D=-D_DEBUG 是編譯 Debug, make clean 項目清理. 手工操做.
這樣搞對我都還好, 什麼都行.
但不妨更精進一步 [vi + make + gcc + gdb] -> [code + F5 + F10 + F11] 是否是更妙.
微軟做爲桌面軟件霸主, code(VSCode 簡稱)不用我多說, 不得不服. 那開搞
1. 安裝軟件
ubuntu best version
vscode
安裝好 vscode 後, 在其內部安裝插件 Microsoft C/C++ for Visual Studio Code
2. F1 -> Edit Configurations -> c_cpp_properties.json
設置以下內容和VS配置很類似
{ "configurations": [ { "name": "Linux", "includePath": [ "/usr/include/c++/7", "/usr/include/x86_64-linux-gnu/c++/7", "/usr/include/c++/7/backward", "/usr/lib/gcc/x86_64-linux-gnu/7/include", "/usr/local/include", "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed", "/usr/include/x86_64-linux-gnu", "/usr/include", "${workspaceRoot}", "${workspaceRoot}/structc/base", "${workspaceRoot}/structc/struct", "${workspaceRoot}/structc/system" ], "defines": [ "_DEBUG", "__GNUC__" ], "intelliSenseMode": "clang-x64", "browse": { "path": [ "/usr/include/c++/7", "/usr/include/x86_64-linux-gnu/c++/7", "/usr/include/c++/7/backward", "/usr/lib/gcc/x86_64-linux-gnu/7/include", "/usr/local/include", "/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed", "/usr/include/x86_64-linux-gnu", "/usr/include", "${workspaceRoot}" ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" }, "compilerPath": "/usr/bin/clang", "cStandard": "c11", "cppStandard": "c++17" } ], "version": 4 }
3. F5 -> launch.json
{ // 使用 IntelliSense 瞭解相關屬性。 // 懸停以查看現有屬性的描述。 // 欲瞭解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/Out/main.exe", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "preLaunchTask": "Debug", "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] }
4. F5 -> tasks.json
{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "type" : "shell", "label" : "Debug", "command" : "make D=-D_DEBUG" } ] }
此刻咱們就能夠 F5 搞起來 ~
兄弟們是否是很親切, 這麼複雜定製化項目均可以可視化調試. 還有誰 ~ 固然 IDE 有沒有
都好說, 難說的是你是否耐的下心去感悟技術的脈絡, 可不能學京東技術, 對開源缺失敬畏
之心, 技術不見得多厲害, 節操提早貸款沒了 ~ 最終成爲奧義之梗 : )
前言 - 不妨說點設計
進入 structc/structc 看到如下項目結構
wzhi@wzc:~/structc/structc$ tree -L 1 . ├── base ├── conf ├── main ├── README.md ├── struct ├── structc.vcxproj ├── structc.vcxproj.filters ├── structc.vcxproj.user ├── system └── test
base : 基礎接口封裝目錄
conf : 配置文件目錄
main : 主函數目錄
struct : 數據結構接口目錄
system : 系統庫包裝目錄
test : 單元測試目錄
1.0 main 主函數設計
wzhi@wzc:~/structc/structc/main$ tree
.
├── main.c
├── main_init.c
├── main_run.c
└── main_test.c
重點關注下入口 mian 主函數設計 main.c
#include "head.h" // // main - 程序的總入口, 從扯開始 // argc : 輸入參數個數 // argv : 參數集 // return : 返回程序退出的狀態碼 // int main(int argc, char * argv[]) { // // 初始化 ... ... // ... ... EXTERN_RUN(main_init); // // make D=-D_DEBUG // main_test 單元測試纔會啓動 // #ifdef _DEBUG EXTERN_RUN(main_test); #endif // ... // ... 啓動當前項目運行的主函數 // EXTERN_RUN(main_run, argc, argv); return EXIT_SUCCESS; }
其中 EXTERN_RUN 也很奇巧
// // EXTERN_RUN - 簡單的聲明, 並當即使用的宏 // ftest : 須要執行的函數名稱 // ... : 可變參數, 保留 // #define EXTERN_RUN(ftest, ...) \ do { \ extern void ftest(); \ ftest (__VA_ARGS__); \ } while(0)
越過聲明直接使用的宏聲明. structc 中 main 函數一共作了二件半事情.
main_init 初始化函數, main_run 業務運行函數, 還有半個 main_test 運行單元測試.
隨後咱們好好看看這個單元測試套路.
1.1 test 單元測試套路設計
先看看 main_test.c
#include "head.h" // // TEST - 用於單元測試函數, 執行並輸出運行時間 // ftest : 須要執行的測試函數名稱 // ... : 可變參數, 保留 // #define TEST(ftest, ...) \ do { \ extern void ftest(); \ clock_t $s = clock(); \ ftest (##__VA_ARGS__); \ double $e = (double)clock(); \ printf(STR(ftest)" run time:%lfs\n", ($e-$s)/CLOCKS_PER_SEC);\ } while(0) // // main_test - *_test is here run // return : void // void main_test(void) { // // 開始你的表演, 單元測試 // EXTERN_RUN(uv_tty_test); }
以上只給予了業務測試的能力. 其中 uv_tty_test 函數就是單元測試目錄下其中一個的單元測試函數體.
而咱們每一個業務測試函數, 順帶會建立一個同名的 .c 文件. 例如這裏是 uv_tty_test.c
#include <uv.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // // 測試 libuv tty 操做控制檯 // 輸出一段有顏色的文字 // void uv_tty_test(void) { uv_tty_t tty; uv_buf_t buf[3]; unsigned i, len = sizeof buf / sizeof *buf; uv_loop_t * loop = uv_default_loop(); // 目前只對 tty 控制檯處理 if (uv_guess_handle(1) != UV_TTY) { fprintf(stderr, "uv_guess_handle(1) != UV_TTY!\n"); exit(EXIT_FAILURE); } uv_tty_init(loop, &tty, 1, 0); uv_tty_set_mode(&tty, UV_TTY_MODE_NORMAL); // 開始發送消息 buf[0].base = "\033[46;37m"; buf[1].base = u8"(✿◡‿◡) 喵醬 ((●'-'●)) 比 ♥ 裏~ \n"; buf[2].base = "\033[0m"; for (i = 0; i < len; ++i) buf[i].len = (int)strlen(buf[i].base); uv_try_write((uv_stream_t *)&tty, buf, len); // 重置終端行爲 uv_tty_reset_mode(); uv_run(loop, UV_RUN_DEFAULT); }
思路很直白. 這些就是單元測試的真相... . 比較清晰的展現(業務是複雜中減負)
1.2 system 系統庫設計
這裏面設計東東很多, 只挑一些經典的供人看看. 代碼即註釋 ~
socket.h - https://github.com/wangzhione/structc/blob/master/structc/system/socket.h
#ifndef _H_SOCKET #define _H_SOCKET #include <time.h> #include <fcntl.h> #include "struct.h" #include <signal.h> #include <sys/types.h> #ifdef __GNUC__ #include <netdb.h> #include <unistd.h> #include <sys/un.h> #include <sys/uio.h> #include <sys/time.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/tcp.h> #include <sys/resource.h> // // This is used instead of -1, since the. by WinSock // On now linux EAGAIN and EWOULDBLOCK may be the same value // connect 連接中, linux 是 EINPROGRESS,winds 是 WSAEWOULDBLOCK // typedef int socket_t; #define INVALID_SOCKET (~0) #define SOCKET_ERROR (-1) // socket_init - 初始化 socket 庫初始化方法 inline void socket_init(void) { // 管道破裂, 忽略 SIGPIPE 信號 signal(SIGPIPE, SIG_IGN); } inline int socket_close(socket_t s) { return close(s); } // socket_set_block - 設置套接字是阻塞 // socket_set_nonblock - 設置套接字是非阻塞 inline int socket_set_block(socket_t s) { int mode = fcntl(s, F_GETFL, 0); return fcntl(s, F_SETFL, mode & ~O_NONBLOCK); } inline int socket_set_nonblock(socket_t s) { int mode = fcntl(s, F_GETFL, 0); return fcntl(s, F_SETFL, mode | O_NONBLOCK); } // socket_recv - 讀取數據 // socket_send - 寫入數據 inline int socket_recv(socket_t s, void * buf, int sz) { return (int)read(s, buf, sz); } inline int socket_send(socket_t s, const void * buf, int sz) { return (int)write(s, buf, sz); } #endif #ifdef _MSC_VER #include <ws2tcpip.h> #undef errno #define errno WSAGetLastError() #undef strerror #define strerror ((char * (*)(int))strerr) #undef EINTR #define EINTR WSAEINTR #undef EAGAIN #define EAGAIN WSAEWOULDBLOCK #undef EINPROGRESS #define EINPROGRESS WSAEWOULDBLOCK /* * WinSock 2 extension -- manifest constants for shutdown() */ #define SHUT_RD SD_RECEIVE #define SHUT_WR SD_SEND #define SHUT_RDWR SD_BOTH #define SO_REUSEPORT SO_REUSEADDR typedef SOCKET socket_t; typedef int socklen_t; // // gettimeofday - Linux sys/time.h 中獲得微秒時間實現 // tv : 返回結果包含秒數和微秒數 // tz : 包含的時區, winds 上這個變量沒有做用 // return : 默認返回 0 // extern int gettimeofday(struct timeval * tv, void * tz); // // strerr - linux 上替代 strerror, winds 替代 FormatMessage // error : linux 是 errno, winds 能夠是 WSAGetLastError() ... // return : system os 拔下來的提示常量字符串 // extern const char * strerr(int err); // socket_init - 初始化 socket 庫初始化方法 inline void socket_init(void) { WSADATA wsad; WSAStartup(WINSOCK_VERSION, &wsad); } // socket_close - 關閉上面建立後的句柄 inline int socket_close(socket_t s) { return closesocket(s); } // socket_set_block - 設置套接字是阻塞 // socket_set_nonblock - 設置套接字是非阻塞 inline int socket_set_block(socket_t s) { u_long mode = 0; return ioctlsocket(s, FIONBIO, &mode); } inline int socket_set_nonblock(socket_t s) { u_long mode = 1; return ioctlsocket(s, FIONBIO, &mode); } // socket_recv - 讀取數據 // socket_send - 寫入數據 inline int socket_recv(socket_t s, void * buf, int sz) { return sz > 0 ? recv(s, buf, sz, 0) : 0; } inline int socket_send(socket_t s, const void * buf, int sz) { return send(s, buf, sz, 0); } #endif // // 通用 sockaddr_in ipv4 地址 // typedef struct sockaddr_in sockaddr_t[1]; // socket_dgram - 建立 UDP socket // socket_stream - 建立 TCP socket inline socket_t socket_dgram(void) { return socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); } inline socket_t socket_stream(void) { return socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); } // socket_set_reuse - 開啓端口和地址複用 // socket_set_keepalive - 開啓心跳包檢測, 默認2h 5次 inline int socket_set_enable(socket_t s, int optname) { int ov = 1; return setsockopt(s, SOL_SOCKET, optname, (void *)&ov, sizeof ov); } inline int socket_set_reuse(socket_t s) { return socket_set_enable(s, SO_REUSEPORT); } inline int socket_set_keepalive(socket_t s) { return socket_set_enable(s, SO_KEEPALIVE); } // socket_set_rcvtimeo - 設置接收數據毫秒超時時間 // socket_set_sndtimeo - 設置發送數據毫秒超時時間 inline int socket_set_time(socket_t s, int ms, int optname) { struct timeval ov = { 0,0 }; if (ms > 0) { ov.tv_sec = ms / 1000; ov.tv_usec = (ms % 1000) * 1000; } return setsockopt(s, SOL_SOCKET, optname, (void *)&ov, sizeof ov); } inline int socket_set_rcvtimeo(socket_t s, int ms) { return socket_set_time(s, ms, SO_RCVTIMEO); } inline int socket_set_sndtimeo(socket_t s, int ms) { return socket_set_time(s, ms, SO_SNDTIMEO); } // socket_get_error - 獲得當前socket error 值, 0 表示正確, 其它都是錯誤 inline int socket_get_error(socket_t s) { int err; socklen_t len = sizeof(err); int r = getsockopt(s, SOL_SOCKET, SO_ERROR, (void *)&err, &len); return r < 0 ? errno : err; } // socket_recvfrom - recvfrom 接受函數 // socket_sendto - sendto 發送函數 inline int socket_recvfrom(socket_t s, void * buf, int len, int flags, sockaddr_t in) { socklen_t inlen = sizeof (sockaddr_t); return recvfrom(s, buf, len, flags, (struct sockaddr *)in, &inlen); } inline int socket_sendto(socket_t s, const void * buf, int len, int flags, const sockaddr_t to) { return sendto(s, buf, len, flags, (const struct sockaddr *)to, sizeof(sockaddr_t)); } // // socket_recvn - socket 接受 sz 個字節 // socket_sendn - socket 發送 sz 個字節 // extern int socket_recvn(socket_t s, void * buf, int sz); extern int socket_sendn(socket_t s, const void * buf, int sz); // socket_bind - bind 綁定函數 // socket_listen - listen 監聽函數 // socket_accept - accept 等接函數 // socket_connect - connect 連接函數 inline int socket_bind(socket_t s, const sockaddr_t addr) { return bind(s, (const struct sockaddr *)addr, sizeof(sockaddr_t)); } inline int socket_listen(socket_t s) { return listen(s, SOMAXCONN); } inline socket_t socket_accept(socket_t s, sockaddr_t addr) { socklen_t len = sizeof (sockaddr_t); return accept(s, (struct sockaddr *)addr, &len); } inline int socket_connect(socket_t s, const sockaddr_t addr) { return connect(s, (const struct sockaddr *)addr, sizeof(sockaddr_t)); } // // socket_binds - 端口綁定返回綁定好的 socket fd, 返回 INVALID_SOCKET or PF_INET PF_INET6 // socket_listens - 端口監聽返回監聽好的 socket fd. // extern socket_t socket_binds(const char * ip, uint16_t port, uint8_t protocol, int * family); extern socket_t socket_listens(const char * ip, uint16_t port, int backlog); // // socket_addr -socket_recv 經過 ip, port 構造 ipv4 結構 // extern int socket_addr(const char * ip, uint16_t port, sockaddr_t addr); // socket_pton - 返回 ip 串 inline char * socket_pton(sockaddr_t addr, char ip[INET_ADDRSTRLEN]) { return (char *)inet_ntop(AF_INET, &addr->sin_addr, ip, INET_ADDRSTRLEN); } // // socket_host - 經過 ip:port 串獲得 socket addr 結構 // host : ip:port 串 // addr : 返回最終生成的地址 // return : >= EBase 表示成功 // extern int socket_host(const char * host, sockaddr_t addr); // // socket_tcp - 建立 TCP 詳細套接字 // host : ip:port 串 // return : 返回監聽後套接字 // extern socket_t socket_tcp(const char * host); // // socket_udp - 建立 UDP 詳細套接字 // host : ip:port 串 // return : 返回綁定後套接字 // extern socket_t socket_udp(const char * host); // // socket_connects - 返回連接後的阻塞套接字 // host : ip:port 串 // return : 返回連接後阻塞套接字 // extern socket_t socket_connects(const char * host); // // socket_connectos - 返回連接後的非阻塞套接字 // host : ip:port 串 // ms : 連接過程當中毫秒數 // return : 返回連接後非阻塞套接字 // extern socket_t socket_connectos(const char * host, int ms); #endif//_H_SOCKET
socket.c - https://github.com/wangzhione/structc/blob/master/structc/system/socket.c
#include "socket.h" #ifdef _MSC_VER // // gettimeofday - Linux sys/time.h 中獲得微秒時間實現 // tv : 返回結果包含秒數和微秒數 // tz : 包含的時區, winds 上這個變量沒有做用 // return : 默認返回 0 // int gettimeofday(struct timeval * tv, void * tz) { struct tm m; SYSTEMTIME se; GetLocalTime(&se); m.tm_year = se.wYear - 1900; m.tm_mon = se.wMonth - 1; m.tm_mday = se.wDay; m.tm_hour = se.wHour; m.tm_min = se.wMinute; m.tm_sec = se.wSecond; m.tm_isdst = -1; // 不考慮夏令時 tv->tv_sec = (long)mktime(&m); tv->tv_usec = se.wMilliseconds * 1000; return 0; } #endif // // socket_recvn - socket 接受 sz 個字節 // socket_sendn - socket 發送 sz 個字節 // int socket_recvn(socket_t s, void * buf, int sz) { int r, n = sz; while (n > 0) { r = recv(s, buf, n, 0); if (r == 0) break; if (r == SOCKET_ERROR) { if (errno == EINTR) continue; return SOCKET_ERROR; } n -= r; buf = (char *)buf + r; } return sz - n; } int socket_sendn(socket_t s, const void * buf, int sz) { int r, n = sz; while (n > 0) { r = send(s, buf, n, 0); if (r == 0) break; if (r == SOCKET_ERROR) { if (errno == EINTR) continue; return SOCKET_ERROR; } n -= r; buf = (char *)buf + r; } return sz - n; } // // socket_addr - 經過 ip, port 構造 ipv4 結構 // int socket_addr(const char * ip, uint16_t port, sockaddr_t addr) { addr->sin_family = AF_INET; addr->sin_port = htons(port); addr->sin_addr.s_addr = inet_addr(ip); if (addr->sin_addr.s_addr == INADDR_NONE) { struct hostent * host = gethostbyname(ip); if (!host || !host->h_addr) return EParam; // 嘗試一種, 默認 ipv4 memcpy(&addr->sin_addr, host->h_addr, host->h_length); } memset(addr->sin_zero, 0, sizeof addr->sin_zero); return SBase; } // // socket_binds - 端口綁定返回綁定好的 socket fd, 返回 INVALID_SOCKET or PF_INET PF_INET6 // socket_listens - 端口監聽返回監聽好的 socket fd. // socket_t socket_binds(const char * ip, uint16_t port, uint8_t protocol, int * family) { socket_t fd; char ports[sizeof "65535"]; struct addrinfo * addr = NULL, hint = { 0 }; if (NULL == ip || *ip == '\0') ip = "0.0.0.0"; // default INADDR_ANY sprintf(ports, "%hu", port); hint.ai_family = AF_UNSPEC; if (protocol == IPPROTO_TCP) hint.ai_socktype = SOCK_STREAM; else { assert(protocol == IPPROTO_UDP); hint.ai_socktype = SOCK_DGRAM; } hint.ai_protocol = protocol; if (getaddrinfo(ip, ports, &hint, &addr)) return INVALID_SOCKET; fd = socket(addr->ai_family, addr->ai_socktype, 0); if (fd == INVALID_SOCKET) goto err_free; if (socket_set_reuse(fd)) goto err_close; if (bind(fd, addr->ai_addr, (int)addr->ai_addrlen)) goto err_close; // Success return ip family if (family) *family = addr->ai_family; freeaddrinfo(addr); return fd; err_close: socket_close(fd); err_free: freeaddrinfo(addr); return INVALID_SOCKET; } socket_t socket_listens(const char * ip, uint16_t port, int backlog) { socket_t fd = socket_binds(ip, port, IPPROTO_TCP, NULL); if (INVALID_SOCKET != fd && listen(fd, backlog)) { socket_close(fd); return INVALID_SOCKET; } return fd; } // host_parse - 解析 host 內容 static int host_parse(const char * host, char ip[BUFSIZ], uint16_t * pprt) { int port = 0; char * st = ip; if (!host || !*host || *host == ':') strcpy(ip, "0.0.0.0"); else { char c; // 簡單檢查字符串是否合法 size_t n = strlen(host); if (n >= BUFSIZ) RETURN(EParam, "host err %s", host); // 尋找分號 while ((c = *host++) != ':' && c) *ip++ = c; *ip = '\0'; if (c == ':') { if (n > ip - st + sizeof "65535") RETURN(EParam, "host port err %s", host); port = atoi(host); // 有些常識數字, 不必定是魔法 ... :) if (port <= 1024 || port > 65535) RETURN(EParam, "host port err %s, %d", host, port); } } *pprt = port; return SBase; } // // socket_host - 經過 ip:port 串獲得 socket addr 結構 // host : ip:port 串 // addr : 返回最終生成的地址 // return : >= EBase 表示成功 // int socket_host(const char * host, sockaddr_t addr) { uint16_t port; char ip[BUFSIZ]; if (host_parse(host, ip, &port) < SBase) return EParam; // 開始構造 addr if (NULL == addr) { sockaddr_t nddr; return socket_addr(ip, port, nddr); } return socket_addr(ip, port, addr); } // // socket_tcp - 建立 TCP 詳細套接字 // host : ip:port 串 // return : 返回監聽後套接字 // socket_t socket_tcp(const char * host) { uint16_t port; char ip[BUFSIZ]; if (host_parse(host, ip, &port) < SBase) return EParam; return socket_listens(ip, port, SOMAXCONN); } // // socket_udp - 建立 UDP 詳細套接字 // host : ip:port 串 // return : 返回綁定後套接字 // socket_t socket_udp(const char * host) { uint16_t port; char ip[BUFSIZ]; if (host_parse(host, ip, &port) < SBase) return EParam; return socket_binds(ip, port, IPPROTO_UDP, NULL); } // // socket_connects - 返回連接後的阻塞套接字 // host : ip:port 串 // return : 返回連接後阻塞套接字 // socket_t socket_connects(const char * host) { sockaddr_t addr; socket_t s = socket_stream(); if (INVALID_SOCKET == s) { RETURN(s, "socket_stream is error"); } // 解析配置成功後嘗試連接 if (socket_host(host, addr) >= SBase) if (socket_connect(s, addr) >= SBase) return s; socket_close(s); RETURN(INVALID_SOCKET, "socket_connects %s", host); } // // socket_connecto - connect 超時連接, 返回非阻塞 socket // static int socket_connecto(socket_t s, const sockaddr_t addr, int ms) { int n, r; struct timeval to; fd_set rset, wset, eset; // 仍是阻塞的connect if (ms < 0) return socket_connect(s, addr); // 非阻塞登陸, 先設置非阻塞模式 r = socket_set_nonblock(s); if (r < SBase) return r; // 嘗試鏈接, connect 返回 -1 而且 errno == EINPROGRESS 表示正在創建連接 r = socket_connect(s, addr); // connect 連接中, linux 是 EINPROGRESS,winds 是 WSAEWOULDBLOCK if (r >= SBase || errno != EINPROGRESS) { socket_set_block(s); return r; } // 超時 timeout, 直接返回結果 EBase = -1 錯誤 if (ms == 0) { socket_set_block(s); return EBase; } FD_ZERO(&rset); FD_SET(s, &rset); FD_ZERO(&wset); FD_SET(s, &wset); FD_ZERO(&eset); FD_SET(s, &eset); to.tv_sec = ms / 1000; to.tv_usec = (ms % 1000) * 1000; n = select((int)s + 1, &rset, &wset, &eset, &to); // 超時直接滾 if (n <= 0) { socket_set_block(s); return EBase; } // 當鏈接成功時候,描述符會變成可寫 if (n == 1 && FD_ISSET(s, &wset)){ socket_set_block(s); return SBase; } // 當鏈接創建遇到錯誤時候, 描述符變爲便可讀又可寫 if (FD_ISSET(s, &eset) || n == 2) { socklen_t len = sizeof n; // 只要最後沒有 error 那就 連接成功 if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char *)&n, &len) && !n) r = SBase; } socket_set_block(s); return r; } // // socket_connectos - 返回連接後的非阻塞套接字 // host : ip:port 串 // ms : 連接過程當中毫秒數 // return : 返回連接後非阻塞套接字 // socket_t socket_connectos(const char * host, int ms) { sockaddr_t addr; socket_t s = socket_stream(); if (INVALID_SOCKET == s) { RETURN(s, "socket_stream is error"); } // 解析配置成功後嘗試連接 if (socket_host(host, addr) >= SBase) if (socket_connecto(s, addr, ms) >= SBase) return s; socket_close(s); RETURN(INVALID_SOCKET, "socket_connectos %s", host); }
哪怕 winds, 設計思路也是仿照 linux socket 套路. 構建 socket 接口, 但願上面代碼能說明什麼是少林
拳法, 千錘百練. 後面 base struct system 代碼量很多, 難一一說. 喜歡的能夠後續抄襲一次. (我也是
抄襲別人而走入了編程的世界, 瞭解這分形的人生吧)
正文 - 風吹草動
經過引言和前言認識了 structc 是什麼樣項目, 項目構建, 代碼風格等. 這裏準備說一下設計 structc
項目初衷. 好久前寫 C 代碼, 發現數據結構肯定後, 基本整個脈絡就定下了. 因此想用 C 構建一個通用
簡單的數據結構庫. 因此有了這個項目.
扯一點, 瞭解 structc 項目後可以爲怎樣技能加點. 例如學完 struct 目錄, 數據結構能夠輕鬆結課.
抄完 system 操做系統能夠結課. base 清楚後, 框架中間件設計也算入門了. 瞭解總體佈局後, 實戰中的
腳手架設計也不過如此. 但缺點也有, 見效慢. C 太老了, 想經過 C 看清編程源頭, 不下時間是不現實的,
幸運的是最終收穫 -> 哈哈 -> 怎麼脫髮又嚴重了 ....
好比 atom.h - https://github.com/wangzhione/structc/blob/master/structc/system/atom.h
#ifndef _H_ATOM #define _H_ATOM #include "atomic.h" // // atom_t 自旋鎖類型 // [static] atom_t o = 0; // atom_lock(o); // - One Man RPG // atom_unlock(o); // typedef volatile long atom_t; // atom_acquire - 維護優化後讀寫代碼不在其前 #define atom_acquire() atomic_fence(ATOMIC_ACQUIRE) // atom_release - 維護優化後讀寫代碼不在其後 #define atom_release() atomic_fence(ATOMIC_RELEASE) // atom_seq_cst - 維護優化後讀寫代碼先後不動 #define atom_seq_cst() atomic_fence(ATOMIC_SEQ_CST) #ifdef __GNUC__ #define atom_trylock(o) (!__sync_lock_test_and_set(&(o), 1)) #define atom_lock(o) while(__sync_lock_test_and_set(&(o), 1)) #define atom_unlock(o) __sync_lock_release(&(o)) // 內存屏障, 維持代碼順序 #define atom_sync() __sync_synchronize() // v += a ; return v; #define atom_add(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)) // v &= a; return v; #define atom_and(v, a) __sync_and_and_fetch(&(v), (a)) // return ++v; #define atom_inc(v) __sync_add_and_fetch(&(v), 1) // return --v; #define atom_dec(v) __sync_sub_and_fetch(&(v), 1) // bool b = v == c; b ? v=a : ; return b; #define atom_cas(v, c, a) __sync_bool_compare_and_swap(&(v), (c), (a)) #endif #ifdef _MSC_VER #include <intrin.h> #include <intrin0.h> /* Interlocked intrinsic mapping for _nf/_acq/_rel */ #if defined(_M_ARM) || defined(_M_ARM64) #define _ACQUIRE(x) ATOMIC_CONCAT(x, _acq) #else /* defined(_M_ARM) || defined(_M_ARM64) */ #define _ACQUIRE(x) x #endif /* defined(_M_ARM) || defined(_M_ARM64) */ #define atom_trylock(o) (!_ACQUIRE(_interlockedbittestandset)(&(o), 0)) #define atom_lock(o) while(_ACQUIRE(_interlockedbittestandset)(&(o), 0)) inline void store_release(atom_t * x) { /* store _Value atomically with release memory order */ #if defined(_M_ARM) || defined(_M_ARM64) __dmb(0xB /* _ARM_BARRIER_ISH or _ARM64_BARRIER_ISH*/); __iso_volatile_store32((volatile int *)x, 0); #else _ReadWriteBarrier(); *x = 0; #endif } #define atom_unlock(o) store_release(&(o)) // 保證代碼優化後不亂序執行 #define atom_sync() MemoryBarrier() // v 和 a 都是 long 這樣數據 #define atom_add(v, a) InterlockedAdd((volatile LONG *)&(v), (LONG)(a)) #define atom_set(v, a) InterlockedExchange((volatile LONG *)&(v), (LONG)(a)) #define atom_and(v, a) InterlockedAnd((volatile LONG *)&(v), (LONG)(a)) #define atom_inc(v) InterlockedIncrement((volatile LONG *)&(v)) #define atom_dec(v) InterlockedDecrement((volatile LONG *)&(v)) // // 對於 InterlockedCompareExchange(v, c, a) 等價於下面 // long tmp = v ; v == a ? v = c : ; return tmp; // // 我們的 atom_cas(v, c, a) 等價於下面 // long tmp = v ; v == c ? v = a : ; return tmp; // #define atom_cas(v, c, a) ((LONG)(c) == InterlockedCompareExchange((volatile LONG *)&(v), (LONG)(a), (LONG)(c))) #endif #endif//_H_ATOM
代碼在改中變的有味道, 有態度. 固然更歡迎同行給予補充, 共同提升進步 ~
畢竟錯誤是不免的 : )
後記 - 江湖再會
金子陵 - https://music.163.com/#/song?id=376994