Cn/Channel
通道类
/ Detailed source-code truncated for AI context efficiency. /---
Cn/EventLoop
事件循环类
/ Detailed source-code truncated for AI context efficiency. /测试代码见:
- evpp/EventLoop_test.cpp
- evpp/EventLoopThread_test.cpp
- evpp/EventLoopThreadPool_test.cpp
---
Cn/Hbase
一些基础函数
/ Detailed source-code truncated for AI context efficiency. /单元测试代码见 unittest/hbase_test.c
---
Cn/Hdns
hdns: 异步DNS解析
hdns 是 libhv 内置的异步 DNS 解析器,完全运行在事件循环(hloop)之内。
为什么需要它
libhv 是一个事件循环库,但此前域名解析走的是阻塞的 getaddrinfo()(base/hsocket.c 的 ResolveAddr)。在事件循环里同步解析 DNS 会卡住整个 loop——期间所有其它连接的读写、定时器都会停摆,网络抖动或 DNS 不可达时甚至会阻塞数秒。
hdns 参考 libevent 的 evdns 思路,自己实现了一套原生的、基于非阻塞 UDP 的 DNS 解析器:构造 DNS 报文、通过 hloop 的非阻塞 UDP 收发、解析响应、处理超时/重试,全程不阻塞事件循环,也不引入任何第三方依赖(不同于 libuv 的线程池方案,也不同于接入 c-ares)。
注意:hdns与protocol/dns.*相互独立。后者是早期的同步 demo(仅供学习),hdns自带一套完整、独立的 DNS 报文实现。
hdns 属于 event 核心模块,默认编入,无需额外编译开关。对外头文件:event/hdns.h。
特性
- 异步 A(IPv4)/ AAAA(IPv6)查询,纯事件驱动。
- 自动读取系统 nameserver:
- Unix/Linux/macOS:解析 /etc/resolv.conf;
- Windows:GetAdaptersAddresses()(链接 iphlpapi);
- 任意平台若拿不到 nameserver,统一回退到 8.8.8.8。
- 加载 /etc/hosts(Windows 为 %SystemRoot%\System32\drivers\etc\hosts),查询前先查表,localhost 等本地映射行为与系统一致。
- 数字 IP 快速路径(字面量 IPv4/IPv6 不发起查询)。
- 每次查询支持超时、重试、多 nameserver 轮转。
- DNS 名字压缩解析。
- 尊重 TTL 的进程内缓存(正向缓存 + 负向缓存)。
- 可取消的查询句柄。
- 结果直接以 sockaddr_u 返回,拿到即可用于 connect。
第一版暂不包含:search domains / ndots、TCP fallback(TC 位截断重查)、nameserver 健康探测、SRV/TXT/MX 等裸记录查询。这些留作后续增强。
接口
/ Detailed source-code truncated for AI context efficiency. /C 层句柄的生命周期契约与htimer_t一致:句柄在查询完成(回调触发)或被hdns_cancel后失效,之后不得再使用。如果需要免疫 use-after-free 的句柄,使用 C++ 层EventLoop::resolveDns()/cancelDns()——它维护DnsID → hdns_t映射(对标TimerID),失效的DnsID在cancelDns里查不到即安全 no-op。
状态码
#define HDNS_STATUS_OK 0 // 成功
#define HDNS_STATUS_TIMEOUT (-1) // 所有尝试均超时
#define HDNS_STATUS_NXDOMAIN (-2) // 无此域名 / 无地址记录
#define HDNS_STATUS_SERVFAIL (-3) // 服务器失败 / 响应异常
#define HDNS_STATUS_BADNAME (-4) // 非法域名
#define HDNS_STATUS_NONAMESERVER (-5) // 无可用 nameserver
#define HDNS_STATUS_NOMEM (-6) // 内存不足
#define HDNS_STATUS_CANCELLED (-7) // 已取消(不会回调)
#define HDNS_STATUS_ERROR (-8) // 其它错误回调时序保证
hdns_resolve 绝不会在调用内部同步触发回调。即使是数字 IP、/etc/hosts 命中、缓存命中这类“立即有结果”的情况,完成也会被投递到下一次 loop 迭代再回调。因此调用方总能先拿到有效句柄,随后可以确定性地保存或取消它,不会出现回调重入或 use-after-free。
示例
#include "hloop.h"
#include "hdns.h"
#include "hsocket.h"static int pending = 0;
static void on_resolved(hdns_t query, const hdns_result_t result, void* userdata) {
hloop_t loop = (hloop_t)userdata;
(void)query;
if (result->status == HDNS_STATUS_OK) {
printf("%s =>\n", result->host);
for (int i = 0; i < result->naddrs; ++i) {
char ip[SOCKADDR_STRLEN] = {0};
sockaddr_ip((sockaddr_u*)&result->addrs[i], ip, sizeof(ip));
printf(" %s\n", ip);
}
} else {
printf("%s => 解析失败, status=%d\n", result->host, result->status);
}
if (--pending == 0) hloop_stop(loop);
}
int main() {
hloop_t* loop = hloop_new(0);
const char* hosts[] = { "localhost", "www.example.com", "github.com" };
pending = 3;
for (int i = 0; i < 3; ++i) {
hdns_resolve(loop, hosts[i], on_resolved, loop);
}
hloop_run(loop);
hloop_free(&loop);
return 0;
}
完整示例见 examples/host.c,性能对比见 unittest/hdns_benchmark.c。
C++ 层:EventLoop::resolveDns
C++ 客户端不直接用 C 层裸指针句柄,而是通过 EventLoop 提供的 id 化接口(对标 setTimer/killTimer 的 TimerID):
// 返回免疫 use-after-free 的 DnsID(0 = 失败);cb 在 loop 线程回调
DnsID resolveDns(const char host, DnsCallback cb, const hdns_setting_t opt = NULL);
void cancelDns(DnsID id); // 线程安全;失效 id 自动 no-op
// DnsCallback: void(int status, int naddrs, const sockaddr_u* addrs)EventLoop 内部维护 DnsID → hdns_t* 映射:查询完成时在完成回调里擦除映射项,cancelDns 按 id 查表(查不到即安全 no-op)。因此上层只持有一个 DnsID 整数,即使底层 hdns_t 已被释放,拿旧 id 去 cancel 也不会 UAF——与 TimerID 完全同构。
与 connect 路径的集成
hdns 已接入 C++ 客户端的 connect 路径。异步解析逻辑统一沉淀在基类 TcpClientEventLoopTmpl(evpp/TcpClient.h)里,并通过上面的 EventLoop::resolveDns 使用 DnsID 句柄,因此所有继承自 TcpClientTmpl 的客户端(TcpClient、WebSocketClient,以及未来任何 XXXClient)都自动获得异步 DNS,无需各自编写胶水代码。
TcpClientTmpl 派生类(TcpClient / WebSocketClient / ...)
- 目标是数字 IP(或 Unix Domain Socket)时,createsocket 走原有同步快速路径立即建 socket,行为不变;
- 目标是域名时,createsocket 只记录 host/port 并延迟解析(不阻塞);startConnect()(首次连接与每次重连都会调用)先用 EventLoop::resolveDns 异步解析,拿到地址后再在 startConnectWithAddr() 建 socket、connect。整个过程不阻塞事件循环。旧实现在 loop 线程里调用阻塞的 getaddrinfo,会卡住整个 loop。
- 每次连接/重连都会重新解析,自动应对 DNS 变化;
- 对象只持有 DnsID;析构或主动 closesocket() 时 cancelDns 即可,无悬垂指针风险(销毁客户端时查询仍在途也安全,由 unittest/tcpclient_dns_test 覆盖)。
- 解析失败(含首次连接就失败、此时还没有 channel)时,会用一个 NULL-io 的 channel(isConnected() 为 false、各方法对空 io 已做防护)走 onConnection(断开) 通知,保证用户总能收到失败回调(而不是静默丢失);若配置了重连,则继续按重连策略重试。由 unittest/tcpclient_dns_test 覆盖。
- 用户可在 onConnection 里用 channel->error() 区分失败原因:DNS 解析失败返回 ERR_DNS_RESOLVE(见 herr.h),连接失败则返回底层 IO 错误(如 ETIMEDOUT、连接被拒等)。Channel 新增了 setError()/error()——error() 优先返回上层显式设置的错误码,否则回退到 hio_error(io),且对空 io 安全。
新增客户端零成本:只要继承TcpClientTmpl并用createsocket(port, host)+start(),就自动拥有异步 DNS,不需要实现任何onDnsResolved之类的回调。
AsyncHttpClient
AsyncHttpClient 不继承 TcpClientTmpl(自带连接池等逻辑),单独接入,但同样使用 EventLoop::resolveDns:
- 请求 URL 里是数字 IP(或 Unix Domain Socket)时,走原有同步快速路径;
- 请求 URL 里是域名时,doTask 会先用 EventLoop::resolveDns 异步解析,回调里再建立连接、发送请求——同样不阻塞 loop。旧实现直接在 loop 线程里对域名做阻塞 getaddrinfo。
- 查询生命周期由 EventLoop 的 DnsID 映射管理,随 loop 拆除统一回收,无需客户端手工跟踪。
说明:同步的HttpClient/requests请求路径运行在调用方线程(并非在 loop 内),阻塞解析可接受,故该路径维持不变。
性能说明
unittest/hdns_benchmark.c 对比“顺序阻塞 getaddrinfo” 与 “单 loop 并发 hdns” 解析同一批域名:由于异步解析把所有查询一次性发出、由事件循环并发多路复用,总耗时约等于一次最慢的解析,而不是所有解析之和;更关键的是解析期间事件循环始终不被阻塞,其它 IO / 定时器可以继续运行。
注意:单次“命中缓存”的孤立解析,系统getaddrinfo(下游有systemd-resolved等本地缓存)可能更快;hdns通过尊重 TTL 的进程内缓存来弥补——命中缓存时是进程内查表(微秒级),且同样不阻塞 loop。
---
Cn/Hlog
日志
/ Detailed source-code truncated for AI context efficiency. /测试代码见 examples/hloop_test.c
---
Cn/Hloop
事件循环和IO多路复用机制介绍
事件循环是libevent、libev、libuv、libhv这类网络库里最核心的概念,即在事件循环里处理IO读写事件、定时器事件、自定义事件等各种事件;<br>
IO多路复用即在一个IO线程监听多个fd,如最早期的select、后来的poll,linux的epoll、windows的iocp、bsd的kqueue、solaris的port等,都属于IO多路复用机制。<br>
非阻塞NIO搭配IO多路复用机制就是高并发的钥匙。<br>libhv下的event模块正是封装了多种平台的IO多路复用机制,提供了统一的事件接口,是libhv的核心模块。<br>
hloop.h: 事件循环模块对外头文件。<br>
/ Detailed source-code truncated for AI context efficiency. /示例代码:
- 事件循环: examples/hloop_test.c
- 定时器: examples/htimer_test.c
- TCP回显服务: examples/tcp_echo_server.c
- TCP聊天服务: examples/tcp_chat_server.c
- TCP代理服务: examples/tcp_proxy_server.c
- TCP客户端: examples/tcp_client_test.c
- UDP回显服务: examples/udp_echo_server.c
- UDP代理服务: examples/udp_proxy_server.c
- 网络客户端: examples/nc
- SOCKS5代理服务: examples/socks5_proxy_server.c
- HTTP服务: examples/tinyhttpd.c
- HTTP代理服务: examples/tinyproxyd.c
- jsonRPC示例: examples/jsonrpc
- protobufRPC示例: examples/protorpc
多进程/多线程模式示例代码:
- 多accept进程模式: examples/multi-thread/multi-acceptor-processes.c
- 多accept线程模式: examples/multi-thread/multi-acceptor-threads.c
- 一个accept线程+多worker线程: examples/multi-thread/one-acceptor-multi-workers.c
---
Cn/HttpClient
HTTP 客户端类
/ Detailed source-code truncated for AI context efficiency. /测试代码见 examples/http_client_test.cpp
---
Cn/HttpContext
/ Detailed source-code truncated for AI context efficiency. /---
Cn/HttpLuaHandler
Http Lua Handler
HttpScriptHandler 允许 HttpService 调用脚本里的 handle(ctx) 方法处理 HTTP 请求。当前支持 .lua 脚本,适合把少量业务逻辑从 C++ 编译周期里解耦出来:修改脚本后无需重新编译服务,下一次请求会自动加载新脚本。
该功能是可选模块,默认不编译。
编译
需要 Lua 5.3 或更新版本的开发库。
Makefile:
make libhv WITH_LUA=yes
make http_server_test WITH_LUA=yes
make unittest WITH_LUA=yes如果系统没有 pkg-config,或者 Lua 安装在自定义路径,可以显式指定:
make libhv WITH_LUA=yes \
LUA_CFLAGS="-I/usr/local/include/lua" \
LUA_LIBS="-L/usr/local/lib -llua"CMake:
cmake -S . -B build -DWITH_LUA=ON -DBUILD_EXAMPLES=ON -DBUILD_UNITTEST=ON
cmake --build build基本用法
C++:
#include "HttpServer.h"
#include "HttpScriptHandler.h"using namespace hv;
int main() {
HttpService router;
router.GET("/hello", HttpScriptHandler("scripts/hello.lua"));
HttpServer server;
server.port = 8080;
server.service = &router;
server.run();
return 0;
}
Lua:
function handle(ctx)
local id = ctx:query("id", "")
return ctx:json({
ok = true,
id = id,
path = ctx:path()
})
end如果需要明确指定 Lua 引擎,也可以直接使用 HttpLuaHandler("scripts/hello.lua")。推荐用户代码优先使用 HttpScriptHandler,这样后续增加 JS/Python 等脚本引擎时不用改路由注册代码。
目录映射
HttpService::Script(path, script_dir) 可以把 URL 前缀映射到脚本目录,内部同样使用 HttpScriptHandler:
router.Script("/script/", "scripts");访问 /script/user?id=42 时会调用 scripts/user.lua。访问 /script/ 时会调用 scripts/index.lua。当前目录映射只自动补 .lua 后缀。
目录映射默认支持 GET、POST、PUT、DELETE、PATCH。路径中包含 .. 路径段时返回 403。
ctx API
首版只暴露 HTTP handler 必需的最小 API:
ctx:method() -- GET/POST/...
ctx:path() -- URL path
ctx:param(name, default) -- query/restful params
ctx:query(name, default) -- alias of param
ctx:header(name, default)
ctx:body()ctx:status(code)
ctx:set_header(name, value)
ctx:text(str)
ctx:json(table)
handle(ctx) 可以直接调用 ctx:text / ctx:json 返回,也可以返回字符串、数字状态码或 Lua table:
function handle(ctx)
ctx:status(201)
ctx:set_header("X-From", "lua")
return ctx:text("created")
endhv API
脚本运行在所属 IO 线程的 协程 里,因此可以用 同步写法 调用异步能力:调用会挂起当前请求的协程、把控制权交还事件循环,结果就绪后在同一线程恢复,全程不阻塞 loop。
所有脚本可用能力都挂在统一的全局 hv 表下:
hv.version() -- libhv 版本串, 如 "1.3.4"
hv.log(...) -- 日志 (INFO), 等价 hv.logi
hv.logd(...) / hv.logi(...) / hv.logw(...) / hv.loge(...) -- debug/info/warn/error
hv.json.encode(tbl) -- table -> json string
hv.json.decode(str) -- json string -> tablehv.setTimeout(ms, fn) -- 定时器 (返回句柄)
hv.setInterval(ms, fn)
hv.clearTimer(handle)
hv.sleep(ms) -- 协程同步 sleep: 挂起当前协程 ms 毫秒, 不阻塞 loop
hv.resolveDns(host) -- 协程同步 DNS 解析: 返回 { ip, ... } 或 nil, err
hv.run() / hv.stop() -- 运行/停止当前线程的 event loop (独立脚本用; HTTP handler 内不需要)
-- TCP/UDP (协程同步, event 层, 仅当前 loop)
local conn, err = hv.connect(host, port [, timeout_ms]) -- TCP 客户端
hv.tcpServer(host, port, function(conn) ... end) -- 每连接一个协程
local sock = hv.udpClient(host, port)
hv.udpServer(host, port, function(sock, data, peer) ... end)
-- conn: conn:read() / conn:readline() / conn:readuntil(d) / conn:readbytes(n)
-- conn:setUnpack(opts) / conn:write(s) / conn:close() / conn:fd() / conn:peeraddr()
-- sock: sock:sendto(s) / sock:recvfrom() -> data,peer / sock:close()
conn:setUnpack(opts) 让后续 conn:read() 每次返回一个完整的包(参考 libhv hio_set_unpack):
conn:setUnpack({
mode = "length_field", -- none|fixed|delimiter|length_field
body_offset = 5, length_field_offset = 1,
length_field_bytes = 4, length_field_coding = "be", -- be|le|varint|asn1
-- delimiter 模式: delimiter = "\r\n" ; fixed 模式: fixed_length = 16
})示例(handler 内部“同步”写法,实际异步,loop 不阻塞):
function handle(ctx)
local addrs, err = hv.resolveDns("example.com")
if err then
ctx:status(502)
return ctx:json({ ok = false, error = err })
end
return ctx:json({ ok = true, addrs = addrs })
end多个请求会在同一 IO 线程上并发交错执行:某个请求在 hv.resolveDns / hv.sleep 处挂起时,同线程的其它请求会继续推进。注意协作式调度的语义——跨越挂起点不要对全局状态做原子性假设。
hv.setTimeout/hv.resolveDns对应event/层能力,hv.log/hv.version对应base/层,hv.json对应cpputil/;实现分别在lua/hvlua_event.c、lua/hvlua_base.c、lua/hvlua_json.cpp。TCP/HTTP client、Redis 等高层 client 绑定后续按WITH_LUA/WITH_REDIS等开关编入。
热更新
HttpScriptHandler 当前会把 .lua 文件转给 HttpLuaHandler。HttpLuaHandler 会记录脚本文件的 mtime。每次请求前,如果文件被修改,会重新加载脚本。
重新加载失败时:
- 如果已有旧版本脚本,继续使用旧版本。
- 如果首次加载失败,返回 500。
这能避免线上脚本语法错误直接打断已有服务。
示例
make http_server_test WITH_LUA=yes
bin/http_server_test 8080
curl "http://127.0.0.1:8080/lua/hello?id=42"
curl "http://127.0.0.1:8080/script/hello?id=42"---
Cn/HttpMessage
/ Detailed source-code truncated for AI context efficiency. /---
Cn/HttpServer
HTTP 服务端类
/ Detailed source-code truncated for AI context efficiency. /测试代码见 examples/http_server_test.cpp
---
Cn/Lua
Lua Binding
libhv 提供一套 Lua 绑定,让 Lua 脚本直接驱动 libhv 的事件循环与异步网络能力。核心特点是用同步写法表达异步 IO:脚本里 local data = conn:read()、local resp = hv.http.get(url)、local v = r:get(k) 读起来都是阻塞式直觉,但底层通过协程 yield → 异步回调 → resume,事件循环全程不阻塞(与 OpenResty 同模型)。
该功能是可选模块,默认不编译。
编译
需要 Lua 5.3 或更新版本的开发库。
Makefile:
./configure --with-lua --with-http --with-redis --with-mqtt
make libhv
make hvlua # 独立 Lua 运行时
make unittest # 编译 lua 相关单测CMake:
cmake -S . -B build -DWITH_LUA=ON -DWITH_HTTP=ON -DWITH_REDIS=ON -DWITH_MQTT=ON
cmake --build build分层说明:hloop.* 定时器、hv.tcpClient/tcpServer/udpClient/udpServer、hv.resolveDns、hv.json、hv.log 只依赖纯 C 的 event 层,WITH_LUA 即可用;hv.http / hv.ws 需要 WITH_HTTP,hv.redis 需要 WITH_REDIS,hv.mqtt 需要 WITH_MQTT。未开启对应模块时,Lua 里 hv.http / hv.redis / hv.mqtt 为 nil。
运行脚本
独立运行时 hvlua:
bin/hvlua examples/lua/timer.lua
bin/hvlua examples/lua/tcp_client.lua 127.0.0.1 10514examples/lua/ 下有 timer / sleep / dns / tcp / udp / http / ws / redis / mqtt 各场景的示例脚本。
核心模型
- 每个 loop 线程一个 lua_State,存放在 hloop_t 上,loop 销毁时关闭。
- 协程同步写法:每个任务(一段脚本 / 每个 HTTP 请求 / 每个接入连接)跑在独立协程里;可挂起的绑定(conn:read、hv.sleep、hv.http.get、r:get 等)内部 yield,异步结果回来后在同一 loop 线程上 resume,脚本从挂起点继续。
- 无锁:单 loop 线程 + 协作式协程,任意时刻只有一个协程在执行,其余停在各自 yield 点。注意跨 yield 点的全局状态可能被其它任务穿插修改(与 OpenResty 同模型)。
- 协作式调度:纯 CPU 死循环会阻塞该 loop 线程。
返回值约定(统一)
- 成功:返回 Lua 原生值(string / integer / boolean / table)。
- 无结果(Redis nil 回复、DNS 无记录):返回 nil。
- 失败:返回 nil, "错误消息"(第二个返回值为错误串),脚本用 local v, err = ...; if err then ... end 处理。
---
hv 通用工具
hv.version() -- libhv 版本串,如 "1.3.4"
hv.log(...) -- 以 tab 连接参数,info 级日志(logi 的别名)
hv.logd(...) / hv.logi(...) / hv.logw(...) / hv.loge(...) -- debug/info/warn/errorhv.json.encode(tbl) -- table -> json 字符串
hv.json.decode(str) -- json 字符串 -> table
定时器与协程 sleep
local id = hv.setTimeout(1000, function() print("once") end) -- 返回句柄
local id2 = hv.setInterval(500, function() print("tick") end)
hv.clearTimer(id)hv.sleep(1000) -- 协程同步:挂起当前协程 1000ms,loop 不阻塞
hv.run() -- 兼容保留;loop 由宿主自动驱动,无需调用
hv.stop() -- 停止当前 loop
hv.resolveDns(协程同步)
local addrs, err = hv.resolveDns("example.com")
-- addrs: { "93.184.216.34", ... } ; 失败: nil, errTCP / UDP(event 层,协程同步)
命名对齐 C++ 类 hv::TcpClient / TcpServer / UdpClient / UdpServer;hv.connect 是 hv.tcpClient 的别名,hv.listen 是 hv.tcpServer 的别名。
TCP 客户端
local conn, err = hv.tcpClient(host, port [, timeout_ms]) -- 别名 hv.connect;协程同步,连上或失败
conn:write("hello") -- 非阻塞,进写队列,即发即走
local data, err = conn:read() -- 协程同步:挂起直到有数据;对端关闭返回 nil,"closed"
conn:close()
conn:fd() -- fd 或 -1
conn:peeraddr() -- "ip:port"拆包读(文本协议 / 二进制协议):
local line = conn:readline() -- 读到 '\n'(含)
local data = conn:readuntil("\n") -- 读到单字节分隔符(含)
local buf = conn:readbytes(16) -- 读满 16 字节-- 设置一次后,conn:read() 每次返回一个完整的包(二进制协议最常用)
conn:setUnpack({
mode = "length_field", -- none | fixed | delimiter | length_field
package_max_length = 1 << 21,
-- length_field 模式:
body_offset = 5, length_field_offset = 1,
length_field_bytes = 4, length_field_coding = "be", -- be | le | varint | asn1
length_adjustment = 0,
-- delimiter 模式: delimiter = "\r\n"
-- fixed 模式: fixed_length = 16
})
TCP 服务端
on_conn(conn) 在每个新连接的独立协程里被调用,因此可在里面用同步写法。
hv.tcpServer("0.0.0.0", 8080, function(conn) -- 别名 hv.listen
while true do
local data, err = conn:read()
if err then break end -- 连接关闭
conn:write(data) -- echo
end
end)UDP
UDP 无连接,sock 复用 conn 对象。
local sock = hv.udpClient("127.0.0.1", 8080)
sock:sendto("ping")
local data, peer = sock:recvfrom() -- 协程同步,返回数据 + 对端地址hv.udpServer("0.0.0.0", 8080, function(sock, data, peer)
sock:sendto("pong")
end)
hv.http(协程同步,需 WITH_HTTP)
local resp, err = hv.http.get("http://127.0.0.1:8080/ping")
-- resp: { status = 200, body = "...", headers = { ... } }
local resp2 = hv.http.post("http://.../echo", "body", { ["Content-Type"] = "text/plain" })
local resp3 = hv.http.put("http://.../item", "body")
local resp4 = hv.http.delete("http://.../item")
local resp5 = hv.http.request("GET", url [, body [, headers]])hv.ws(WebSocket,协程同步,需 WITH_HTTP)
WebSocket 是消息驱动的,收到的消息缓存在收件箱,ws:recv() 挂起直到有一条消息。连接断开时 recv()/send() 返回 (nil, err):开启了自动重连时 err="reconnecting"(临时断开,底层正在重连),否则 err="closed"(终止)。
-- 第二参数为可选 opts 表
local ws, err = hv.ws.connect("ws://127.0.0.1:8888/path", {
headers = { ["X-Token"] = "..." }, -- 可选:握手请求头
ping_interval = 10000, -- 可选:心跳 ping 间隔 ms
reconnect = { -- 可选:给了就开自动重连
min_delay = 1000, -- ms
max_delay = 10000, -- ms
delay_policy = 2, -- 0 固定 / 1 线性 / 2 指数退避
max_retry = 0, -- 最大重试次数,0 = 无限
},
})
ws:send("hello") -- 文本帧
ws:send(payload, "binary") -- 二进制帧
local msg, err = ws:recv() -- 协程同步:挂起直到收到一条消息
if err == "reconnecting" then ... end -- 临时断开,正在重连
ws:close() -- 显式关闭(终止,禁用重连)开启重连后:重连成功会自动恢复,后续recv()/send()继续可用;断开期间send()会返回(nil,"reconnecting")而非静默丢弃,recv()同样返回(nil,"reconnecting")让脚本自行决定继续等待还是退出。ws:close()是显式终止,会禁用重连。
hv.redis(协程同步,需 WITH_REDIS)
local r = hv.redis.new({ host = "127.0.0.1", port = 6379, auth = "", db = 0, timeout = 3000 })r:set("k", "v") -- 语法糖
local v = r:get("k") -- bulk -> string / nil
local n = r:incr("c") -- integer
r:del("k") / r:decr("c") / r:expire("k", 60) / r:exists("k")
-- 任意命令:变参或数组表两种形态
local pong = r:command("PING")
local ret = r:command({ "HSET", "u:1", "name", "tom" })
回复到 Lua 值的映射:string -> string,integer -> integer,nil 回复 -> nil,array -> 表(1 起始,其中嵌套的 nil 元素用 false 占位),error 回复 -> nil, "错误消息"。
hv.redis用new而非connect:它是纯构造,连接是懒发起 + 断线重连,命令可在未连接时排队;这与hv.ws.connect/hv.mqtt.connect挂起到握手完成才返回的语义不同。
hv.mqtt(协程同步,需 WITH_MQTT)
MQTT 是消息驱动的,m:recv() 挂起直到 broker 推来一条 PUBLISH。
local m, err = hv.mqtt.connect({
host = "127.0.0.1", port = 1883,
id = "client-1", username = "", password = "",
keepalive = 60, clean_session = true, ssl = false,
reconnect = { -- 可选:给了就开自动重连
min_delay = 1000, max_delay = 10000, delay_policy = 2, max_retry = 0,
},
}) -- 协程同步:挂起到 CONNACK 或失败m:subscribe("topic", 1) -- 返回 mid
m:publish("topic", "payload", 1, 0) -- topic, payload, qos, retain -> mid
m:unsubscribe("topic")
local msg, err = m:recv() -- { topic =, payload =, qos = }
if err == "reconnecting" then ... end -- 临时断开,正在重连;err="closed" 为终止
m:disconnect() -- 显式断开(终止,禁用重连)
与 hv.ws 一致:开启重连后断线是临时的,recv()在断线期间返回(nil,"reconnecting"),重连成功后自动恢复;m:disconnect()是显式终止,禁用重连。
HTTP Lua Handler
在 HTTP 服务端里用 Lua 脚本处理请求(handle(ctx)),请求在 IO 线程的协程里执行,脚本内可用上述同步写法调用异步 client。详见 HttpLuaHandler.md。
---
Cn/README
c接口
- hloop: 事件循环
- hdns: 异步DNS解析
- hbase: 基础函数
- hlog: 日志
lua接口
c++接口
- class EventLoop: 事件循环类
- class Channel: 通道类
- class TcpServer: TCP服务端类
- class TcpClient: TCP客户端类
- class UdpServer: UDP服务端类
- class UdpClient: UDP客户端类
- class HttpServer: HTTP服务端类
- Http Lua Handler: HTTP Lua脚本处理器
- class HttpClient: HTTP客户端类
- class WebSocketServer: WebSocket服务端类
- class WebSocketClient: WebSocket客户端类
- class RedisClient: Redis客户端类
rpc
---
Cn/RedisClient
Redis 客户端类
libhv 的 Redis C++ 模块位于仓库根目录 redis/,采用 .h + .cpp 分离结构。第一版只支持单机 Redis 与 RESP2 协议,提供同步 / 异步命令、typed helpers、pipeline、transaction 和 pub/sub。
Redis 模块默认不编译,需要显式开启:
./configure --with-redis && make libhv
或
cmake -S . -B build -DWITH_EVPP=ON -DWITH_REDIS=ON && cmake --build build结果模型
命令的结果严格区分三类语义:客户端错误、Redis 服务端错误回复、nil / 空结果。
``c++
// RESP2 回复类型
enum RedisReplyType {
REDIS_REPLY_NIL, // nil
REDIS_REPLY_STRING, // 字符串 (SimpleString / BulkString)
REDIS_REPLY_ERROR, // 服务端错误回复
REDIS_REPLY_INTEGER, // 整数
REDIS_REPLY_ARRAY, // 数组
};
// 统一底层回复对象
struct RedisReply {
RedisReplyType type;
std::string str; // 字符串 / 错误信息
int64_t integer; // 整数
std::vector<RedisReply> elements; // 数组元素
bool isNil() const;
bool isError() const;
bool isArray() const;
bool isString() const;
const std::string& error() const; // 错误信息
const std::string& asString() const;
int64_t asInt() const;
const std::vector<RedisReply>& asArray() const;
};
// 一次命令调用的统一结果
struct RedisResult {
int code; // 0 表示客户端侧成功
RedisReply reply; // 客户端侧成功时保存 Redis 回复
bool ok() const; // code == 0 且 reply 不是错误回复
};
// typed helper 的结果 (带具体值 value)
template<typename T>
struct RedisValueResult {
int code;
RedisReply reply;
T value;
bool has_value;
bool ok() const; // code == 0 且非错误回复且 has_value
bool isNil() const; // code == 0 且回复为 nil (例如 GET 不存在的 key)
};
语义约定:code == 0-
:客户端侧调用成功,reply有效。code != 0
-:客户端侧失败(未连接、连接失败、超时、断线、协议错误、参数非法等),reply无效。code == 0 && reply.isError()
-:成功收到 Redis 服务端错误回复(如-ERR、-WRONGTYPE、-NOAUTH)。isNil()
-:不是错误,例如GET不存在的 key。HttpClientclass RedisClient
面向多数使用者的主入口,同步 + 异步接口共存,用法与
类似。内部使用独立线程运行事件循环,同步接口在内部等待完成,对外无需操作 future。
/ Detailed source-code truncated for AI context efficiency. /
其中RedisCommand即std::vector<std::string>,RedisCallback为std::function<void(const RedisResult&)>。typed helpers 是对原始命令的薄封装,复用统一的命令编码、reply 解析与错误语义,不构成第二套协议实现。
同步用法
using namespace hv;
RedisClient client;
client.setHost("127.0.0.1");
client.setPort(6379);
client.setConnectTimeout(3000);
client.setTimeout(3000);
// typed helper
if (client.set("key", "hello").ok()) {
RedisValueResult<std::string> v = client.get("key");
if (v.ok()) {
printf("key => %s\n", v.value.c_str());
} else if (v.isNil()) {
printf("key not exists\n");
}
}
// 原始命令
RedisResult r = client.command(RedisCommand{"INCR", "counter"});
if (r.ok()) {
printf("counter = %lld\n", (long long)r.reply.asInt());
}
异步用法
异步命令采用单连接、FIFO 配对模型:按发送顺序取出对应 callback。回调保证只触发一次。
client.getAsync("key", [](const RedisValueResult<std::string>& v) {
if (v.ok()) {
printf("key => %s\n", v.value.c_str());
}
});
client.get(...)注意:不要在异步回调线程里再调用同步接口(如),会因处于事件循环线程内被拒绝并返回ERR_INVALID_HANDLE。execclass RedisPipeline
批量命令对象:先累积命令,
时一次性发送,按顺序返回 N 条回复。整体失败(发送失败 / 超时 / 断线 / 回复不完整)通过RedisResult.code体现;单条命令的错误回复是replies中某一项的合法 error reply。
class RedisPipeline {
void appendCommand(const RedisCommand& command);
RedisResult exec(std::vector<RedisReply>* replies = NULL); // 同步
int execAsync(const RedisRepliesCallback& cb); // 异步
};
RedisPipeline pipe = client.pipeline();
pipe.appendCommand(RedisCommand{"SET", "counter", "1"});
pipe.appendCommand(RedisCommand{"INCR", "counter"});
std::vector<RedisReply> replies;
RedisResult result = pipe.exec(&replies);
if (result.ok()) {
printf("INCR => %lld\n", (long long)replies[1].asInt()); // 2
}
MULTIclass RedisTransaction
封装
/EXEC/DISCARD。exec返回事务结果数组。第一版不把WATCH及其冲突重试作为重点能力。
class RedisTransaction {
void appendCommand(const RedisCommand& command);
RedisResult exec(std::vector<RedisReply>* replies = NULL); // MULTI + 命令 + EXEC
RedisResult discard(); // DISCARD
};
RedisTransaction tx = client.transaction();
tx.appendCommand(RedisCommand{"SET", "k", "7"});
tx.appendCommand(RedisCommand{"GET", "k"});
std::vector<RedisReply> replies;
RedisResult result = tx.exec(&replies);
if (result.ok()) {
printf("GET => %s\n", replies[1].asString().c_str()); // 7
}
TcpClientclass AsyncRedisClient
面向事件循环模型的纯异步客户端,风格贴近
/AsyncHttpClient。RedisClient的异步能力即基于它实现。适合高并发、长连接、批量发送场景。
class AsyncRedisClient {
AsyncRedisClient(EventLoopPtr loop = NULL);
// 连接配置 (同 RedisClient)
void setHost(const std::string& host);
void setPort(int port);
void setAuth(const std::string& password);
void setDb(int db);
void setConnectTimeout(int ms);
void setTimeout(int ms);
void setReconnect(reconn_setting_t* setting);
// 生命周期
void start(bool wait_threads_started = true);
void stop(bool wait_threads_stopped = true);
bool isConnected() const;
bool isStarted() const;
bool isInLoopThread();
// 异步命令
int command(const RedisCommand& command, RedisCallback cb);
int commandBatch(const std::vector<RedisCommand>& commands, RedisRepliesCallback cb);
// 事件回调
std::function<void()> onConnect;
std::function<void()> onClose;
std::function<void(int)> onError;
};
连接断开时,所有尚未完成的 pending 异步请求会统一以客户端错误失败,且不会自动重放未完成命令(Redis 命令可能有副作用,自动重放不安全)。
class RedisSubscriber
独立订阅客户端,专门处理 Pub/Sub。使用独立连接,不与普通命令连接复用状态机。
class RedisSubscriber {
RedisSubscriber(EventLoopPtr loop = NULL);
// 连接配置
void setHost(const std::string& host);
void setPort(int port);
void setAuth(const std::string& password);
void setDb(int db);
void setReconnect(reconn_setting_t* setting);
// 生命周期
void start(bool wait_threads_started = true);
void stop(bool wait_threads_stopped = true);
// 订阅 / 退订
int subscribe(const std::string& channel);
int psubscribe(const std::string& pattern);
int unsubscribe(const std::string& channel);
int punsubscribe(const std::string& pattern);
// 事件回调
std::function<void(const std::string& channel, const std::string& message)> onMessage;
std::function<void(const std::string& name)> onSubscribe;
std::function<void(const std::string& name)> onUnsubscribe;
std::function<void(int code)> onError;
};
RedisSubscriber subscriber;
subscriber.setHost("127.0.0.1");
subscriber.setPort(6379);
subscriber.onMessage = [](const std::string& channel, const std::string& message) {
printf("%s => %s\n", channel.c_str(), message.c_str());
};
subscriber.start();
subscriber.subscribe("news");
WATCH配置了 reconnect 时,连接恢复后会重放当前订阅集合并继续接收推送;已经显式退订的条目不会自动恢复。说明
- 第一版明确不做:Redis Sentinel、Redis Cluster、RESP3、coroutine / future-first 的公开 API、
冲突重试、自动重放未完成命令、大而全的全命令 typed API 覆盖。type-length-value
- 普通命令流与订阅流分离,是第一版设计中的硬边界。测试代码见 examples/redis_client_test.cpp 和 examples/redis_subscriber_test.cpp
---
Cn/Rpc
libhv RPC (hrpc)
hrpc 是 libhv 自带的轻量 RPC,两端均基于 libhv。传输走自定义 TCP,序列化用 protobuf,服务桩代码由 protoc 插件生成。默认不依赖 nghttp2,不追求与 gRPC 线上兼容。
分三层,自底向上:
1. TLV 编解码层:通用的
帧编解码,不依赖 IO,可被任意二进制协议复用。evpp/
2. TLV 三件套():TLVChannel/TLVClient/TLVServer,把 TLV 编解码接到 evpp 事件循环,按整帧投递。rpc/
3. RPC 层():RpcClient/RpcServer继承 TLV 三件套,处理服务路由、请求应答关联、超时、心跳。---
一、TLV 编解码层
TLV 是通用编解码单元,T 和 L 的字节宽度均可配置,让不同协议自由裁剪帧头。
+----------------+------------------+------------------+
| Type (T bytes) | Length (L bytes) | Value (Length) |
+----------------+------------------+------------------+
配置
typedef struct tlv_setting_s {
unsigned char type_bytes; // T 宽度: 0/1/2/4/8, 默认 4; 0 表示无 Type (纯 LV)
unsigned char length_bytes; // L 宽度: 1/2/4, 默认 4; 底层拆包按 32 位处理长度字段,
// >4 会被 tlv_unpack_setting 钳制到 4
bool big_endian; // 默认 true (网络字节序)
} tlv_setting_t;
由配置推算底层unpack_setting_t(UNPACK_BY_LENGTH_FIELD):
length_field_offset = type_bytes
length_field_bytes = length_bytes
body_offset = type_bytes + length_bytes
length_field_coding = big_endian ? ENCODE_BY_BIG_ENDIAN : ENCODE_BY_LITTLE_ENDIAN
TLVMessageTLVMessage 类
Type 用定长字节数组承载(最大 8 字节),既能当整数用,也能按字节位切分子字段。类名用
(而非TLV)以避免与全大写宏冲突;头文件为evpp/TLVMessage.h。
class TLVMessage {
public:
// Type: 原始字节 / 按位 / 整数三种视图
const unsigned char* type() const;
void setType(const void* data, int len);
unsigned char typeAt(int i) const;
void setTypeAt(int i, unsigned char b);
uint64_t typeInt(const tlv_setting_t* setting) const; // 按宽度/字节序解释为整数
void setTypeInt(uint64_t v, const tlv_setting_t* setting);
// Length: 只读,随 setValue 自动维护
uint64_t length() const;
// Value
const char* value() const;
void setValue(const void* data, uint64_t len);
// 编解码 (按 setting 决定 T/L 宽度与字节序)
// pack: 写出 [T|L|V] 到 buf, 返回写入字节数, 不足返回 <0
// unpack: 从 buf 解析 T/L/V (Value 拷贝到内部), 返回整帧长度, 不足返回 <0
int packSize(const tlv_setting_t* setting) const;
int pack(void buf, int cap, const tlv_setting_t setting) const;
int unpack(const void buf, int len, const tlv_setting_t setting);
};
length用uint64_t,声明宽度 8 字节时理论上限 16EB,但实际拆包仍受底层package_max_length限制,不会因为声明大宽度就分配巨型 buffer。WebSocketChannel---
二、TLV 三件套(evpp/)
复刻 libhv 已有的 WebSocket 三件套模式(
/WebSocketClient/WebSocketServer)。TLVChannel : SocketChannel-
—— 提供sendTLV(type, data, len),内部用 TLV 编解码封帧。TLVClient : TcpClientTmpl<TLVChannel>
-—— 构造接受tlv_setting_t(默认 T=4/L=4/大端),自动推算并setUnpack;内部接管基类onMessage做整帧解析,对上层暴露高层回调。TLVServer : TcpServerTmpl<TLVChannel>
-—— 同上。对上层暴露的高层回调(与 WebSocket 风格一致):
// 收到一整帧
std::function<void(const TLVChannelPtr& channel, const TLVMessage& msg)> onmessage;
三件套只负责“完整帧的收发”,不含任何 RPC 语义,可被 IM、游戏等其它 TLV 协议直接继承复用。type_bytes = 8---
三、RPC 层(rpc/)
帧头(Type 的 8 字节切分)
hrpc 令
,把 Type 切成子字段(等价于早期 protorpc 的定长头):"hrpc"| 字节 | 含义 |
|-----------|-------------------------------------------------|
| type[0-3] | magic|1
| type[4] | version =|RpcMessage
| type[5] | 消息类型: REQUEST/RESPONSE/PING/PONG/CLOSE |
| type[6-7] | reserved |- REQUEST / RESPONSE:Value 承载 protobuf 信封
。length_bytes = 4
- PING / PONG:心跳,Value 为空。
- CLOSE:优雅关闭,Value 为空。(Value 即信封长度)。信封 rpc.proto
syntax = "proto3";
package hv.rpc;
message RpcMessage {
uint64 id = 1; // 请求/响应关联 id
string method = 2; // "package.Service.Method"
int32 status = 3; // 0=OK, 非0=错误码 (仅 response)
string message = 4; // 错误描述 (仅 response)
bytes payload = 5; // 用户 request/response message 的序列化
}
一次 parse 信封即拿到路由信息(method)、关联 id、错误状态;payload再由生成的桩代码按具体类型 parse。RpcServer
class RpcServer : public TLVServer {
public:
// 注册一个 service (由 codegen 生成的 RpcService 子类)
void registerService(const std::shared_ptr<RpcService>& service);
};
RpcService是 codegen 生成的服务基类,内部持有method -> handler表并向RpcServer注册。收到 REQUEST 帧后,RpcServer解析信封、按method路由到对应 service,执行后把结果打包成 RESPONSE 帧回写。registerService()线程约束(重要)-必须在start()之前调用:method 表在 IO 线程无锁读取,start 后再注册是数据竞争。- service 方法同步运行在连接所属的 IO 线程上,同一连接的所有调用串行执行。慢 handler 会阻塞该 IO 线程上的其它连接。重活请交给自己的工作线程/线程池处理后再回复。RpcClient
class RpcStatus { // RPC 调用结果 (区别于 evpp/Status.h 的生命周期状态)
public:
int code; // 0=OK
std::string message;
bool ok() const { return code == 0; }
};
class RpcClient : public TLVClient {
public:
// 同步 (禁止在所属 loop 线程内调用, 内部 future 等待)
RpcStatus call(const std::string& method, const std::string& reqData,
std::string* respData, int timeout_ms = 10000);
// 异步 (回调在 loop 线程触发)
void callAsync(const std::string& method, const std::string& reqData,
std::function<void(const RpcStatus&, const std::string&)> cb,
int timeout_ms = 10000);
void setPingInterval(int ms); // 0 关闭心跳; 默认 3000ms
};
- 内部维护id -> 调用上下文表,收到 RESPONSE 按 id 匹配。TcpClient
- 复用的重连、EventLoop 所有权体系。timeout_ms
- 超时:同步靠 future 等待;异步挂一个 loop 定时器,到点回调HRPC_STATUS_TIMEOUT。HRPC_STATUS_NOT_CONNECTED
- 断线:连接关闭时,所有在途调用立即以失败(不会挂死、不泄漏上下文)。setPingInterval
- 心跳:连接建立后定时发 PING(,默认 3000ms),收 PONG 重置计数;连续 3 次无 PONG 主动关闭连接(触发重连)。对端 PING 自动回 PONG。---
四、代码生成(rpc/protoc-gen-hrpc)
标准 protobuf service 定义:
service Calc {
rpc Add (CalcRequest) returns (CalcReply);
}
protoc-gen-hrpc是 protoc 插件(依赖libprotoc),protoc 把已解析的 AST 从 stdin 传入,插件遍历ServiceDescriptor套模板输出calc.hrpc.h,无需自己写任何 IDL 解析。生成产物:
Server —— 用户继承实现纯虚方法
class CalcService : public hv::rpc::RpcService {
public:
virtual hv::rpc::RpcStatus Add(const CalcRequest& req, CalcReply* reply) = 0;
// 自动填充 method 表: "Calc.Add" -> 内部 trampoline (parse payload -> Add -> serialize)
};
Client —— 生成类型安全的 stubclass CalcStub {
public:
explicit CalcStub(hv::rpc::RpcClient* client);
// 同步
hv::rpc::RpcStatus Add(const CalcRequest& req, CalcReply* reply, int timeout_ms = 10000);
// 异步
void Add(const CalcRequest& req,
std::function<void(const hv::rpc::RpcStatus&, const CalcReply&)> cb);
};
生成方式沿用examples/protorpc/proto/protoc.sh的一键脚本风格:
protoc --plugin=protoc-gen-hrpc=./protoc-gen-hrpc \
--cpp_out=. --hrpc_out=. calc.proto
---libhrpc五、构建与使用
hrpc 依赖 protobuf,因此单独编译成
,让libhv本身保持零 protobuf 依赖。三者关系:
用户代码 (含 protoc-gen-hrpc 生成的 xxx.hrpc.h)
→ libhrpc (RpcClient/RpcServer + rpc.pb 信封) 依赖 protobuf
→ libhv (TLV 三件套 + evpp + event...) 零 protobuf
- TLV 三件套(evpp/TLVMessage.h+TLV{Channel,Client,Server}.h)不依赖 protobuf,随 evpp 一起编译安装(include/hv/),可被其它二进制协议独立复用。RpcClient.cpp
- libhrpc(可选,默认关):/RpcServer.cpp+ 信封rpc.pb.cc,链接 libhv + protobuf。头文件安装到include/hv/rpc/。bin/
- protoc-gen-hrpc 插件安装到,用户用它生成 service stub。
- 现代 protobuf/abseil 头要求 C++17,故 libhrpc 以 C++17 编译;libhv/TLV 仍是 C++11。编译 libhrpc
Makefile (homebrew 环境指定 protobuf 前缀)
./configure --with-rpc
make libhv && make libhrpc PROTOBUF_PREFIX=/opt/homebrew
sudo make install WITH_RPC=yes # 安装 libhrpc + include/hv/rpc + bin/protoc-gen-hrpc
CMake
cmake .. -DWITH_RPC=ON -DCMAKE_PREFIX_PATH=/opt/homebrew
cmake --build . --target hrpc
用户使用(基于发布产物)
1. 用安装的插件生成 stub
protoc --plugin=protoc-gen-hrpc=$(which protoc-gen-hrpc) \
--cpp_out=. --hrpc_out=. myservice.proto
2. 编译链接: -lhrpc -lhv -lprotobuf
g++ -std=c++17 myapp.cpp myservice.pb.cc \
-I<prefix>/include/hv -I<prefix>/include/hv/rpc \
-lhrpc -lhv -lprotobuf
代码里#include <hv/rpc/RpcClient.h>+#include "myservice.hrpc.h"即可。---
六、范围
第一版包含:unary RPC、同步 + 异步调用、service/method 路由、错误传递、超时、心跳(PING/PONG)、优雅关闭(CLOSE)、断线重连。
暂不包含(后续):streaming(server/client/bidi stream)、TLS(后续复用 libhv ssl 层)、Lua 绑定。
---
Cn/TcpClient
TCP 客户端类
class TcpClient {
// 返回所在的事件循环
const EventLoopPtr& loop();
// 创建套接字
int createsocket(int remote_port, const char* remote_host = "127.0.0.1");
int createsocket(struct sockaddr* remote_addr);
// 绑定端口
int bind(int local_port, const char* local_host = "0.0.0.0");
int bind(struct sockaddr* local_addr);
// 关闭套接字
void closesocket();
// 开始运行
void start(bool wait_threads_started = true);
// 停止运行
void stop(bool wait_threads_stopped = true);
// 是否已连接
bool isConnected();
// 发送
int send(const void* data, int size);
int send(Buffer* buf);
int send(const std::string& str);
// 设置SSL/TLS加密通信
int withTLS(hssl_ctx_opt_t* opt = NULL);
// 设置连接超时
void setConnectTimeout(int ms);
// 设置重连
void setReconnect(reconn_setting_t* setting);
// 是否是重连
bool isReconnect();
// 设置拆包规则
void setUnpack(unpack_setting_t* setting);
// 连接状态回调
std::function<void(const TSocketChannelPtr&)> onConnection;
// 消息回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// 写完成回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
};
测试代码见 evpp/TcpClient_test.cpp---
Cn/TcpServer
TCP 服务端类
class TcpServer {
// 返回索引的事件循环
EventLoopPtr loop(int idx = -1);
// 创建套接字
int createsocket(int port, const char* host = "0.0.0.0");
// 关闭套接字
void closesocket();
// 设置最大连接数
void setMaxConnectionNum(uint32_t num);
// 设置负载均衡策略
void setLoadBalance(load_balance_e lb);
// 设置线程数
void setThreadNum(int num);
// 开始运行
void start(bool wait_threads_started = true);
// 停止运行
void stop(bool wait_threads_stopped = true);
// 设置SSL/TLS加密通信
int withTLS(hssl_ctx_opt_t* opt = NULL);
// 设置拆包规则
void setUnpack(unpack_setting_t* setting);
// 返回当前连接数
size_t connectionNum();
// 遍历连接
int foreachChannel(std::function<void(const TSocketChannelPtr& channel)> fn);
// 广播消息
int broadcast(const void* data, int size);
int broadcast(const std::string& str);
// 连接到来/断开回调
std::function<void(const TSocketChannelPtr&)> onConnection;
// 消息回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// 写完成回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
};
测试代码见 evpp/TcpServer_test.cpp---
Cn/UdpClient
UDP 客户端类
class UdpClient {
// 返回所在的事件循环
const EventLoopPtr& loop();
// 创建套接字
int createsocket(int remote_port, const char* remote_host = "127.0.0.1");
// 绑定端口
int bind(int local_port, const char* local_host = "0.0.0.0");
// 关闭套接字
void closesocket();
// 开始运行
void start(bool wait_threads_started = true);
// 停止运行
void stop(bool wait_threads_stopped = true);
// 发送
int sendto(const void data, int size, struct sockaddr peeraddr = NULL);
int sendto(Buffer buf, struct sockaddr peeraddr = NULL);
int sendto(const std::string& str, struct sockaddr* peeraddr = NULL);
// 设置KCP
void setKcp(kcp_setting_t* setting);
// 消息回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// 写完成回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
};
测试代码见 evpp/UdpClient_test.cpp---
Cn/UdpServer
UDP 服务端类
class UdpServer {
// 返回所在的事件循环
const EventLoopPtr& loop();
// 创建套接字
int createsocket(int port, const char* host = "0.0.0.0");
// 关闭套接字
void closesocket();
// 开始运行
void start(bool wait_threads_started = true);
// 停止运行
void stop(bool wait_threads_stopped = true);
// 发送
int sendto(const void data, int size, struct sockaddr peeraddr = NULL);
int sendto(Buffer buf, struct sockaddr peeraddr = NULL);
int sendto(const std::string& str, struct sockaddr* peeraddr = NULL);
// 设置KCP
void setKcp(kcp_setting_t* setting);
// 消息回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onMessage;
// 写完成回调
std::function<void(const TSocketChannelPtr&, Buffer*)> onWriteComplete;
};
测试代码见 evpp/UdpServer_test.cpp---
Cn/WebSocketClient
WebSocket 客户端类
class WebSocketClient {
// 打开回调
std::function<void()> onopen;
// 关闭回调
std::function<void()> onclose;
// 消息回调
std::function<void(const std::string& msg)> onmessage;
// 打开
int open(const char* url, const http_headers& headers = DefaultHeaders);
// 关闭
int close();
// 发送
int send(const std::string& msg);
int send(const char* buf, int len, enum ws_opcode opcode = WS_OPCODE_BINARY);
// 设置心跳间隔
void setPingInterval(int ms);
// 设置WebSocket握手阶段的HTTP请求
void setHttpRequest(const HttpRequestPtr& req);
// 获取WebSocket握手阶段的HTTP响应
const HttpResponsePtr& getHttpResponse();
};
测试代码见 examples/websocket_client_test.cpp---
Cn/WebSocketServer
WebSocket 服务端类
// WebSocketServer 继承自 HttpServer
class WebSocketServer : public HttpServer {
// 注册WebSocket业务类
void registerWebSocketService(WebSocketService* service);
};
// WebSocket业务类
struct WebSocketService {
// 打开回调
std::function<void(const WebSocketChannelPtr&, const HttpRequestPtr&)> onopen;
// 消息回调
std::function<void(const WebSocketChannelPtr&, const std::string&)> onmessage;
// 关闭回调
std::function<void(const WebSocketChannelPtr&)> onclose;
// 心跳间隔
int ping_interval;
};
测试代码见 examples/websocket_server_test.cpphv::MutexLock---
API
libhv API Manual
base
hplatform.h
- OS: OS_WIN, OS_UNIX (OS_LINUX, OS_ANDROID, OS_DARWIN ...)
- ARCH: ARCH_X86, ARCH_X64, ARCH_ARM, ARCH_ARM64
- COMPILER: COMPILER_MSVC, COMPILER_MINGW, COMPILER_GCC, COMPILER_CLANG
- BYTE_ORDER: BIG_ENDIAN, LITTLE_ENDIAN
- stdbool.h: bool, true, false
- stdint.h: int8_t, int16_t, int32_t, int64_t
- hv_sleep, hv_msleep, hv_usleep, hv_delay
- hv_mkdir
- stricmp, strcasecmphexport.h
- HV_EXPORT, HV_INLINE
- HV_SOURCE, HV_STATICLIB, HV_DYNAMICLIB
- HV_DEPRECATED
- HV_UNUSED
- EXTERN_C, BEGIN_EXTERN_C, END_EXTERN_C
- BEGIN_NAMESPACE, END_NAMESPACE, USING_NAMESPACE
- DEFAULT
- ENUM, STRUCT
- IN, OUT, INOUT
- OPTIONAL, REQUIRED, REPEATEDhdef.h
- ABS, NABS
- ARRAY_SIZE
- BITSET, BITCLR, BITGET
- CR, LF, CRLF
- FLOAT_EQUAL_ZERO
- INFINITE
- IS_ALPHA, IS_DIGIT, IS_ALPHANUM
- IS_CNTRL, IS_GRAPH
- IS_HEX
- IS_LOWER, IS_UPPER
- LOWER, UPPER
- MAKEWORD, LOBYTE, HIBYTE
- MAKELONG, LOWORD, HIWORD
- MAKEINT64, LOINT, HIINT
- MAKE_FOURCC
- MAX, MIN, LIMIT
- MAX_PATH
- NULL, TRUE, FALSE
- SAFE_FREE, SAFE_DELETE, SAFE_DELETE_ARRAY, SAFE_RELEASE
- STRINGCAT
- STRINGIFY
- offsetof, offsetofend
- container_of
- prefetch
- printd, printehatomic.h
- hatomic_flag_t, hatomic_t
- hatomic_flag_test_and_set
- hatomic_flag_clear
- hatomic_add
- hatomic_sub
- hatomic_inc
- hatomic_decherr.h
- hv_strerrorhtime.h
- IS_LEAP_YEAR
- datetime_t
- gettick_ms
- gettimeofday
- gettimeofday_ms
- gettimeofday_us
- gethrtime_us
- datetime_now
- datetime_localtime
- datetime_mktime
- datetime_past
- datetime_future
- duration_fmt
- datetime_fmt
- gmtime_fmt
- days_of_month
- month_atoi
- month_itoa
- weekday_atoi
- weekday_itoa
- hv_compile_datetime
- cron_next_timeouthmath.h
- floor2e
- ceil2e
- varint_encode
- varint_decodehbase.h
- hv_malloc
- hv_calloc
- hv_realloc
- hv_zalloc
- hv_strncpy
- hv_strncat
- hv_strlower
- hv_strupper
- hv_strreverse
- hv_strstartswith
- hv_strendswith
- hv_strcontains
- hv_wildcard_match
- hv_strnchr
- hv_strrchr_dot
- hv_strrchr_dir
- hv_basename
- hv_suffixname
- hv_mkdir_p
- hv_rmdir_p
- hv_exists
- hv_isdir
- hv_isfile
- hv_islink
- hv_filesize
- get_executable_path
- get_executable_dir
- get_executable_file
- get_run_dir
- hv_rand
- hv_random_string
- hv_getboolean
- hv_parse_size
- hv_parse_time
- hv_parse_urlhversion.h
- hv_version
- hv_compile_version
- version_atoi
- version_itoahsysinfo.h
- get_ncpu
- get_meminfohproc.h
- hproc_spawnhthread.h
- hv_getpid
- hv_gettid
- HTHREAD_RETTYPE
- HTHREAD_ROUTINE
- hthread_create
- hthread_join
- class HThreadhmutex.h
- hmutex_t
- hmutex_init
- hmutex_destroy
- hmutex_lock
- hmutex_unlock
- hspinlock_t
- hspinlock_init
- hspinlock_destroy
- hspinlock_lock
- hspinlock_unlock
- hrwlock_t
- hrwlock_init
- hrwlock_destroy
- hrwlock_rdlock
- hrwlock_rdunlock
- hrwlock_wrlock
- hrwlock_wrunlock
- htimed_mutex_t
- htimed_mutex_init
- htimed_mutex_destroy
- htimed_mutex_lock
- htimed_mutex_lock_for
- htimed_mutex_unlock
- hcondvar_t
- hcondvar_init
- hcondvar_destroy
- hcondvar_wait
- hcondvar_wait_for
- hcondvar_signal
- hcondvar_broadcast
- hsem_init
- hsem_destroy
- hsem_wait
- hsem_post
- hsem_timedwait
- honce_t
- HONCE_INIT
- honce
- classhv::SpinLock
- classhv::RWLock
- classhv::LockGuard
- classlibevent, libev, and libuv
- synchronizedhsocket.h
- INVALID_SOCKET
- closesocket
- blocking
- nonblocking
- Bind
- Listen
- Connect
- ConnectNonblock
- ConnectTimeout
- ResolveAddr
- Socketpair
- socket_errno
- socket_strerror
- sockaddr_u
- sockaddr_ip
- sockaddr_port
- sockaddr_set_ip
- sockaddr_set_port
- sockaddr_set_ipport
- sockaddr_len
- sockaddr_str
- sockaddr_print
- SOCKADDR_LEN
- SOCKADDR_STR
- SOCKADDR_PRINT
- tcp_nodelay
- tcp_nopush
- tcp_keepalive
- udp_broadcast
- ip_v6only
- so_sndtimeo
- so_rcvtimeo
- so_sndbuf
- so_rcvbuf
- so_reuseaddr
- so_reuseport
- so_lingerhlog.h
- default_logger
- file_logger
- stderr_logger
- stdout_logger
- logger_create
- logger_destroy
- logger_enable_color
- logger_enable_fsync
- logger_fsync
- logger_print
- logger_set_file
- logger_set_handler
- logger_set_level
- logger_set_max_bufsize
- logger_set_max_filesize
- logger_set_remain_days
- logger_set_truncate_percent
- logger_get_cur_file
- hlogd, hlogi, hlogw, hloge, hlogf
- LOGD, LOGI, LOGW, LOGE, LOGFhbuf.h
- hbuf_t
- offset_buf_t
- class HBuf
- class HVLBuf
- class HRingBufhmain.h
- main_ctx_init
- parse_opt
- parse_opt_long
- dump_opt_long
- get_arg
- get_env
- setproctitle
- signal_init
- signal_handle
- create_pidfile
- delete_pidfile
- getpid_form_pidfile
- master_workers_runhstring.h
- to_string
- from_string
- toupper
- tolower
- reverse
- startswith
- endswith
- contains
- asprintf
- trim
- ltrim
- rtrim
- trim_pairs
- split
- splitKV
- replace
- replaceAllhfile.h
- class HFilehpath.h
- exists
- isdir
- isfile
- islink
- basename
- dirname
- filename
- suffixname
- joinhdir.h
- listdirhurl.h
- HUrl::escape
- HUrl::unescape
- HUrl::parse
- HUrl::dumphscope.h
- defer
- template ScopeCleanup
- template ScopeFree
- template ScopeDelete
- template ScopeDeleteArray
- template ScopeRelease
- template ScopeLockifconfig.h
- ifconfigutils
md5.h
- HV_MD5Init
- HV_MD5Update
- HV_MD5Final
- hv_md5
- hv_md5_hexsha1.h
- HV_SHA1Init
- HV_SHA1Update
- HV_SHA1Final
- HV_SHA1
- hv_sha1
- hv_sha1_hexbase64.h
- hv_base64_decode
- hv_base64_encodejson.hpp
- json::parse
- json::dumpsingleton.h
- DISABLE_COPY
- SINGLETON_DECL
- SINGLETON_IMPLevent
hloop.h
- hloop_create_tcp_client
- hloop_create_tcp_server
- hloop_create_udp_client
- hloop_create_udp_server
- hloop_create_ssl_client
- hloop_create_ssl_server
- hloop_new
- hloop_free
- hloop_run
- hloop_stop
- hloop_pause
- hloop_resume
- hloop_status
- hloop_pid
- hloop_tid
- hloop_now
- hloop_now_ms
- hloop_now_us
- hloop_update_time
- hloop_set_userdata
- hloop_userdata
- hloop_wakeup
- hloop_post_event
- hevent_loop
- hevent_type
- hevent_id
- hevent_priority
- hevent_userdata
- hevent_set_priority
- hevent_ser_userdata
- haccept
- hconnect
- hread
- hwrite
- hrecv
- hsend
- hrecvfrom
- hsendto
- hio_add
- hio_del
- hio_get
- hio_detach
- hio_attach
- hio_read
- hio_read_start
- hio_read_stop
- hio_read_once
- hio_read_until
- hio_read_until_length
- hio_read_until_delim
- hio_read_readline
- hio_read_readstring
- hio_read_readbytes
- hio_write
- hio_close
- hio_accept
- hio_connect
- hio_fd
- hio_id
- hio_type
- hio_error
- hio_localaddr
- hio_peeraddr
- hio_events
- hio_revents
- hio_is_opened
- hio_is_closed
- hio_enable_ssl
- hio_is_ssl
- hio_get_ssl
- hio_set_ssl
- hio_get_ssl_ctx
- hio_set_ssl_ctx
- hio_new_ssl_ctx
- hio_setcb_accept
- hio_setcb_connect
- hio_setcb_read
- hio_setcb_write
- hio_setcb_close
- hio_getcb_accept
- hio_getcb_connect
- hio_getcb_read
- hio_getcb_write
- hio_getcb_close
- hio_set_type
- hio_set_localaddr
- hio_set_peeraddr
- hio_set_readbuf
- hio_set_connect_timeout
- hio_set_close_timeout
- hio_set_read_timeout
- hio_set_write_timeout
- hio_set_keepalive_timeout
- hio_set_heartbeat
- hio_set_unpack
- hio_unset_unpack
- hio_read_upstream
- hio_write_upstream
- hio_close_upstream
- hio_setup_upstream
- hio_get_upstream
- hio_setup_tcp_upstream
- hio_setup_ssl_upstream
- hio_setup_udp_upstream
- hio_create_socket
- hio_create_pipe
- hio_context
- hio_set_context
- htimer_add
- htimer_add_period
- htimer_del
- htimer_reset
- hidle_add
- hidle_del
- hsignal_add
- hsignal_delnlog.h
- network_logger
- nlog_listenevpp
- class Buffer
- class Channel
- class Event
- class EventLoop
- class EventLoopThread
- class EventLoopThreadPool
- class TcpClient
- class TcpServer
- class UdpClient
- class UdpServerssl
- hssl_ctx_init
- hssl_ctx_cleanup
- hssl_ctx_instance
- hssl_ctx_new
- hssl_ctx_free
- hssl_new
- hssl_free
- hssl_accept
- hssl_connnect
- hssl_read
- hssl_write
- hssl_close
- hssl_set_sni_hostnameprotocol
dns.h
- dns_name_decode
- dns_name_encode
- dns_pack
- dns_unpack
- dns_rr_pack
- dns_rr_unpack
- dns_query
- dns_free
- nslookupftp.h
- ftp_command_str
- ftp_connect
- ftp_login
- ftp_exec
- ftp_upload
- ftp_download
- ftp_download_with_cb
- ftp_quit
- ftp_status_strsmtp.h
- smtp_command_str
- smtp_status_str
- smtp_build_command
- sendmailicmp.h
- pinghttp
- class HttpMessage
- class HttpRequest
- class HttpResponse
- class HttpParserhttpdef.h
- http_content_type_enum
- http_content_type_enum_by_suffix
- http_content_type_str
- http_content_type_str_by_suffix
- http_content_type_suffix
- http_errno_description
- http_errno_name
- http_method_enum
- http_method_str
- http_status_enum
- http_status_strhttp_content.h
- parse_query_params
- parse_json
- parse_multipart
- dump_query_params
- dump_json
- dump_multipartHttpClient.h
- http_client_new
- http_client_del
- http_client_send
- http_client_send_async
- http_client_strerror
- http_client_set_timeout
- http_client_set_header
- http_client_del_header
- http_client_get_header
- http_client_clear_headers
- http_client_set_http_proxy
- http_client_set_https_proxy
- http_client_add_no_proxy
- class HttpClientrequests.h
- requests::request
- requests::get
- requests::post
- requests::put
- requests::patch
- requests::Delete
- requests::head
- requests::asyncaxios.h
- axios::axios
- axios::get
- axios::post
- axios::put
- axios::patch
- axios::Delete
- axios::head
- axios::asyncHttpServer.h
- http_server_run
- http_server_stop
- class HttpService
- class HttpServerWebSocketClient.h
- class WebSocketClientWebSocketServer.h
- websocket_server_run
- websocket_server_stop
- class WebSocketService
- class WebSocketServermqtt
- mqtt_client_new
- mqtt_client_free
- mqtt_client_run
- mqtt_client_stop
- mqtt_client_set_id
- mqtt_client_set_will
- mqtt_client_set_auth
- mqtt_client_set_callback
- mqtt_client_set_userdata
- mqtt_client_get_userdata
- mqtt_client_get_last_error
- mqtt_client_set_ssl_ctx
- mqtt_client_new_ssl_ctx
- mqtt_client_set_reconnect
- mqtt_client_reconnect
- mqtt_client_set_connect_timeout
- mqtt_client_connect
- mqtt_client_is_connected
- mqtt_client_disconnect
- mqtt_client_publish
- mqtt_client_subscribe
- mqtt_client_unsubscribe
- class MqttClientother
- class HThreadPool
- class HObjectPool
- class ThreadLocalStorage---
PLAN
Done
- base: cross platfrom infrastructure
- event: select/poll/epoll/wepoll/kqueue/port/io_uring
- ssl: openssl/gnutls/mbedtls/wintls/appletls
- rudp: KCP
- evpp: c++ EventLoop interface similar to muduo and evpp
- http client/server: include https http1/x http2
- http server sync/async/ctx/state/script handlers
- websocket client/server
- mqtt client
- redis client
- async DNS
- lua binding
- hrpc = libhv + protobufPlan
- js binding
- rudp: FEC, ARQ, UDT, QUIC
- coroutine
- cppsocket.io
- IM-libhv
- MediaServer-libhv
- GameServer-libhv---
README
English | 中文
libhv
[](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
[](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
[](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
[](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
[](https://github.com/ithewei/libhv/actions/workflows/CI.yml?query=branch%3Amaster)
[](https://github.com/ithewei/libhv/actions/workflows/benchmark.yml?query=branch%3Amaster)
<br>
[](https://github.com/ithewei/libhv/releases)
[](https://github.com/ithewei/libhv/stargazers)
[](https://github.com/ithewei/libhv/forks)
[](https://github.com/ithewei/libhv/issues)
[](https://github.com/ithewei/libhv/pulls)
[](https://github.com/ithewei/libhv/contributors)
[](LICENSE)
<br>
[](https://gitee.com/libhv/libhv)
[](https://github.com/oz123/awesome-c)
[](https://github.com/fffaraz/awesome-cpp)Like
,libhvprovides event-loop with non-blocking IO and timer,
but simpler api and richer protocols.✨ Features
- Cross-platform (Linux, Windows, macOS, Android, iOS, BSD, Solaris)
- High-performance EventLoop (IO, timer, idle, custom, signal)
- TCP/UDP client/server/proxy
- TCP supports heartbeat, reconnect, upstream, MultiThread-safe write and close, etc.
- Built-in common unpacking modes (FixedLength, Delimiter, LengthField)
- RUDP support: WITH_KCP
- SSL/TLS support: (via WITH_OPENSSL or WITH_GNUTLS or WITH_MBEDTLS)
- HTTP client/server (support https http1/x http2 grpc)
- HTTP supports static service, indexof service, forward/reverse proxy service, sync/async API handler
- HTTP supports RESTful, router, middleware, keep-alive, chunked, SSE, etc.
- WebSocket client/server
- MQTT client
- Redis client⌛️ Build
see BUILD.md
Makefile:
./configure
make
sudo make install
or cmake:mkdir build
cd build
cmake ..
cmake --build .
or bazel:bazel build libhv
or vcpkg:vcpkg install libhv
or xmake:xrepo install libhv
./getting_started.sh⚡️ Getting Started
run
:
git clone https://github.com/ithewei/libhv.git
cd libhv
./configure
make
bin/httpd -h
bin/httpd -d
#bin/httpd -c etc/httpd.conf -s restart -d
ps aux | grep httpd
http file service
bin/curl -v localhost:8080
http indexof service
bin/curl -v localhost:8080/downloads/
http api service
bin/curl -v localhost:8080/ping
bin/curl -v localhost:8080/echo -d "hello,world!"
bin/curl -v localhost:8080/query?page_no=1\&page_size=10
bin/curl -v localhost:8080/kv -H "Content-Type:application/x-www-form-urlencoded" -d 'user=admin&pswd=123456'
bin/curl -v localhost:8080/json -H "Content-Type:application/json" -d '{"user":"admin","pswd":"123456"}'
bin/curl -v localhost:8080/form -F 'user=admin' -F 'pswd=123456'
bin/curl -v localhost:8080/upload -d "@LICENSE"
bin/curl -v localhost:8080/upload -F "file=@LICENSE"
bin/curl -v localhost:8080/test -H "Content-Type:application/x-www-form-urlencoded" -d 'bool=1&int=123&float=3.14&string=hello'
bin/curl -v localhost:8080/test -H "Content-Type:application/json" -d '{"bool":true,"int":123,"float":3.14,"string":"hello"}'
bin/curl -v localhost:8080/test -F 'bool=1' -F 'int=123' -F 'float=3.14' -F 'string=hello'
RESTful API: /group/:group_name/user/:user_id
bin/curl -v -X DELETE localhost:8080/group/test/user/123
benchmark
bin/wrk -c 1000 -d 10 -t 4 http://127.0.0.1:8080/
TCP
#### tcp server
c version: examples/tcp_echo_server.cc++ version: evpp/TcpServer_test.cpp
#include "TcpServer.h"
using namespace hv;
int main() {
int port = 1234;
TcpServer srv;
int listenfd = srv.createsocket(port);
if (listenfd < 0) {
return -1;
}
printf("server listen on port %d, listenfd=%d ...\n", port, listenfd);
srv.onConnection = [](const SocketChannelPtr& channel) {
std::string peeraddr = channel->peeraddr();
if (channel->isConnected()) {
printf("%s connected! connfd=%d\n", peeraddr.c_str(), channel->fd());
} else {
printf("%s disconnected! connfd=%d\n", peeraddr.c_str(), channel->fd());
}
};
srv.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
// echo
channel->write(buf);
};
srv.setThreadNum(4);
srv.start();
// press Enter to stop
while (getchar() != '\n');
return 0;
}
#### tcp client
c version: examples/tcp_client_test.cc++ version: evpp/TcpClient_test.cpp
#include <iostream>
#include "TcpClient.h"
using namespace hv;
int main() {
int port = 1234;
TcpClient cli;
int connfd = cli.createsocket(port);
if (connfd < 0) {
return -1;
}
cli.onConnection = [](const SocketChannelPtr& channel) {
std::string peeraddr = channel->peeraddr();
if (channel->isConnected()) {
printf("connected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
} else {
printf("disconnected to %s! connfd=%d\n", peeraddr.c_str(), channel->fd());
}
};
cli.onMessage = [](const SocketChannelPtr& channel, Buffer* buf) {
printf("< %.s\n", (int)buf->size(), (char)buf->data());
};
cli.start();
std::string str;
while (std::getline(std::cin, str)) {
if (str == "close") {
cli.closesocket();
} else if (str == "start") {
cli.start();
} else if (str == "stop") {
cli.stop();
break;
} else {
if (!cli.isConnected()) break;
cli.send(str);
}
}
return 0;
}
HTTP
#### http server
see examples/http_server_test.cppgolang gin style
#include "HttpServer.h"
using namespace hv;
int main() {
HttpService router;
router.GET("/ping", [](HttpRequest req, HttpResponse resp) {
return resp->String("pong");
});
router.GET("/data", [](HttpRequest req, HttpResponse resp) {
static char data[] = "0123456789";
return resp->Data(data, 10);
});
router.GET("/paths", &router {
return resp->Json(router.Paths());
});
router.GET("/get", [](HttpRequest req, HttpResponse resp) {
resp->json["origin"] = req->client_addr.ip;
resp->json["url"] = req->url;
resp->json["args"] = req->query_params;
resp->json["headers"] = req->headers;
return 200;
});
router.POST("/echo", [](const HttpContextPtr& ctx) {
return ctx->send(ctx->body(), ctx->type());
});
HttpServer server(&router);
server.setPort(8080);
server.setThreadNum(4);
server.run();
return 0;
}
#### http client
see examples/http_client_test.cpppython requests style
#include "requests.h"
int main() {
auto resp = requests::get("http://www.example.com");
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%s\n", resp->body.c_str());
}
resp = requests::post("127.0.0.1:8080/echo", "hello,world!");
if (resp == NULL) {
printf("request failed!\n");
} else {
printf("%s\n", resp->body.c_str());
}
return 0;
}
WebSocket
#### WebSocket server
see examples/websocket_server_test.cpp#include "WebSocketServer.h"
using namespace hv;
int main(int argc, char argv) {
WebSocketService ws;
ws.onopen = [](const WebSocketChannelPtr& channel, const HttpRequestPtr& req) {
printf("onopen: GET %s\n", req->Path().c_str());
};
ws.onmessage = [](const WebSocketChannelPtr& channel, const std::string& msg) {
printf("onmessage: %.*s\n", (int)msg.size(), msg.data());
};
ws.onclose = [](const WebSocketChannelPtr& channel) {
printf("onclose\n");
};
WebSocketServer server(&ws);
server.setPort(9999);
server.setThreadNum(4);
server.run();
return 0;
}
#### WebSocket client
see examples/websocket_client_test.cpp#include "WebSocketClient.h"
using namespace hv;
int main(int argc, char argv) {
WebSocketClient ws;
ws.onopen = []() {
printf("onopen\n");
};
ws.onmessage = [](const std::string& msg) {
printf("onmessage: %.*s\n", (int)msg.size(), msg.data());
};
ws.onclose = []() {
printf("onclose\n");
};
// reconnect: 1,2,4,8,10,10,10...
reconn_setting_t reconn;
reconn_setting_init(&reconn);
reconn.min_delay = 1000;
reconn.max_delay = 10000;
reconn.delay_policy = 2;
ws.setReconnect(&reconn);
ws.open("ws://127.0.0.1:9999/test");
std::string str;
while (std::getline(std::cin, str)) {
if (!ws.isConnected()) break;
if (str == "quit") {
ws.close();
break;
}
ws.send(str);
}
return 0;
}
redis/Redis
see examples/redis_client_test.cpp and examples/redis_subscriber_test.cppThe Redis C++ module lives in the repository root
and follows the same.h + .cppsplit used by other libhv modules. Redis is disabled by default; enable it explicitly with./configure --with-redisorcmake -DWITH_REDIS=ON.
./configure --with-redis
make unittest
cmake -S . -B build -DWITH_EVPP=ON -DWITH_REDIS=ON -DBUILD_UNITTEST=ON
cmake --build build --target redis_protocol_test redis_async_client_test redis_client_test redis_batch_test redis_subscriber_test
pingpong echo-servers🍭 More examples
c version
- examples/hloop_test.c
- examples/htimer_test.c
- examples/pipe_test.c
- examples/tcp_echo_server.c
- examples/tcp_chat_server.c
- examples/tcp_proxy_server.c
- examples/udp_echo_server.c
- examples/udp_proxy_server.c
- examples/socks5_proxy_server.c
- examples/tinyhttpd.c
- examples/tinyproxyd.c
- examples/jsonrpc
- examples/mqtt
- examples/multi-thread/multi-acceptor-processes.c
- examples/multi-thread/multi-acceptor-threads.c
- examples/multi-thread/one-acceptor-multi-workers.cc++ version
- evpp/EventLoop_test.cpp
- evpp/EventLoopThread_test.cpp
- evpp/EventLoopThreadPool_test.cpp
- evpp/TimerThread_test.cpp
- evpp/TcpServer_test.cpp
- evpp/TcpClient_test.cpp
- evpp/UdpServer_test.cpp
- evpp/UdpClient_test.cpp
- examples/http_server_test.cpp
- examples/http_client_test.cpp
- examples/websocket_server_test.cpp
- examples/websocket_client_test.cpp
- examples/redis_client_test.cpp
- examples/redis_subscriber_test.cpp
- examples/protorpc
- hv-projects/QtDemosimulate well-known command line tools
- examples/nc
- examples/nmap
- examples/httpd
- examples/wrk
- examples/curl
- examples/wget
- examples/consul
- examples/kcptun🥇 Benchmark
cd echo-servers
./build.sh
./benchmark.sh
throughput:libevent running on port 2001
libev running on port 2002
libuv running on port 2003
libhv running on port 2004
asio running on port 2005
poco running on port 2006
==============2001=====================================
[127.0.0.1:2001] 4 threads 1000 connections run 10s
total readcount=1616761 readbytes=1655563264
throughput = 157 MB/s
==============2002=====================================
[127.0.0.1:2002] 4 threads 1000 connections run 10s
total readcount=2153171 readbytes=2204847104
throughput = 210 MB/s
==============2003=====================================
[127.0.0.1:2003] 4 threads 1000 connections run 10s
total readcount=1599727 readbytes=1638120448
throughput = 156 MB/s
==============2004=====================================
[127.0.0.1:2004] 4 threads 1000 connections run 10s
total readcount=2202271 readbytes=2255125504
throughput = 215 MB/s
==============2005=====================================
[127.0.0.1:2005] 4 threads 1000 connections run 10s
total readcount=1354230 readbytes=1386731520
throughput = 132 MB/s
==============2006=====================================
[127.0.0.1:2006] 4 threads 1000 connections run 10s
total readcount=1699652 readbytes=1740443648
throughput = 165 MB/s
iperf tcp_proxy_server
sudo apt install iperf
iperf -s -p 5001 > /dev/null &
bin/tcp_proxy_server 1212 127.0.0.1:5001 &
iperf -c 127.0.0.1 -p 5001 -l 8K
iperf -c 127.0.0.1 -p 1212 -l 8K
Bandwidth:------------------------------------------------------------
[ 3] local 127.0.0.1 port 52560 connected with 127.0.0.1 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 20.8 GBytes 17.9 Gbits/sec
------------------------------------------------------------
[ 3] local 127.0.0.1 port 48142 connected with 127.0.0.1 port 1212
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 11.9 GBytes 10.2 Gbits/sec
webbench
sudo apt install wrk
wrk -c 100 -t 4 -d 10s http://127.0.0.1:8080/
sudo apt install apache2-utils
ab -c 100 -n 100000 http://127.0.0.1:8080/
``
libhv(port:8080) vs nginx(port:80)
Above test results can be found on Github Actions.
---