C修真之旅五 常見組件上三路[轉(zhuǎn)]

第6章-武技-常見組件上三路

時(shí)間過得真快, 武技修煉完畢, 修真之旅就過去一半了. 
武技本是金丹期選手在妖魔大戰(zhàn)中招招實(shí)戰(zhàn)的經(jīng)驗(yàn). 原始而奏效, 飛沙走石, 熱浪滾滾.
說的也許很短, 但未名傳說等你隨意去亂寫 ~
此刻只帶你穿梭那種場景, 感受一瞬間的震耳欲動(dòng), 山河破碎, 天地不仁 ~

帶好你的劍, 
那年學(xué)的華山劍法 ~
此刻上路吧 ---:o

6.1 傳說中的線程池

線程池是很古老的舊話題. 討論的很多, 深入的不多. 也就在那些基礎(chǔ)庫中才能見到這種
有點(diǎn)精妙的技巧. 這里也會(huì)隨大流深入簡述一種高效且控制性強(qiáng)的一種線程池實(shí)現(xiàn). 先引入
一個(gè)概念, 驚群. 簡單舉個(gè)例子. 春天來了, 公園出現(xiàn)了很多麻雀. 而你恰巧有一個(gè)玉米粒. 
扔出去, 立馬無數(shù)麻雀過來爭搶. 而最終只有一只麻雀得到了. 而那些沒有搶到的麻雀很累
....... 編程中驚群, 也是個(gè)很古老的編程話題了. 服務(wù)器框架開發(fā)中很有機(jī)會(huì)遇到. 有興
趣的可以自行搜索, 多數(shù)介紹的解決方案質(zhì)量非常高. 而我們今天只討論線程池中驚群現(xiàn)象.
采用的POSIX跨平臺的線程庫 pthread. 
extern int __cdecl pthread_cond_signal (pthread_cond_t * cond);
上面 pthread 接口就是線程池中出現(xiàn)驚群來源. 它會(huì)激活 pthread_cond_wait 等待態(tài)
一個(gè)或多個(gè)線程. 同樣這里解決驚群的方式很普通也很實(shí)在, 定向激活, 每個(gè)實(shí)際運(yùn)行線程
對象都有自己的條件變量. 那么每次只要激活需要激活的線程變量就可以了. 驚群的現(xiàn)象自
然就避免了.

開始.jpg

6.1.1 線程池接口設(shè)計(jì)

#ifndef _H_SIMPLEC_SCTHREADS
#define _H_SIMPLEC_SCTHREADS

#include "schead.h"

//
// 這是個(gè)簡易的線程池的庫.
//

typedef struct threads * threads_t;

//
// threads_create - 創(chuàng)建一個(gè)線程池處理對象
// return   : 返回創(chuàng)建好的線程池對象, NULL表示失敗
//
extern threads_t threads_create(void);

//
// threads_delete - 異步銷毀一個(gè)線程池對象
// pool     : 線程池對象
// return   : void
//
extern void threads_delete(threads_t pool);

//
// threads_insert - 線程池中添加要處理的任務(wù)
// pool     : 線程池對象
// run      : 運(yùn)行的執(zhí)行題
// arg      : run的參數(shù)
// return   : void
//
extern void threads_insert_(threads_t pool, node_f run, void * arg);
#define threads_insert(pool, run, arg) threads_insert_(pool, (node_f)run, (void *)(intptr_t)arg)

#endif // !_H_SIMPLEC_SCTHREADS
定義了不完全類型 threads_t, 創(chuàng)建銷毀添加等行為. 其中使用了個(gè)函數(shù)宏的技巧. 用于
去掉警告. 也許 n 年后, 這類 void (*)(long *) convert void (*)(void *) 不再彈
出 warning. 那 threads_insert 就可以壽終正寢了. 其中對于 (intptr_t)arg 的意圖
讓其可以塞入 int 變量, 因?yàn)樵?x64 上 int 是4字節(jié)強(qiáng)轉(zhuǎn)8字節(jié)的指針變量會(huì)被彈出警告.
到這線程池接口設(shè)計(jì)部分已經(jīng)完工. 代碼比文字好理解, 文字可以瞎寫, 但是代碼的真相缺
早已經(jīng)注定. 設(shè)計(jì)的不好, 那將什么都不是 ......

6.1.2 線程池部分實(shí)現(xiàn)

線程池解決的問題是避免創(chuàng)建過多線程對象, 加重操作系統(tǒng)的負(fù)擔(dān). 從而換了一種方式, 將
多個(gè)線程業(yè)務(wù)轉(zhuǎn)成多個(gè)任務(wù)被固定的線程來回處理. 這個(gè)思路是不是很巧妙. 先說說線程池
容易的實(shí)現(xiàn)部分, 來看任務(wù)結(jié)構(gòu)的設(shè)計(jì):
// 任務(wù)鏈表 結(jié)構(gòu) 和 構(gòu)造
struct job {
    struct job * next;  // 指向下一個(gè)任務(wù)結(jié)點(diǎn)
    node_f run;         // 任務(wù)結(jié)點(diǎn)執(zhí)行的函數(shù)體
    void * arg; 
};

