structc 開源框架介紹

引言 - 一切纔剛剛開始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

    按照規律改 program 項目生成 和 preLaunchTask 前置任務
{
    // 使用 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

創建下面任務, 目前只使用了 Debug
{
    // 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

相關文章
相關標籤/搜索