libhv每日一學

libhv簡介

libhv是一個跨平臺的類似libevent、libev、libuv的異步事件驅動庫,但提供了更加接近原生的API接口和更加豐富的協議。libhv已被awesome-c收錄。
libhv已廣泛實用在公司的IOT平臺、http API服務之中,正確性、穩定性、可擴展性、性能都有保證,完全開源,請放心使用。

項目地址:https://github.com/ithewei/libhv.git
碼雲鏡像:https://gitee.com/ithewei/libhv.git
QQ技術交流羣:739352073
API文檔:https://hewei.blog.csdn.net/article/details/103976875
注:libhv每日一學博文爲QQ羣裏的libhv每日一學技術分享整理所得,方便新老朋友查閱學習,該博文每隔幾日會同步更新一次。

http模塊(包含http、https、http2、grpc、RESTful API)

http編譯測試,包含web serviceindexof serviceapi service (支持RESTful API
HTTP API ContentType支持application/jsonapplication/x-www-form-urlencodedmultipart/form-data的構造和解析

git clone https://github.com/ithewei/libhv.git
cd libhv
make httpd curl

bin/httpd -h
bin/httpd -d
#bin/httpd -c etc/httpd.conf -s restart -d
ps aux | grep httpd

# http web service
bin/curl -v localhost:8080

# http indexof service
bin/curl -v localhost:8080/downloads/

# http api service
bin/curl -v localhost:8080/v1/api/hello
bin/curl -v localhost:8080/v1/api/echo -d "hello,world!"
bin/curl -v localhost:8080/v1/api/query?page_no=1&page_size=10
bin/curl -v localhost:8080/v1/api/kv   -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
bin/curl -v localhost:8080/v1/api/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
bin/curl -v localhost:8080/v1/api/form -F "file=@LICENSE"

bin/curl -v localhost:8080/v1/api/test  -H "Content-Type:application/x-www-form-urlencoded" -d
'bool=1&int=123&float=3.14&string=hello'
bin/curl -v localhost:8080/v1/api/test  -H "Content-Type:application/json" -d
'{"bool":true,"int":123,"float":3.14,"string":"hello"}'
bin/curl -v localhost:8080/v1/api/test  -F 'bool=1 int=123 float=3.14 string=hello'
# RESTful API: /group/:group_name/user/:user_id
bin/curl -v -X DELETE localhost:8080/v1/api/group/test/user/123

# webbench (linux only)
make webbench
bin/webbench -c 2 -t 60 localhost:8080

libhv提供的httpd性能可媲美nginx
libhv-vs-nginx
indexof service目錄服務效果圖:
indexof
https編譯測試,集成了openssl庫(修改config.mkWITH_OPENSSL=yes
https
http2編譯測試,集成了nghttp2
nghttp2
注:以下是模擬HTTP1的打印結果,HTTP2是二進制協議,採用了HPACK頭部壓縮和幀的概念
http2
通過libhv庫編寫http API是如此簡單,支持RESTful API,並且可擴展成多進程/多線程模型
下面貼出最基礎的body用法,其它ContentType用法見examples/httpd/http_api_test.h

#include "HttpServer.h"

int http_api_hello(HttpRequest* req, HttpResponse* res) {
    res->body = "hello";
    return 0;
}

int main() {
    HttpService service;
    service.base_url = "/v1/api";
    service.AddApi("/hello", HTTP_GET, http_api_hello);

    http_server_t server;
    server.port = 8080;
    server.worker_processes = 4;
    //server.worker_threads = 4;
    server.service = &service;
    http_server_run(&server);
    return 0;
}

日誌模塊

hlog

libhv應用程序框架

libhv提供了命令行解析、INI配置文件解析、日誌文件、pid文件、信號處理等創建一個應用程序的常用模塊

參考examples/hmain_test.cpp,講解這些模塊使用方法

測試示例:

make test
bin/test -h
bin/test -v
bin/test -t
bin/test -d
ps aux | grep test
bin/test -s status
bin/test -s stop
ps aux | grep test
bin/test -s start -d
ps aux | grep test
bin/test -s restart -d
ps aux | grep test

流程圖:
main.cpp

libhv事件循環使用入門

參考examples/loop.cexamples/timer.cexamples/tcp.cexamples/udp.cexamples/nc.c

make loop timer tcp udp nc
bin/loop
bin/timer
bin/tcp 1111
bin/nc 127.0.0.1 1111
bin/udp 2222
bin/nc -u 127.0.0.1 2222
#include "hloop.h"

void on_close(hio_t* io) {
}

void on_recv(hio_t* io, void* buf, int readbytes) {
    hio_write(io, buf, readbytes);
}

void on_accept(hio_t* io) {
    hio_setcb_close(io, on_close);
    hio_setcb_read(io, on_recv);
    hio_read(io);
}

int main(int argc, char** argv) {
    if (argc < 2) {
        printf("Usage: cmd port\n");
        return -10;
    }
    int port = atoi(argv[1]);

    hloop_t* loop = hloop_new(0);
    hio_t* listenio = create_tcp_server(loop, "0.0.0.0", port, on_accept);
    if (listenio == NULL) {
        return -20;
    }
    hloop_run(loop);
    hloop_free(&loop);
    return 0;
}

流程圖:
hloop

libevent、libev、libuv、libhv、boost.asio、poco、muduo七種echo-server實現對比

https://github.com/ithewei/libhv/tree/master/echo-servers中包含libevent、libev、libuv、libhv、boost.asio、poco、muduo七種echo-server實現,感興趣的可以看看

編譯測試步驟見README.md 中的echo-servers/benchmark

make libhv
make webbench
# ubuntu16.04
sudo apt-get install libevent-dev libev-dev libuv1-dev libboost-dev libasio-dev libpoco-dev
# muduo install => https://github.com/chenshuo/muduo.git
make echo-servers
sudo echo-servers/benchmark.sh

壓力測試結果圖:
echo-macros
注:客戶端和服務端位於同一臺電腦,有一定隨機性,僅供參考,總的來說,這幾個庫性能接近,各有千秋吧

libhv如何實現跨平臺的

主要靠兩個文件:
1、./configure生成的hconfig.h
configure腳本中檢測頭文件、函數是否存在定義相應宏(如HAVE_PTHREAD_HHAVE_GETTIMEOFDAY)
2、base/hplatform.h
操作系統宏:(如_WIN32__linux__等)
編譯器宏:(如__GNUC____clang___MSC_VER等)
CPU體系結構宏:(如__i386____x86_64____arm____aarch64__等)
編程語言宏:__cplusplus

以獲取當前本地日期時間爲例,見base/htime.c
datetime_now

libhv中的宏藝術

C語言宏基礎知識
宏是C/C++語言的一大特色,它將一個標識符定義爲一個字符串,在預處理階段源程序中的該標識符均以指定的字符串來代替,使用宏可以使代碼更加簡潔和增強可讀性。

#define <宏名> (<參數表>) <宏體>
#undef <宏名>

#ifdef <宏名>
    ...
#else
    ...
#endif

//define中的三個特殊符號:#,##,#@
#define STRCAT(x,y) x##y //連接x和y成一個字符串
#define TOCHAR(x) #@x  //給x加上單引號
#define TOSTR(x) #x //給x加上雙引號

base/herr.hbase/herr.c中對錯誤碼定義爲例:

#define FOREACH_ERR_COMMON(F)   \
    F(0,    OK,             "OK")               \
    F(1000, UNKNOWN,        "Unknown error")    \
    \
    F(1001, NULL_PARAM,     "Null parameter")   \
    F(1002, NULL_POINTER,   "Null pointer")     \
    F(1003, NULL_DATA,      "Null data")        \
    F(1004, NULL_HANDLE,    "Null handle")      \
    \
    F(1011, INVALID_PARAM,      "Invalid parameter")\
    F(1012, INVALID_POINTER,    "Invalid pointer")  \
    F(1013, INVALID_DATA,       "Invalid data")     \
    F(1014, INVALID_HANDLE,     "Invalid handle")   \
    F(1015, INVALID_JSON,       "Invalid json")     \
    F(1016, INVALID_XML,        "Invalid xml")      \
    F(1017, INVALID_FMT,        "Invalid format")   \
    F(1018, INVALID_PROTOCOL,   "Invalid protocol") \
    F(1019, INVALID_PACKAGE,    "Invalid package")  \

#define FOREACH_ERR(F)      \
    FOREACH_ERR_COMMON(F)   \
    FOREACH_ERR_FUNC(F)     \
    FOREACH_ERR_SERVICE(F)  \
    FOREACH_ERR_GRPC(F)     \

#undef ERR_OK // prevent conflict
enum {
#define F(errcode, name, errmsg) ERR_##name = errcode,
    FOREACH_ERR(F)
#undef  F
};
// errcode => errmsg
const char* hv_strerror(int err) {
    if (err > 0 && err <= SYS_NERR) {
        return strerror(err);
    }

    switch (err) {
#define F(errcode, name, errmsg) \
    case errcode: return errmsg;
    FOREACH_ERR(F)
#undef  F
    default:
        return "Undefined error";
    }
}

hv_strerror中宏替換後實際上是很多個case errcode: return errmsg;,添加一個錯誤碼定義只需在頭文件見中添加即可,無需改動源文件,代碼更簡潔,可擴展性更好

golang defer 宏實現

defer在作用域釋放時做一些清理工作,可避免return前漏做,或者到處是調用清理函數,或者亂用goto導致的可讀性差 等問題
base/hscope.h,此頭文件中還定義了很多利用作用域和RAII機制釋放資源的模板類型,感興趣的可以看看,用在自己項目中

// same as golang defer
class Defer {
public:
    Defer(Function&& fn) : _fn(std::move(fn)) {}
    ~Defer() { if(_fn) _fn();}
private:
    Function _fn;
};
#define defer(code) Defer STRINGCAT(_defer_, __LINE__)([&](){code});

defer_test

libhv多線程同步相關知識

base/hthread.h base/hmutex.h unittest/hmutex_test.c
hthread
hmutex
pthread和hmutex對應的宏就不貼出來了,請自行查閱base/hmutex.h
編譯運行單元測試

make unittest
bin/hmutex_test

跨平臺socket編程

Windows網絡編程和Unix網絡編程區別:

  • 頭文件和庫文件不同;
  • Windows下需要調用WSAStartup初始化;
  • 獲取錯誤碼方式不同,以及錯誤碼不同,Windows錯誤碼以WSA開頭
  • Unix下沒有closesocket;
  • 設置發送超時和接受超時參數類型要求不同;
  • 設置非阻塞方式不同;
  • 非阻塞connect返回值不同;
    hsocket
    SO_RCVTIMEO
    connect

如何編寫兼容IPv6的網絡程序

IPv6

c語言如何實現c++的繼承

使用宏即可實現,原理如下
inherit
libhv中就應用了這種技巧,見event/hloop.hevent/hevent.h
hevent_s
htimer_s

libhv事件循環邏輯

int hloop_run(hloop_t* loop) {
    loop->status = HLOOP_STATUS_RUNNING;
    while (loop->status != HLOOP_STATUS_STOP) {
        if (loop->status == HLOOP_STATUS_PAUSE) {
            msleep(PAUSE_TIME);
            hloop_update_time(loop);
            continue;
        }
        ++loop->loop_cnt;
        if (loop->nactives == 0) break;
        hloop_process_events(loop);
        if (loop->flags & HLOOP_FLAG_RUN_ONCE) {
            break;
        }
    }
    loop->status = HLOOP_STATUS_STOP;
    loop->end_hrtime = gethrtime();
    if (loop->flags & HLOOP_FLAG_AUTO_FREE) {
        hloop_cleanup(loop);
        SAFE_FREE(loop);
    }
    return 0;
}

調用hloop_run後,我們就進入了libhv的事件循環,很簡單,就是while循環中調用hloop_process_events處理各類事件
hloop_process_events
具體各類事件是如何處理的,感興趣的可以研究源碼

libhv完美結合openssl

libhv中使用openssl庫實現TCP加密通信,這是幾乎所有異步IO通信庫都沒有做的一點,而且libhv中開啓SSL特別簡單,僅需兩個API
1、初始化全局的SSL_CTX,見base/ssl_ctx.h

int ssl_ctx_init(const char* crt_file, const char* key_file, const char* ca_file);

2、啓用SSL,見event/hloop.h

int  hio_enable_ssl(hio_t* io);

libhv中的https即是最好的例子:

sudo apt-get install openssl libssl-dev # ubuntu下安裝openssl依賴
make clean
make WITH_OPENSSL=yes
修改配置文件etc/httpd.conf => ssl = on
bin/httpd -d
bin/curl -v https://localhost:8080
curl -v https://localhost:8080 --insecure

master-worker 多進程|多線程模型

大多數庫提供了ThreadPool線程池實現多線程模型
nginx中則實現了master-workers多進程模型
多線程的好處是數據共享以及跨平臺性好(windows下實現多進程可不好做)
多進程的好處是一個worker進程崩潰了,不影響其它worker進程正常工作

libhv中提供了hthread_create創建線程,和spawn_proc(封裝了fork)衍生進程
並封裝了master-workers模型,見utils/hmian.h中定義的master_workers_run函數
給定一個工作函數(包含一個事件循環),可自由擴展成多進程 or 多線程 or 多進程|多線程 三種模式
http_server_run即是調用了master_worker_run實現的,見效果圖:
httpd-master-workers
簡單說下master_workers_run的實現:
在這裏插入圖片描述
worker_proc
最後,如果你看到了這裏,覺得該項目不錯的,請github
star下,支持下國內開源,感謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章