static inline struct job * _job_new(node_f run, void * arg) {
    struct job * job = malloc(sizeof(struct job));
    if (NULL == job)
        CERR_EXIT("malloc sizeof(struct job) is error!");
    job->next = NULL;
    job->run = run;
    job->arg = arg;
    return job;
}
內(nèi)存處理方式很直接, 申請失敗直接退出. 更加粗暴的方式是調(diào)用 abort(). 強(qiáng)制退出.
但實(shí)際開發(fā)中還真沒遇到過內(nèi)存不足(拋開測試). 假如一個(gè)系統(tǒng)真的內(nèi)存不足了, 可能一
切都是未知. 繼續(xù)分析線程池結(jié)構(gòu), 線程處理對象和線程池對象結(jié)構(gòu)如下:
// 線程結(jié)構(gòu)體, 每個(gè)線程一個(gè)信號量, 定點(diǎn)觸發(fā)
struct thread {
    struct thread * next;   // 下一個(gè)線程對象
    bool wait;              // true 表示當(dāng)前線程被掛起
    pthread_t tid;          // 當(dāng)前線程id
    pthread_cond_t cond;    // 線程條件變量
};

// 定義線程池(線程集)定義
struct threads {
    size_t size;            // 線程池大小, 最大線程結(jié)構(gòu)體數(shù)量
    size_t curr;            // 當(dāng)前線程池中總的線程數(shù)
    size_t idle;            // 當(dāng)前線程池中空閑的線程數(shù)
    volatile bool cancel;   // true表示當(dāng)前線程池正在 delete
    pthread_mutex_t mutx;   // 線程互斥量
    struct thread * thrs;   // 線程結(jié)構(gòu)體對象集
    struct job * head;      // 線程任務(wù)鏈表的鏈頭, 隊(duì)列結(jié)構(gòu)
    struct job * tail;      // 線程任務(wù)隊(duì)列的表尾, 后插入后執(zhí)行
};
上面可以找出每個(gè) struct thread 線程運(yùn)行對象中, 都有個(gè) pthread_cont_t 條件變量
. 這就是定向激活的關(guān)鍵. struct threads::cancel 用于標(biāo)識當(dāng)前線程池是否在銷毀階段
. 來避免使用 pthread_cancel + pthread_cleanup_push 和 pthread_cleanup_pop ,
這類有風(fēng)險(xiǎn)的設(shè)計(jì). 上面兩個(gè)結(jié)構(gòu)衍生了幾個(gè)輔助行為操作如下 :
// 根據(jù) cond 內(nèi)存地址熟悉, 刪除 pool->thrs 中指定數(shù)據(jù)
static void _threads_del(struct threads * pool, pthread_cond_t * cond) {
    struct thread * head = pool->thrs, * front = NULL;
    
    while (head) {
        // 找見了, 否則開始記錄前置位置
        if (cond == &head->cond)
            break;
        front = head;
        head = head->next;
    }

    if (head) {
        if (front)
            front->next = head->next;
        else
            pool->thrs = head->next;

        // head 結(jié)點(diǎn)就是我們要找的
        pthread_cond_destroy(&head->cond);
        free(head);
    }
}

// 找到線程 tid 對應(yīng)的條件變量地址
static struct thread * _threads_get(struct threads * pool, pthread_t tid) {
    struct thread * head = pool->thrs;
    while (head) {
        if (pthread_equal(tid, head->tid))
            return head;
        head = head->next;
    }
    return NULL;
}

// 找到空閑的線程, 并返回起信號量 
static pthread_cond_t * _threads_getcont(struct threads * pool) {
    struct thread * head = pool->thrs;
    while (head) {
        if (head->wait)
            return &head->cond;
        head = head->next;
    }
    return NULL;
}
對于 struct threads 結(jié)構(gòu)中 struct job * head, * tail; 是個(gè)待處理的任務(wù)隊(duì)列.
struct thread * thrs; 鏈接所有線程對象的鏈表. 線程池對象中共用 struct threads 
中 mutex 一個(gè)互斥量. 希望還記得前面數(shù)據(jù)結(jié)構(gòu)部分扯的, 鏈表是 C結(jié)構(gòu)中基礎(chǔ)的內(nèi)丹, 
所有代碼都是或多或少圍繞它這個(gè)結(jié)構(gòu). 要在勤磨練中熟悉提高, 對于剛學(xué)習(xí)的人. 上面代
碼其實(shí)和業(yè)務(wù)代碼沒啥區(qū)別, 創(chuàng)建銷毀添加查找等. 前戲營造的估計(jì)夠了, 現(xiàn)在開搞其它接
口簡單實(shí)現(xiàn)代碼 :
// 開啟的線程數(shù)
#define _UINT_THREADS       (4u)

threads_t 
threads_create(void) {
    struct threads * pool = calloc(1, sizeof(struct threads));
    if (NULL == pool) {
        RETURN(NULL, "calloc sizeof(struct threads) is error!");
    }

    pool->size = _UINT_THREADS;
    pthread_mutex_init(&pool->mutx, NULL);

    return pool;
}
上面創(chuàng)建接口的實(shí)現(xiàn)代碼中, calloc 相比 malloc 多調(diào)用了 bzero 的相關(guān)置零清空操作. 
還有一個(gè)釋放資源函數(shù). 設(shè)計(jì)意圖允許創(chuàng)建多個(gè)線程池(但是沒有必要). 請看下面優(yōu)雅的銷
毀操作:
void 
threads_delete(threads_t pool) {
    struct job * head;
    struct thread * thrs;

    if (!pool || pool->cancel) 
        return;
    // 標(biāo)識當(dāng)前線程池正在銷毀過程中
    pool->cancel = true;

    pthread_mutex_lock(&pool->mutx);

    // 先來釋放任務(wù)列表
    head = pool->head;
    while (head) {
        struct job * next = head->next;
        free(head);
        head = next;
    }
    pool->head = pool->tail = NULL;

    pthread_mutex_unlock(&pool->mutx);

    // 再來銷毀每個(gè)線程
    thrs = pool->thrs;
    while (thrs) {
        struct thread * next = thrs->next;
        // 激活每個(gè)線程讓其主動(dòng)退出
        pthread_cond_signal(&thrs->cond);
        pthread_join(thrs->tid, NULL);
        thrs = next;
    }

    // 銷毀自己
    pthread_mutex_destroy(&pool->mutx);
    free(pool);
}
用到很多 pthread api. 不熟悉的多搜索, 多做筆記. 不懂多了, 說明提升功力的機(jī)會(huì)來了.
對于上面釋放函數(shù)先監(jiān)測銷毀標(biāo)識, 后競爭唯一互斥量, 競爭到后就開始釋放過程. 先清除任
務(wù)隊(duì)列并置空, 隨后解鎖. 再去準(zhǔn)備銷毀每個(gè)線程, 激活它并等待它退出. 最終清除鎖銷毀自
己. 優(yōu)雅結(jié)束了線程池的生成周期. 

