文章目錄
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 service
、indexof service
、api service
(支持RESTful API
)
HTTP API ContentType
支持application/json
、application/x-www-form-urlencoded
、multipart/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
indexof service
目錄服務效果圖:
https編譯測試,集成了openssl
庫(修改config.mk
中WITH_OPENSSL=yes
)
http2編譯測試,集成了nghttp2
庫
注:以下是模擬HTTP1的打印結果,HTTP2是二進制協議,採用了HPACK
頭部壓縮和幀的概念
通過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;
}
日誌模塊
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
流程圖:
libhv事件循環使用入門
參考examples/loop.c
、examples/timer.c
、examples/tcp.c
、examples/udp.c
、examples/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;
}
流程圖:
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
壓力測試結果圖:
注:客戶端和服務端位於同一臺電腦,有一定隨機性,僅供參考,總的來說,這幾個庫性能接近,各有千秋吧
libhv如何實現跨平臺的
主要靠兩個文件:
1、./configure
生成的hconfig.h
configure腳本中檢測頭文件、函數是否存在定義相應宏(如HAVE_PTHREAD_H
、HAVE_GETTIMEOFDAY
)
2、base/hplatform.h
中
操作系統宏:(如_WIN32
、__linux__
等)
編譯器宏:(如__GNUC__
、__clang__
、_MSC_VER
等)
CPU體系結構宏:(如__i386__
、__x86_64__
、__arm__
、__aarch64__
等)
編程語言宏:__cplusplus
以獲取當前本地日期時間爲例,見base/htime.c
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.h
、base/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});
libhv多線程同步相關知識
見base/hthread.h
base/hmutex.h
unittest/hmutex_test.c
pthread和hmutex對應的宏就不貼出來了,請自行查閱base/hmutex.h
編譯運行單元測試
make unittest
bin/hmutex_test
跨平臺socket編程
Windows網絡編程和Unix網絡編程區別:
- 頭文件和庫文件不同;
- Windows下需要調用WSAStartup初始化;
- 獲取錯誤碼方式不同,以及錯誤碼不同,Windows錯誤碼以WSA開頭
- Unix下沒有closesocket;
- 設置發送超時和接受超時參數類型要求不同;
- 設置非阻塞方式不同;
- 非阻塞connect返回值不同;
如何編寫兼容IPv6的網絡程序
c語言如何實現c++的繼承
使用宏即可實現,原理如下
libhv中就應用了這種技巧,見event/hloop.h
、event/hevent.h
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
處理各類事件
具體各類事件是如何處理的,感興趣的可以研究源碼
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
實現的,見效果圖:
簡單說下master_workers_run
的實現:
最後,如果你看到了這裏,覺得該項目不錯的,請github
上star
下,支持下國內開源,感謝!