如果不知道是不是真愛, 那就追求優(yōu)雅 ~
如果是真愛, 那么什么都不想要 ~

6.1.3 線程池核心部分

核心部分就兩個(gè)函數(shù), 一個(gè)是線程輪詢處理任務(wù)的函數(shù). 另一個(gè)是構(gòu)建線程池函數(shù). 線程輪詢
函數(shù)如下:
// 線程運(yùn)行的時(shí)候執(zhí)行函數(shù), 消費(fèi)者線程
static void _consumer(struct threads * pool) {
    int status;
    struct thread * thrd;
    pthread_cond_t * cond;
    pthread_t tid = pthread_self();
    pthread_mutex_t * mutx = &pool->mutx;

    pthread_mutex_lock(mutx);

    thrd = _threads_get(pool, tid);
    assert(thrd);
    cond = &thrd->cond;

    // 使勁循環(huán)的主體, 開始消費(fèi) or 沉睡
    while (!pool->cancel) {
        if (pool->head) {
            struct job * job = pool->head;
            pool->head = job->next;
            // 隊(duì)列尾置空監(jiān)測
            if (pool->tail == job)
                pool->tail = NULL;

            // 解鎖, 允許其它消費(fèi)者線程加鎖或生產(chǎn)者添加新任務(wù)
            pthread_mutex_unlock(mutx);

            // 回調(diào)函數(shù), 后面再去刪除任務(wù)
            job->run(job->arg);
            free(job);

            // 新的一輪開始, 需要重新加鎖
            pthread_mutex_lock(mutx);
            continue;
        }

        // job 已經(jīng)為empty , 開啟線程等待
        thrd->wait = true;
        ++pool->idle;

        // 開啟等待, 直到線程被激活
        status = pthread_cond_wait(cond, mutx);
        if (status < 0) {
            pthread_detach(tid);
            CERR("pthread_cond_wait error status = %zu.", status);
            break;
        }
        thrd->wait = false;
        --pool->idle;
    }

    // 到這里程序出現(xiàn)異常, 線程退出中, 先減少當(dāng)前線程
    --pool->curr;
    // 去掉這個(gè)線程鏈表pool->thrs中對應(yīng)的數(shù)據(jù)
    _threads_del(pool, cond);

    // 一個(gè)線程一把鎖, 退出中
    pthread_mutex_unlock(mutx);
}
對于消費(fèi)者線程 _consumer 函數(shù)運(yùn)行起來, 內(nèi)部出現(xiàn)異常進(jìn)入自銷毀操作 pthread_detach
. 外部讓其退出, 走 pthread_join 關(guān)聯(lián)接收. 

突然想起以前扯的一句話, 關(guān)于提升技術(shù)好辦法
1. 多看書
2. 多寫代碼, 多搜搜, 多問問
3. 多看別人的好代碼, 多臨摹源碼
4. 多創(chuàng)造, 多改進(jìn), 多實(shí)戰(zhàn)
等該明白的都明白了, 一切都是那樣容易, 那樣的美的時(shí)候. 就可以回家種田了. 哈哈 ~

到這里線程池是結(jié)束了, 不妨為其寫個(gè)測試代碼吧 ~
#include "scthreads.h"

// 測試開啟線程量集
#define _INT_THS (8192)

//全局計(jì)時(shí)器,存在鎖問題
static int _old;

//簡單的線程打印函數(shù)
static inline void _ppt(const char * str) {
    printf("%d => %s\n", ++_old, str);
}

//另一個(gè)線程測試函數(shù)
static inline void _doc(void * arg) {
    printf("p = %d, 技術(shù)不決定項(xiàng)目的成敗!我老大哭了\n", ++_old);
}

int main(void) {
    //創(chuàng)建線程池
    threads_t pool = threads_create();

    //添加任務(wù)到線程池中
    for (int i = 0; i<_INT_THS; ++i) {
        threads_insert(pool, _ppt, "你為你負(fù)責(zé)的項(xiàng)目拼命過嗎.流過淚嗎");
        threads_insert(pool, _doc, NULL);
    }

    //等待5s 再結(jié)束吧
    sh_msleep(5000);
    //清除當(dāng)前線程池資源, 實(shí)戰(zhàn)上線程池是常駐內(nèi)存,不要清除.
    threads_delete(pool);
    return EXIT_SUCCESS;
}
咱們費(fèi)了老大勁寫了個(gè)線程池, 9.9 業(yè)務(wù)基本都不會(huì)用到. 密集型業(yè)務(wù)目前修真界都流
行少量線程加輪詢消息隊(duì)列的方式處理, 下一個(gè)出場的主角登場了 ~

4.2 消息輪序器

系統(tǒng)開發(fā)中, 消息輪詢器基本上就是整個(gè)服務(wù)器調(diào)度處理的核心! 所有待處理的業(yè)務(wù)統(tǒng)
一封裝 push 進(jìn)去, 單獨(dú)線程異步 loop 去處理, 周而復(fù)始. 等同于守護(hù)門派安定的
無上大陣. 下面就帶大家寫個(gè)超酷炫的封魔大鎮(zhèn), 收獲門派一世繁華 ~

接口設(shè)計(jì)部分 scrunloop.h
#ifndef _H_SIMPLEC_SCRUNLOOP
#define _H_SIMPLEC_SCRUNLOOP

#include "struct.h"

typedef struct srl * srl_t;

//
// srl_create - 創(chuàng)建輪詢服務(wù)對象
// run      : 輪序處理每條消息體, 彈出消息體的時(shí)候執(zhí)行
// die      : srl_push msg 的銷毀函數(shù)
// return   : void 
//
srl_t srl_create_(node_f run, node_f die);
#define srl_create(run, die) srl_create_((node_f)(run), (node_f)(die))

//
// srl_delete - 銷毀輪詢對象,回收資源
// s        : 輪詢對象
// return   : void 
//
extern void srl_delete(srl_t srl);

//
// srl_push - 將消息壓入到輪詢器中
// s        : 輪詢對象
// msg      : 待加入的消息地址
// return   : void
// 
extern void srl_push(srl_t s, void * msg);

#endif // !_H_SIMPLEC_SCRUNLOOP
通過宏函數(shù) srl_create 啟動(dòng)一個(gè)消息輪序器, 需要注冊兩個(gè)函數(shù) run 和 die, 前者用于處
理每個(gè) push 進(jìn)來的消息, die 用戶 push 進(jìn)來消息的善后工作(銷毀清除). 這個(gè)類實(shí)現(xiàn)的非
常精簡. 直貼代碼, 比較有價(jià)值. 多感受其中的妙用, 真是, 小也能滿足你 

scrunloop.c

#include "mq.h"
#include "schead.h"
#include "scrunloop.h"
#include <semaphore.h>

// 具體輪詢器
struct srl {
    pthread_t id;           // 具體奔跑的線程
    sem_t blocks;           // 線程阻塞

    mq_t mq;                // 消息隊(duì)列
    node_f run;             // 每個(gè)消息都會(huì)調(diào)用 run(pop())
    node_f die;             // 每個(gè)消息體的善后工作
    volatile bool loop;     // true表示還在繼續(xù) 
    volatile bool wait;     // true表示當(dāng)前輪序器正在等待
};

static void * _srl_loop(struct srl * s) {
    while (s->loop) {
        void * pop = mq_pop(s->mq);
        if (NULL == pop) {
            s->wait = true;
            sem_wait(&s->blocks);
        } else {
            // 開始處理消息
            s->run(pop);
            s->die(pop);
        }
    }
    return s;
}

srl_t
srl_create_(node_f run, node_f die) {
    struct srl * s = malloc(sizeof(struct srl));
    assert(s && run);
    s->mq = mq_create();
    s->loop = true;
    s->run = run;
    s->die = die;
    s->wait = true;
    // 初始化 POSIX 信號量, 進(jìn)程內(nèi)線程共享, 初始值 0
    sem_init(&s->blocks, 0, 0);
    // 創(chuàng)建線程,并啟動(dòng)
    if (pthread_create(&s->id, NULL, (start_f)_srl_loop, s)) {
        mq_delete(s->mq, die);
        free(s);
        RETURN(NULL, "pthread_create create error !!!");
    }
    return s;
}

void
srl_delete(srl_t s) {
    if (s) {
        s->loop = false;
        sem_post(&s->blocks);
        // 等待線程結(jié)束, 然后退出
        pthread_join(s->id, NULL);
        sem_destroy(&s->blocks);
        mq_delete(s->mq, s->die);
        free(s);
    }
}

inline void 
srl_push(srl_t s, void * msg) {
    assert(s && msg);
    mq_push(s->mq, msg);
    if (s->wait) {
        s->wait = false;
        sem_post(&s->blocks);
    }
}
對于 struct srl::die 可以優(yōu)化, 支持為 NULL 操作, 或者添加函數(shù)行為. 但是總的而言,
語法糖層面支持的越好, 用起來是爽了, 多次爽之后那種退燒的感覺會(huì)更有意思. C 很難把所
有業(yè)務(wù)統(tǒng)一起來支持, 那樣的代碼臃腫和不敢維護(hù). 體會(huì)并學(xué)會(huì)那種 C 設(shè)計(jì)的思路, 大不了
為另一種業(yè)務(wù)寫個(gè)更加貼切的支持, 如果追求性能的話.

再分析會(huì), 假如是多線程環(huán)境 srl_push 觸發(fā)了并發(fā)操作, 相應(yīng)的 sem_post 會(huì)執(zhí)行多次
P 操作. 但 _srl_loop 是單線程輪詢處理的, 只會(huì)觸發(fā)對映次的 sem_wait V 操作. 所以
push 不加鎖不影響業(yè)務(wù)正確性. 而且 sem_wait 是通用層面阻塞性能最好的選擇. 這些都是
高效的保證. 武技修煉中, srl 庫是繼 clog 庫之后, 最小意外的實(shí)現(xiàn) ~

修煉到現(xiàn)在, 逐漸進(jìn)出妖魔戰(zhàn)場, 另一個(gè)異持E担恐怖 - 域外天魔正在逼近. 它會(huì)拷問你的心,
你為什么修煉編程 ? 進(jìn)入彌天幻境, 多數(shù)人在幻境的路中間 ~ 不曾解脫 ~  

走不過去那條大道, 元嬰終身無望 ~ 

4.3 http util by libcurl

開發(fā)中對于 http 請求處理, 也是一個(gè)重要環(huán)節(jié), 鏈接運(yùn)營管理業(yè)務(wù), 不可缺失. 同樣都有固
定套路. 這里用的是 libcurl c http 處理庫. 
// linux 安裝總結(jié)
sudo apt-get install libcurl4-openssl-dev

// winds 使用方法
/*
 方法1. 查教程, 去 libcurl 官網(wǎng)下載最新版本編譯
 方法2. 如果你怕麻煩 github 找我的項(xiàng)目 simplc, 參照 extern 目錄下面
 方法3. 隨便, 只要你搭好 hello world 就行 
 */
按照上面思路, 搭建好環(huán)境開搞. 服務(wù)器中對于 http 請求, 主要圍繞在 get 和 post. 
接收別人的 http 請求和接收 tcp請求一樣, 通過協(xié)議得到完整報(bào)文, 拋給業(yè)務(wù)層. 所
以封裝的 http 工具庫, 接口功能很單一. 就是對外發(fā)送 get post 請求:

httputil.h

#ifndef _H_SIMPLEC_HTTPUTIL
#define _H_SIMPLEC_HTTPUTIL

#include "tstr.h"
#include <curl/curl.h>

//
// http_start - http 庫啟動(dòng)
// return   : void
//
extern void http_start(void);

//
// http_get - http get 請求 or https get請求 
// url      : 請求的url
// str      : 返回最終申請的結(jié)果, NULL表示只請求不接收數(shù)據(jù)
// return   : true表示請求成功
//
extern bool http_get(const char * url, tstr_t str);
extern bool http_sget(const char * url, tstr_t str);

//
// http_post - http post 請求 or https post 請求
// url      : 請求的路徑
// params   : 參數(shù)集 key=value&.... 串
// str      : 返回結(jié)果, 需要自己釋放, NULL表示只請求不接受數(shù)據(jù)返回
// return   : false表示請求失敗, 會(huì)打印錯(cuò)誤信息
//
extern bool http_post(const char * url, const char * params, tstr_t str);
extern bool http_spost(const char * url, const char * params, tstr_t str);

#endif // !_H_SIMPLEC_HTTPUTIL
對于 http_start 啟動(dòng), 稍微說點(diǎn). 不知道有沒有人好奇, winds 上面使用 socket 的
時(shí)候需要先加載 WSAStartup 操作. 其實(shí)是我們用慣了 linux 上面的, 沒有先加載操作.
linux c 程序編譯的時(shí)候, 默認(rèn)加了 socket 庫的初始化加載工作. 所以咱們就不需要繼
續(xù)加載了(編譯器主動(dòng)做了). 這就清晰了 xxx_start 相當(dāng)于啟動(dòng)操作, 先啟動(dòng)車子, 才
能操作車子不是嗎. 那我們繼續(xù)看吧
#include "httputil.h"

// 超時(shí)時(shí)間10s
#define _INT_TIMEOUT        (4)

// libcurl 庫退出操作
static inline void _http_end(void) {
    curl_global_cleanup();
}

inline void 
http_start(void) {
    //
    // CURLcode curl_global_init(long flags);
    // @ 初始化libcurl, 全局只需調(diào)一次
    // @ flags : CURL_GLOBAL_DEFAULT        // 等同于 CURL_GLOBAL_ALL
    //           CURL_GLOBAL_ALL            // 初始化所有的可能的調(diào)用
    //           CURL_GLOBAL_SSL            // 初始化支持安全套接字層
    //           CURL_GLOBAL_WIN32          // 初始化WIN32套接字庫
    //           CURL_GLOBAL_NOTHING        // 沒有額外的初始化
    //
    CURLcode code = curl_global_init(CURL_GLOBAL_DEFAULT);
    if (code != CURLE_OK) {
        RETURN(NIL, "curl_global_init error = %s.", curl_easy_strerror(code));
    }

    atexit(_http_end);
}
這類 start 操作基本都是單例, 在 main 函數(shù)啟動(dòng)的時(shí)候組織順序執(zhí)行一次就可以. 其中
atexit 可有可無, 因?yàn)?http_start 操作 curl_global_init 需要跟隨程序整個(gè)生命周期.
對于這種同程序同生共死的操作, 流程操作系統(tǒng)回收吧. 下面 http_get 函數(shù)也只是熟悉 
libcurl 庫的簡單用法:
// 請求共有頭部
static CURL * _http_head(const char * url, tstr_t str) {
    CURL * crl = curl_easy_init();
    if (NULL == crl) {
        RETURN(NULL, "curl_easy_init error !!!");
    }

    // 設(shè)置下載屬性和常用參數(shù)
    curl_easy_setopt(crl, CURLOPT_URL, url);                    // 訪問的URL
    curl_easy_setopt(crl, CURLOPT_TIMEOUT, _INT_TIMEOUT);       // 設(shè)置超時(shí)時(shí)間 單位s
    curl_easy_setopt(crl, CURLOPT_HEADER, true);                // 下載數(shù)據(jù)包括HTTP頭部
    curl_easy_setopt(crl, CURLOPT_NOSIGNAL, true);              // 屏蔽其它信號

    curl_easy_setopt(crl, CURLOPT_WRITEFUNCTION, (curl_write_callback)_http_write); // 輸入函數(shù)類型
    curl_easy_setopt(crl, CURLOPT_WRITEDATA, str);              // 輸入?yún)?shù)

    return crl;
}

// 請求共有尾部
static inline bool _http_tail(CURL * crl, tstr_t str) {
    CURLcode res = curl_easy_perform(crl);
    tstr_cstr(str);
    curl_easy_cleanup(crl);
    if (res != CURLE_OK) {
        RETURN(false, "curl_easy_perform error = %s!", curl_easy_strerror(res));
    }

    return true;
}

inline bool 
http_get(const char * url, tstr_t str) {
    CURL * crl = _http_head(url, str);
    return crl ? _http_tail(crl, str) : false;
}
對于 http_sget 走 https 協(xié)議, 相比 http_get 請求也只是多了個(gè)屬性設(shè)置
// 添加 https 請求設(shè)置
static inline void _http_sethttps(CURL * crl) {
    curl_easy_setopt(crl, CURLOPT_SSL_VERIFYPEER, false);
    curl_easy_setopt(crl, CURLOPT_SSL_VERIFYHOST, false);
}

inline bool 
http_sget(const char * url, tstr_t str) {
    CURL * crl = _http_head(url, str);
    if (crl) {
        _http_sethttps(crl);
        return _http_tail(crl, str);
    }
    return false;
}
get 方面請求完了, 自然 post 也會(huì)很快就完了. 不是它太簡單, 而是這種看的多了, 
查查手冊, 看看源碼, 用的會(huì)賊溜 ~
// 添加 post 請求設(shè)置
static inline void _http_setpost(CURL * crl, const char * params) {
    curl_easy_setopt(crl, CURLOPT_POST, true); 
    curl_easy_setopt(crl, CURLOPT_POSTFIELDS, params);
}

inline bool 
http_post(const char * url, const char * params, tstr_t str) {
    CURL * crl = _http_head(url, str);
    if (crl) {
        _http_setpost(crl, params);
        return _http_tail(crl, str);
    }
    return false;
}

bool 
http_spost(const char * url, const char * params, tstr_t str) {
    CURL * crl = _http_head(url, str);
    if (crl) {
        _http_sethttps(crl);
        _http_setpost(crl, params);
        return _http_tail(crl, str);
    }
    return false;
}
前面幾章內(nèi)功練到好, 這里武技只是三花聚頂, 越打越帶勁. 出掌震飛尸, 拔劍滅妖魔.
當(dāng)然了 http 解決方案不少, 很多知名網(wǎng)絡(luò)庫都會(huì)附贈(zèng)個(gè) http 處理小單元. libcurl 是
我見過比較良心的了. 成熟可靠, 資料很全. 武技講的這么多, 希望修煉的你能懂. 一切
都是套路, 只有赤城之人才能走到道的遠(yuǎn)方 ~

4.4 閱讀理解時(shí)間, 不妨來個(gè)定時(shí)器

對于定式器實(shí)現(xiàn)而言無外乎三大套路. 一種是有序鏈表用于解決, 大量重復(fù)輪詢的定時(shí)結(jié)點(diǎn)設(shè)
計(jì)的. 另一種是采用時(shí)間堆構(gòu)建的定時(shí)器, 例如小頂堆, 時(shí)間差最小的在堆定執(zhí)行速度最快. 
還有一種時(shí)間片結(jié)構(gòu), 時(shí)間按照一定顆度轉(zhuǎn)呀轉(zhuǎn), 轉(zhuǎn)到那就去執(zhí)行那條顆度上的鏈表. 總的而
言定時(shí)器的套路不少, 具體看應(yīng)用的場景. 我們這里帶來的閱讀理解是基于有序鏈表, 剛好溫
故下 list 用. 可以說起緣 list, 終于 list. 

sctimer.h

#ifndef _H_SIMPLEC_SCTIMER
#define _H_SIMPLEC_SCTIMER

#include "schead.h"

//
// st_add - 添加定時(shí)器事件,雖然設(shè)置的屬性有點(diǎn)多但是都是必要的
// intval   : 執(zhí)行的時(shí)間間隔, <=0 表示立即執(zhí)行, 單位是毫秒
// timer    : 定時(shí)器執(zhí)行函數(shù)
// arg      : 定時(shí)器參數(shù)指針
// return   : 返回這個(gè)定時(shí)器的唯一id
//
extern int st_add_(int intval, node_f timer, void * arg);
#define st_add(intval, timer, arg) st_add_(intval, (node_f)timer, (void *)(intptr_t)arg)

//
// st_del - 刪除指定事件
// id       : st_add 返回的定時(shí)器id
// return   : void
//
extern void st_del(int id);

#endif // !_H_SIMPLEC_SCTIMER
結(jié)構(gòu)清晰易懂, 業(yè)務(wù)只有添加和刪除. 順便用了一個(gè)去除警告的函數(shù)宏技巧 st_add . 實(shí)現(xiàn)部分如下, 
重點(diǎn)感受數(shù)據(jù)結(jié)構(gòu)內(nèi)功的 list 結(jié)構(gòu)的用法. 
// 使用到的定時(shí)器結(jié)點(diǎn)
struct stnode {
    $LIST_HEAD;

    int id;                 // 當(dāng)前定時(shí)器的id
    struct timespec tv;     // 運(yùn)行的具體時(shí)間
    node_f timer;           // 執(zhí)行的函數(shù)事件
    void * arg;             // 執(zhí)行函數(shù)參數(shù)
};                     
                               
// 當(dāng)前鏈表對象管理器              
struct stlist {                
    int lock;               // 加鎖用的
    int nowid;              // 當(dāng)前使用的最大timer id
    bool status;            // false表示停止態(tài), true表示主線程loop運(yùn)行態(tài)
    struct stnode * head;   // 定時(shí)器鏈表的頭結(jié)點(diǎn)
};

// 定時(shí)器對象的單例, 最簡就是最復(fù)雜
static struct stlist _st;

// 先創(chuàng)建鏈表對象處理函數(shù)
static struct stnode * _stnode_new(int s, node_f timer, void * arg) {
    struct stnode * node = malloc(sizeof(struct stnode));
    if (NULL == node)
        RETURN(NULL, "malloc struct stnode is error!");

    // 初始化, 首先初始化當(dāng)前id
    node->id = ATOM_INC(_st.nowid);
    timespec_get(&node->tv, TIME_UTC);
    node->tv.tv_sec += s / 1000;
    node->tv.tv_nsec += (s % 1000) * 1000000;
    node->timer = timer;
    node->arg = arg;

    return node;
}

// 得到等待的微秒時(shí)間, <=0的時(shí)候頭時(shí)間就可以執(zhí)行了
static inline int _stlist_sus(struct stlist * st) {
    struct timespec t[1], * v = &st->head->tv;
    timespec_get(t, TIME_UTC);
    return (int)((v->tv_sec - t->tv_sec) * 1000000
        + (v->tv_nsec - t->tv_nsec) / 1000);
}

// 重新調(diào)整, 只能在 _stlist_loop 后面調(diào)用, 線程安全,只加了一把鎖
static void _stlist_run(struct stlist * st) {
    struct stnode * sn;

    ATOM_LOCK(st->lock); // 加鎖防止調(diào)整關(guān)系覆蓋,可用還是比較重要的
    sn = st->head;
    st->head = (struct stnode *)list_next(sn);
    ATOM_UNLOCK(st->lock);

    sn->timer(sn->arg);
    free(sn);
}

// 運(yùn)行的主loop,基于timer管理器
static void * _stlist_loop(struct stlist * st) {
    // 正常輪詢,檢測時(shí)間
    while (st->head) {
        int nowt = _stlist_sus(st);
        if (nowt > 0) {
            usleep(nowt);
            continue;
        }
        _stlist_run(st);
    }

    // 已經(jīng)運(yùn)行結(jié)束
    st->status = false;
    return NULL;
}

// st < sr 返回 < 0, == 返回 0, > 返回 > 0
static inline int _stnode_cmptime(const struct stnode * sl, const struct stnode * sr) {
    if (sl->tv.tv_sec != sr->tv.tv_sec)
        return (int)(sl->tv.tv_sec - sr->tv.tv_sec);
    return (int)(sl->tv.tv_nsec - sr->tv.tv_nsec);
}

int 
st_add_(int intval, node_f timer, void * arg) {
    struct stnode * now;
    // 各種前戲操作
    if (intval <= 0) {
        timer(arg);
        return SufBase;
    }

    now = _stnode_new(intval, timer, arg);
    if (NULL == now) {
        RETURN(ErrAlloc, "_new_stnode is error intval = %d.", intval);
    }

    ATOM_LOCK(_st.lock); //核心添加模塊 要等, 添加到鏈表, 看線程能否取消等

    list_add(&_st.head, _stnode_cmptime, now);

    // 這個(gè)時(shí)候重新開啟線程
    if(!_st.status) {
        if (async_run(_stlist_loop, &_st)) {
            list_destroy(&_st.head, free);
            RETURN(ErrFd, "pthread_create is error!");
        }
        _st.status = true;
    }

    ATOM_UNLOCK(_st.lock);
    
    return now->id;
}

// 通過id開始查找
static inline int _stnode_cmpid(int id, const struct stnode * sr) {
    return id - sr->id;
}

void 
st_del(int id) {
    struct stnode * node;
    if (!_st.head) return;

    ATOM_LOCK(_st.lock);
    node = list_findpop(&_st.head, _stnode_cmpid, id);
    ATOM_UNLOCK(_st.lock);

    free(node);
}
其中用到的 usleep 移植到 winds 上面實(shí)現(xiàn)為 
// 為 Visual Studio 導(dǎo)入一些 GCC 上優(yōu)質(zhì)思路
#ifdef _MSC_VER

//
// usleep - 微秒級別等待函數(shù)
// usec     : 等待的微秒
// return   : The usleep() function returns 0 on success.  On error, -1 is returned.
//
int
usleep(unsigned usec) {
    int rt = -1;
    // Convert to 100 nanosecond interval, negative value indicates relative time
    LARGE_INTEGER ft = { .QuadPart = -10ll * usec };

    HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL);
    if (timer) {
        // 負(fù)數(shù)以100ns為單位等待, 正數(shù)以標(biāo)準(zhǔn)FILETIME格式時(shí)間
        SetWaitableTimer(timer, &ft, 0, NULL, NULL, FALSE);
        WaitForSingleObject(timer, INFINITE);
        if (GetLastError() == ERROR_SUCCESS)
            rt = 0;
        CloseHandle(timer);
    }

    return rt;
}

#endif
以上 sctime 模塊中操作, 無外乎利用 list 構(gòu)建了一個(gè)升序鏈表, 通過額外異步分離
線程 loop 監(jiān)測下去并執(zhí)行. 定時(shí)器一個(gè)通病, 不要放入阻塞函數(shù), 容易失真. 
sctimer 使用方面也很簡單, 例如一個(gè)技能, 吟唱 1s, 持續(xù)傷害 2s. 構(gòu)造如下:
struct skills {
    int id;
    bool exist; // 實(shí)戰(zhàn)走狀態(tài)機(jī), true 表示施法狀態(tài)中 
};

// 007 號技能 火球術(shù), 沒有釋放
struct skills fireball = { 007, false };

static void _end(struct skills * kill) {
    ...
    if (kill.id == 007) {
        puts("火球術(shù)持續(xù)輸出結(jié)束...");
        kill.exist = false;
    }
    ...
}

static void _continued(struct skills * kill) {
    ...
    if (kill.id == 007) {
        puts("火球術(shù)吟唱成功, 開始持續(xù)輸出...");
        kill.exist = true;
        st_add(2000, _end, kill);
    }
    ...
}

static void _start(struct skills * kill) {
    ...
    if (kill.id == 007) {
        puts("火球術(shù)開始吟唱...");
        kill.exist = false;
        st_add(1000, _continued, kill);
    }
    ...
}
調(diào)用 _start 就可以了, 火球術(shù)吟唱, 持續(xù)輸出. 中間打斷什么鬼, 那就自己擴(kuò)展. 后期
根據(jù)標(biāo)識統(tǒng)一繪制顯示. 以上是簡單到吐的思路說不定也很有效. 有點(diǎn)像優(yōu)化過的 select
特定的時(shí)候出其不意 ~

過的真快, 修煉之路已經(jīng)走過小一半了. 從華山劍法練起, 到現(xiàn)在的一步兩步三步. 以后
自己可以上路了, 單純的客戶端業(yè)務(wù)的小妖魔, 分分鐘可以干掉吧. 本章在實(shí)戰(zhàn)中會(huì)用的最
多就是日月輪 sclooprun 模塊. 對于定時(shí)器, 多數(shù)內(nèi)嵌到主線程輪詢模塊(update) 中.  
此刻應(yīng)該多出去歷練求索, 在血與歌中感受生的洗禮. 聆聽心中的道. Thx

元日
王安石 - 宋代

爆竹聲中一歲除,春風(fēng)送暖入屠蘇屋匕。
千門萬戶曈曈日,總把新桃換舊符。

女苑姜明.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牍陌,一起剝皮案震驚了整個(gè)濱河市响牛,隨后出現(xiàn)的幾起案子歉嗓,更是在濱河造成了極大的恐慌,老刑警劉巖舵抹,帶你破解...
    沈念sama閱讀 212,294評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肪虎,死亡現(xiàn)場離奇詭異,居然都是意外死亡掏父,警方通過查閱死者的電腦和手機(jī)笋轨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赊淑,“玉大人爵政,你說我怎么就攤上這事√杖保” “怎么了钾挟?”我有些...
    開封第一講書人閱讀 157,790評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長饱岸。 經(jīng)常有香客問我掺出,道長,這世上最難降的妖魔是什么苫费? 我笑而不...
    開封第一講書人閱讀 56,595評論 1 284
  • 正文 為了忘掉前任汤锨,我火速辦了婚禮,結(jié)果婚禮上百框,老公的妹妹穿的比我還像新娘闲礼。我一直安慰自己,他們只是感情好铐维,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評論 6 386
  • 文/花漫 我一把揭開白布柬泽。 她就那樣靜靜地躺著,像睡著了一般嫁蛇。 火紅的嫁衣襯著肌膚如雪锨并。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,906評論 1 290
  • 那天睬棚,我揣著相機(jī)與錄音第煮,去河邊找鬼。 笑死抑党,一個(gè)胖子當(dāng)著我的面吹牛包警,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播新荤,決...
    沈念sama閱讀 39,053評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼台汇!你這毒婦竟也來了苛骨?” 一聲冷哼從身側(cè)響起篱瞎,我...
    開封第一講書人閱讀 37,797評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痒芝,沒想到半個(gè)月后俐筋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,250評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡严衬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評論 2 327
  • 正文 我和宋清朗相戀三年澄者,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片请琳。...
    茶點(diǎn)故事閱讀 38,711評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粱挡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俄精,到底是詐尸還是另有隱情询筏,我是刑警寧澤,帶...
    沈念sama閱讀 34,388評論 4 332
  • 正文 年R本政府宣布竖慧,位于F島的核電站嫌套,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏圾旨。R本人自食惡果不足惜踱讨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砍的。 院中可真熱鬧痹筛,春花似錦、人聲如沸挨约。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诫惭。三九已至翁锡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夕土,已是汗流浹背馆衔。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怨绣,地道東北人角溃。 一個(gè)月前我還...
    沈念sama閱讀 46,461評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像篮撑,于是被迫代替她去往敵國和親减细。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在赢笨,面了一些公司未蝌,掛了不少驮吱,但最終還是拿到小米、百度萧吠、阿里左冬、京東、新浪纸型、CVTE拇砰、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,216評論 11 349
  • 鄧攸,東晉人狰腌。 東晉那時(shí)候可亂除破,在一次戰(zhàn)亂中鄧攸舍棄了自己的兒子,選擇保住自己的侄子癌别。
    煲湯的鵝閱讀 175評論 0 0
  • 形單影只常伴皂岔,成雙成對無緣。 陌上開花未有展姐,路行一行獨(dú)印躁垛。
    段秦嵐閱讀 117評論 0 1
  • 當(dāng)自己走上了回憶的道路,也就意味著混世有些久了擂达。從初出社會(huì)土铺,帶著好奇的心,到經(jīng)歷了社會(huì)的鎖碎板鬓,直至把自己的那點(diǎn)希望...
    桔子juzi閱讀 456評論 0 1
  • 幼師是一個(gè)偉大的職業(yè)悲敷,可是又會(huì)面對很多的孩子的問題,怎么才能成為一名優(yōu)秀的幼師呢俭令?今天和大家分享一個(gè)我聽到一...
    孔娃閱讀 8,687評論 0 15