libhv

GitHub

🔥 比libevent/libuv/asio更易用的网络库。A c/c++ network library for developing TCP/UDP/SSL/HTTP/WebSocket/MQTT client/server.

RAW Doc

Cn/Channel

通道类

text
/ Detailed source-code truncated for AI context efficiency. /

---

Cn/EventLoop

事件循环类

text
/ Detailed source-code truncated for AI context efficiency. /

测试代码见:

- evpp/EventLoop_test.cpp
- evpp/EventLoopThread_test.cpp
- evpp/EventLoopThreadPool_test.cpp

---

Cn/Hbase

一些基础函数

text
/ Detailed source-code truncated for AI context efficiency. /

单元测试代码见 unittest/hbase_test.c

---

Cn/Hdns

hdns: 异步DNS解析

hdnslibhv 内置的异步 DNS 解析器,完全运行在事件循环(hloop)之内。

为什么需要它

libhv 是一个事件循环库,但此前域名解析走的是阻塞的 getaddrinfo()base/hsocket.cResolveAddr)。在事件循环里同步解析 DNS 会卡住整个 loop——期间所有其它连接的读写、定时器都会停摆,网络抖动或 DNS 不可达时甚至会阻塞数秒。

hdns 参考 libeventevdns 思路,自己实现了一套原生的、基于非阻塞 UDP 的 DNS 解析器:构造 DNS 报文、通过 hloop 的非阻塞 UDP 收发、解析响应、处理超时/重试,全程不阻塞事件循环,也不引入任何第三方依赖(不同于 libuv 的线程池方案,也不同于接入 c-ares)。

注意:hdnsprotocol/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 等裸记录查询。这些留作后续增强。

接口

text
/ Detailed source-code truncated for AI context efficiency. /

C 层句柄的生命周期契约与 htimer_t 一致:句柄在查询完成(回调触发)或被 hdns_cancel 后失效,之后不得再使用。如果需要免疫 use-after-free 的句柄,使用 C++ 层 EventLoop::resolveDns() / cancelDns()——它维护 DnsID → hdns_t 映射(对标 TimerID),失效的 DnsIDcancelDns 里查不到即安全 no-op。

状态码

c
#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。

示例

c
#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/killTimerTimerID):

cpp
// 返回免疫 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 路径。异步解析逻辑统一沉淀在基类 TcpClientEventLoopTmplevpp/TcpClient.h)里,并通过上面的 EventLoop::resolveDns 使用 DnsID 句柄,因此所有继承自 TcpClientTmpl 的客户端(TcpClientWebSocketClient,以及未来任何 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
- 查询生命周期由 EventLoopDnsID 映射管理,随 loop 拆除统一回收,无需客户端手工跟踪。

说明:同步的 HttpClient / requests 请求路径运行在调用方线程(并非在 loop 内),阻塞解析可接受,故该路径维持不变。

性能说明

unittest/hdns_benchmark.c 对比“顺序阻塞 getaddrinfo” 与 “单 loop 并发 hdns” 解析同一批域名:由于异步解析把所有查询一次性发出、由事件循环并发多路复用,总耗时约等于一次最慢的解析,而不是所有解析之和;更关键的是解析期间事件循环始终不被阻塞,其它 IO / 定时器可以继续运行。

注意:单次“命中缓存”的孤立解析,系统 getaddrinfo(下游有 systemd-resolved 等本地缓存)可能更快;hdns 通过尊重 TTL 的进程内缓存来弥补——命中缓存时是进程内查表(微秒级),且同样不阻塞 loop。

---

Cn/Hlog

日志

text
/ 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、后来的polllinux的epollwindows的iocpbsd的kqueuesolaris的port等,都属于IO多路复用机制。<br>
非阻塞NIO搭配IO多路复用机制就是高并发的钥匙。<br>
libhv下的event模块正是封装了多种平台的IO多路复用机制,提供了统一的事件接口,是libhv的核心模块。<br>

hloop.h: 事件循环模块对外头文件。<br>

text
/ 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 客户端类

text
/ Detailed source-code truncated for AI context efficiency. /

测试代码见 examples/http_client_test.cpp

---

Cn/HttpContext

text
/ Detailed source-code truncated for AI context efficiency. /

---

Cn/HttpLuaHandler

Http Lua Handler

HttpScriptHandler 允许 HttpService 调用脚本里的 handle(ctx) 方法处理 HTTP 请求。当前支持 .lua 脚本,适合把少量业务逻辑从 C++ 编译周期里解耦出来:修改脚本后无需重新编译服务,下一次请求会自动加载新脚本。

该功能是可选模块,默认不编译。

编译

需要 Lua 5.3 或更新版本的开发库。

Makefile:

bash
make libhv WITH_LUA=yes
make http_server_test WITH_LUA=yes
make unittest WITH_LUA=yes

如果系统没有 pkg-config,或者 Lua 安装在自定义路径,可以显式指定:

bash
make libhv WITH_LUA=yes \
LUA_CFLAGS="-I/usr/local/include/lua" \
LUA_LIBS="-L/usr/local/lib -llua"

CMake:

bash
cmake -S . -B build -DWITH_LUA=ON -DBUILD_EXAMPLES=ON -DBUILD_UNITTEST=ON
cmake --build build

基本用法

C++:

cpp
#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:

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

cpp
router.Script("/script/", "scripts");

访问 /script/user?id=42 时会调用 scripts/user.lua。访问 /script/ 时会调用 scripts/index.lua。当前目录映射只自动补 .lua 后缀。

目录映射默认支持 GETPOSTPUTDELETEPATCH。路径中包含 .. 路径段时返回 403

ctx API

首版只暴露 HTTP handler 必需的最小 API:

lua
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:

lua
function handle(ctx)
ctx:status(201)
ctx:set_header("X-From", "lua")
return ctx:text("created")
end

hv API

脚本运行在所属 IO 线程的 协程 里,因此可以用 同步写法 调用异步能力:调用会挂起当前请求的协程、把控制权交还事件循环,结果就绪后在同一线程恢复,全程不阻塞 loop。

所有脚本可用能力都挂在统一的全局 hv 表下:

lua
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 -> table

hv.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):

lua
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 不阻塞):

lua
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.clua/hvlua_base.clua/hvlua_json.cpp。TCP/HTTP client、Redis 等高层 client 绑定后续按 WITH_LUA / WITH_REDIS 等开关编入。

热更新

HttpScriptHandler 当前会把 .lua 文件转给 HttpLuaHandlerHttpLuaHandler 会记录脚本文件的 mtime。每次请求前,如果文件被修改,会重新加载脚本。

重新加载失败时:

- 如果已有旧版本脚本,继续使用旧版本。
- 如果首次加载失败,返回 500

这能避免线上脚本语法错误直接打断已有服务。

示例

bash
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

text
/ Detailed source-code truncated for AI context efficiency. /

---

Cn/HttpServer

HTTP 服务端类

text
/ 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:

bash
./configure --with-lua --with-http --with-redis --with-mqtt
make libhv
make hvlua # 独立 Lua 运行时
make unittest # 编译 lua 相关单测

CMake:

bash
cmake -S . -B build -DWITH_LUA=ON -DWITH_HTTP=ON -DWITH_REDIS=ON -DWITH_MQTT=ON
cmake --build build

分层说明:hloop.* 定时器、hv.tcpClient/tcpServer/udpClient/udpServerhv.resolveDnshv.jsonhv.log 只依赖纯 C 的 event 层,WITH_LUA 即可用;hv.http / hv.ws 需要 WITH_HTTPhv.redis 需要 WITH_REDIShv.mqtt 需要 WITH_MQTT。未开启对应模块时,Lua 里 hv.http / hv.redis / hv.mqttnil

运行脚本

独立运行时 hvlua

bash
bin/hvlua examples/lua/timer.lua
bin/hvlua examples/lua/tcp_client.lua 127.0.0.1 10514

examples/lua/ 下有 timer / sleep / dns / tcp / udp / http / ws / redis / mqtt 各场景的示例脚本。

核心模型

- 每个 loop 线程一个 lua_State,存放在 hloop_t 上,loop 销毁时关闭。
- 协程同步写法:每个任务(一段脚本 / 每个 HTTP 请求 / 每个接入连接)跑在独立协程里;可挂起的绑定(conn:readhv.sleephv.http.getr: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 通用工具

lua
hv.version()                 -- libhv 版本串,如 "1.3.4"
hv.log(...) -- 以 tab 连接参数,info 级日志(logi 的别名)
hv.logd(...) / hv.logi(...) / hv.logw(...) / hv.loge(...) -- debug/info/warn/error

hv.json.encode(tbl) -- table -> json 字符串
hv.json.decode(str) -- json 字符串 -> table

定时器与协程 sleep

lua
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(协程同步)

lua
local addrs, err = hv.resolveDns("example.com")
-- addrs: { "93.184.216.34", ... } ; 失败: nil, err

TCP / UDP(event 层,协程同步)

命名对齐 C++ 类 hv::TcpClient / TcpServer / UdpClient / UdpServerhv.connecthv.tcpClient 的别名,hv.listenhv.tcpServer 的别名。

TCP 客户端

lua
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"

拆包读(文本协议 / 二进制协议):

lua
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) 在每个新连接的独立协程里被调用,因此可在里面用同步写法。

lua
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 对象。

lua
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)

lua
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"(终止)。

lua
-- 第二参数为可选 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)

lua
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.redisnew 而非 connect:它是纯构造,连接是懒发起 + 断线重连,命令可在未连接时排队;这与 hv.ws.connect / hv.mqtt.connect 挂起到握手完成才返回的语义不同。

hv.mqtt(协程同步,需 WITH_MQTT)

MQTT 是消息驱动的,m:recv() 挂起直到 broker 推来一条 PUBLISH。

lua
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接口

- Lua Binding: hv.* 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

- hrpc: TLV + protobuf RPC 框架

---

Cn/RedisClient

Redis 客户端类

libhv 的 Redis C++ 模块位于仓库根目录 redis/,采用 .h + .cpp 分离结构。第一版只支持单机 Redis 与 RESP2 协议,提供同步 / 异步命令、typed helpers、pipeline、transaction 和 pub/sub。

Redis 模块默认不编译,需要显式开启:

shell
./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)
};

text
语义约定:

- code == 0:客户端侧调用成功,reply 有效。
-
code != 0:客户端侧失败(未连接、连接失败、超时、断线、协议错误、参数非法等),reply 无效。
-
code == 0 && reply.isError():成功收到 Redis 服务端错误回复(如 -ERR-WRONGTYPE-NOAUTH)。
-
isNil():不是错误,例如 GET 不存在的 key。

class RedisClient

面向多数使用者的主入口,同步 + 异步接口共存,用法与 HttpClient 类似。内部使用独立线程运行事件循环,同步接口在内部等待完成,对外无需操作 future。


/ Detailed source-code truncated for AI context efficiency. /
text
其中 RedisCommandstd::vector<std::string>RedisCallbackstd::function<void(const RedisResult&)>

typed helpers 是对原始命令的薄封装,复用统一的命令编码、reply 解析与错误语义,不构成第二套协议实现。

同步用法

c++
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());
}

text

异步用法

异步命令采用单连接、FIFO 配对模型:按发送顺序取出对应 callback。回调保证只触发一次。

c++
client.getAsync("key", [](const RedisValueResult<std::string>& v) {
if (v.ok()) {
printf("key => %s\n", v.value.c_str());
}
});
text
注意:不要在异步回调线程里再调用同步接口(如 client.get(...)),会因处于事件循环线程内被拒绝并返回 ERR_INVALID_HANDLE

class RedisPipeline

批量命令对象:先累积命令,exec 时一次性发送,按顺序返回 N 条回复。整体失败(发送失败 / 超时 / 断线 / 回复不完整)通过 RedisResult.code 体现;单条命令的错误回复是 replies 中某一项的合法 error reply。

c++
class RedisPipeline {
void appendCommand(const RedisCommand& command);
RedisResult exec(std::vector<RedisReply>* replies = NULL); // 同步
int execAsync(const RedisRepliesCallback& cb); // 异步
};
text
c++
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
}

text

class RedisTransaction

封装 MULTI / EXEC / DISCARDexec 返回事务结果数组。第一版不把 WATCH 及其冲突重试作为重点能力。

c++
class RedisTransaction {
void appendCommand(const RedisCommand& command);
RedisResult exec(std::vector<RedisReply>* replies = NULL); // MULTI + 命令 + EXEC
RedisResult discard(); // DISCARD
};
text
c++
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
}

text

class AsyncRedisClient

面向事件循环模型的纯异步客户端,风格贴近 TcpClient / AsyncHttpClientRedisClient 的异步能力即基于它实现。适合高并发、长连接、批量发送场景。

c++
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;
};

text
连接断开时,所有尚未完成的 pending 异步请求会统一以客户端错误失败,且不会自动重放未完成命令(Redis 命令可能有副作用,自动重放不安全)。

class RedisSubscriber

独立订阅客户端,专门处理 Pub/Sub。使用独立连接,不与普通命令连接复用状态机。

c++
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;
};

text
c++
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");
text
配置了 reconnect 时,连接恢复后会重放当前订阅集合并继续接收推送;已经显式退订的条目不会自动恢复。

说明

- 第一版明确不做:Redis Sentinel、Redis Cluster、RESP3、coroutine / future-first 的公开 API、WATCH 冲突重试、自动重放未完成命令、大而全的全命令 typed API 覆盖。
- 普通命令流与订阅流分离,是第一版设计中的硬边界。

测试代码见 examples/redis_client_test.cppexamples/redis_subscriber_test.cpp

---

Cn/Rpc

libhv RPC (hrpc)

hrpc 是 libhv 自带的轻量 RPC,两端均基于 libhv。传输走自定义 TCP,序列化用 protobuf,服务桩代码由 protoc 插件生成。默认不依赖 nghttp2,不追求与 gRPC 线上兼容。

分三层,自底向上:

1. TLV 编解码层:通用的 type-length-value 帧编解码,不依赖 IO,可被任意二进制协议复用。
2. TLV 三件套
evpp/):TLVChannel / TLVClient / TLVServer,把 TLV 编解码接到 evpp 事件循环,按整帧投递。
3. RPC 层
rpc/):RpcClient / RpcServer 继承 TLV 三件套,处理服务路由、请求应答关联、超时、心跳。

---

一、TLV 编解码层

TLV 是通用编解码单元,T 和 L 的字节宽度均可配置,让不同协议自由裁剪帧头。


+----------------+------------------+------------------+
| Type (T bytes) | Length (L bytes) | Value (Length) |
+----------------+------------------+------------------+
text

配置

cpp
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;
text
由配置推算底层 unpack_setting_tUNPACK_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
text

TLVMessage 类

Type 用定长字节数组承载(最大 8 字节),既能当整数用,也能按字节位切分子字段。类名用 TLVMessage(而非 TLV)以避免与全大写宏冲突;头文件为 evpp/TLVMessage.h

cpp
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);
};

text
lengthuint64_t,声明宽度 8 字节时理论上限 16EB,但实际拆包仍受底层 package_max_length 限制,不会因为声明大宽度就分配巨型 buffer。

---

二、TLV 三件套(evpp/)

复刻 libhv 已有的 WebSocket 三件套模式(WebSocketChannel / 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 风格一致):

cpp
// 收到一整帧
std::function<void(const TLVChannelPtr& channel, const TLVMessage& msg)> onmessage;
text
三件套只负责“完整帧的收发”,不含任何 RPC 语义,可被 IM、游戏等其它 TLV 协议直接继承复用。

---

三、RPC 层(rpc/)

帧头(Type 的 8 字节切分)

hrpc 令 type_bytes = 8,把 Type 切成子字段(等价于早期 protorpc 的定长头):

| 字节 | 含义 |
|-----------|-------------------------------------------------|
| type[0-3] | magic
"hrpc" |
| type[4] | version =
1 |
| type[5] | 消息类型: REQUEST/RESPONSE/PING/PONG/CLOSE |
| type[6-7] | reserved |

- REQUEST / RESPONSE:Value 承载 protobuf 信封 RpcMessage
- PING / PONG:心跳,Value 为空。
- CLOSE:优雅关闭,Value 为空。

length_bytes = 4(Value 即信封长度)。

信封 rpc.proto

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 的序列化
}

text
一次 parse 信封即拿到路由信息(method)、关联 id、错误状态;payload 再由生成的桩代码按具体类型 parse。

RpcServer

cpp
class RpcServer : public TLVServer {
public:
// 注册一个 service (由 codegen 生成的 RpcService 子类)
void registerService(const std::shared_ptr<RpcService>& service);
};
text
RpcService 是 codegen 生成的服务基类,内部持有 method -> handler 表并向 RpcServer 注册。收到 REQUEST 帧后,RpcServer 解析信封、按 method 路由到对应 service,执行后把结果打包成 RESPONSE 帧回写。

线程约束(重要)

- registerService() 必须在 start() 之前调用:method 表在 IO 线程无锁读取,start 后再注册是数据竞争。

- service 方法同步运行在连接所属的 IO 线程上,同一连接的所有调用串行执行。慢 handler 会阻塞该 IO 线程上的其它连接。重活请交给自己的工作线程/线程池处理后再回复。

RpcClient

cpp
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
};

text
- 内部维护 id -> 调用上下文 表,收到 RESPONSE 按 id 匹配。
- 复用
TcpClient 的重连、EventLoop 所有权体系。
- 超时:同步靠 future 等待
timeout_ms;异步挂一个 loop 定时器,到点回调 HRPC_STATUS_TIMEOUT
- 断线:连接关闭时,所有在途调用立即以
HRPC_STATUS_NOT_CONNECTED 失败(不会挂死、不泄漏上下文)。
- 心跳:连接建立后定时发 PING(
setPingInterval,默认 3000ms),收 PONG 重置计数;连续 3 次无 PONG 主动关闭连接(触发重连)。对端 PING 自动回 PONG。

---

四、代码生成(rpc/protoc-gen-hrpc)

标准 protobuf service 定义:

proto
service Calc {
rpc Add (CalcRequest) returns (CalcReply);
}
text
protoc-gen-hrpc 是 protoc 插件(依赖 libprotoc),protoc 把已解析的 AST 从 stdin 传入,插件遍历 ServiceDescriptor 套模板输出 calc.hrpc.h,无需自己写任何 IDL 解析。

生成产物:

Server —— 用户继承实现纯虚方法

cpp
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)
};
text
Client —— 生成类型安全的 stub
cpp
class 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);
};
text
生成方式沿用 examples/protorpc/proto/protoc.sh 的一键脚本风格:
bash
protoc --plugin=protoc-gen-hrpc=./protoc-gen-hrpc \
--cpp_out=. --hrpc_out=. calc.proto
text
---

五、构建与使用

hrpc 依赖 protobuf,因此单独编译成 libhrpc,让 libhv 本身保持零 protobuf 依赖。三者关系:


用户代码 (含 protoc-gen-hrpc 生成的 xxx.hrpc.h)
→ libhrpc (RpcClient/RpcServer + rpc.pb 信封) 依赖 protobuf
→ libhv (TLV 三件套 + evpp + event...) 零 protobuf
text
- TLV 三件套evpp/TLVMessage.h + TLV{Channel,Client,Server}.h)不依赖 protobuf,随 evpp 一起编译安装(include/hv/),可被其它二进制协议独立复用。
- libhrpc(可选,默认关):
RpcClient.cpp/RpcServer.cpp + 信封 rpc.pb.cc,链接 libhv + protobuf。头文件安装到 include/hv/rpc/
- protoc-gen-hrpc 插件安装到
bin/,用户用它生成 service stub。
- 现代 protobuf/abseil 头要求 C++17,故 libhrpc 以 C++17 编译;libhv/TLV 仍是 C++11。

编译 libhrpc

bash

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
text

用户使用(基于发布产物)

bash

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
text
代码里 #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 客户端类

c++

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;

};

text
测试代码见 evpp/TcpClient_test.cpp

---

Cn/TcpServer

TCP 服务端类

c++

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;

};

text
测试代码见 evpp/TcpServer_test.cpp

---

Cn/UdpClient

UDP 客户端类

c++

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;
};

text
测试代码见 evpp/UdpClient_test.cpp

---

Cn/UdpServer

UDP 服务端类

c++

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;
};

text
测试代码见 evpp/UdpServer_test.cpp

---

Cn/WebSocketClient

WebSocket 客户端类

c++

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();

};

text
测试代码见 examples/websocket_client_test.cpp

---

Cn/WebSocketServer

WebSocket 服务端类

c++

// 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;
};

text
测试代码见 examples/websocket_server_test.cpp

---

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, strcasecmp

hexport.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, REPEATED

hdef.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, printe

hatomic.h


- hatomic_flag_t, hatomic_t
- hatomic_flag_test_and_set
- hatomic_flag_clear
- hatomic_add
- hatomic_sub
- hatomic_inc
- hatomic_dec

herr.h


- hv_strerror

htime.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_timeout

hmath.h


- floor2e
- ceil2e
- varint_encode
- varint_decode

hbase.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_url

hversion.h


- hv_version
- hv_compile_version
- version_atoi
- version_itoa

hsysinfo.h


- get_ncpu
- get_meminfo

hproc.h


- hproc_spawn

hthread.h


- hv_getpid
- hv_gettid
- HTHREAD_RETTYPE
- HTHREAD_ROUTINE
- hthread_create
- hthread_join
- class HThread

hmutex.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
- class
hv::MutexLock
- class
hv::SpinLock
- class
hv::RWLock
- class
hv::LockGuard
- synchronized

hsocket.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_linger

hlog.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, LOGF

hbuf.h


- hbuf_t
- offset_buf_t
- class HBuf
- class HVLBuf
- class HRingBuf

hmain.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_run

hstring.h


- to_string
- from_string
- toupper
- tolower
- reverse
- startswith
- endswith
- contains
- asprintf
- trim
- ltrim
- rtrim
- trim_pairs
- split
- splitKV
- replace
- replaceAll

hfile.h


- class HFile

hpath.h


- exists
- isdir
- isfile
- islink
- basename
- dirname
- filename
- suffixname
- join

hdir.h


- listdir

hurl.h


- HUrl::escape
- HUrl::unescape
- HUrl::parse
- HUrl::dump

hscope.h


- defer
- template ScopeCleanup
- template ScopeFree
- template ScopeDelete
- template ScopeDeleteArray
- template ScopeRelease
- template ScopeLock

ifconfig.h


- ifconfig

utils


md5.h


- HV_MD5Init
- HV_MD5Update
- HV_MD5Final
- hv_md5
- hv_md5_hex

sha1.h


- HV_SHA1Init
- HV_SHA1Update
- HV_SHA1Final
- HV_SHA1
- hv_sha1
- hv_sha1_hex

base64.h


- hv_base64_decode
- hv_base64_encode

json.hpp


- json::parse
- json::dump

singleton.h


- DISABLE_COPY
- SINGLETON_DECL
- SINGLETON_IMPL

event

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_del

nlog.h


- network_logger
- nlog_listen

evpp


- class Buffer
- class Channel
- class Event
- class EventLoop
- class EventLoopThread
- class EventLoopThreadPool
- class TcpClient
- class TcpServer
- class UdpClient
- class UdpServer

ssl


- 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_hostname

protocol

dns.h


- dns_name_decode
- dns_name_encode
- dns_pack
- dns_unpack
- dns_rr_pack
- dns_rr_unpack
- dns_query
- dns_free
- nslookup

ftp.h


- ftp_command_str
- ftp_connect
- ftp_login
- ftp_exec
- ftp_upload
- ftp_download
- ftp_download_with_cb
- ftp_quit
- ftp_status_str

smtp.h


- smtp_command_str
- smtp_status_str
- smtp_build_command
- sendmail

icmp.h


- ping

http


- class HttpMessage
- class HttpRequest
- class HttpResponse
- class HttpParser

httpdef.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_str

http_content.h


- parse_query_params
- parse_json
- parse_multipart
- dump_query_params
- dump_json
- dump_multipart

HttpClient.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 HttpClient

requests.h


- requests::request
- requests::get
- requests::post
- requests::put
- requests::patch
- requests::Delete
- requests::head
- requests::async

axios.h


- axios::axios
- axios::get
- axios::post
- axios::put
- axios::patch
- axios::Delete
- axios::head
- axios::async

HttpServer.h


- http_server_run
- http_server_stop
- class HttpService
- class HttpServer

WebSocketClient.h


- class WebSocketClient

WebSocketServer.h


- websocket_server_run
- websocket_server_stop
- class WebSocketService
- class WebSocketServer

mqtt


- 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 MqttClient

other


- 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 + protobuf

Plan

- 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 libevent, libev, and libuv,
libhv provides 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:

shell
./configure
make
sudo make install
text
or cmake:
shell
mkdir build
cd build
cmake ..
cmake --build .
text
or bazel:
shell
bazel build libhv
text
or vcpkg:
shell
vcpkg install libhv
text
or xmake:
shell
xrepo install libhv
text

⚡️ Getting Started

run ./getting_started.sh:

shell
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/
text

TCP


#### tcp server
c version: examples/tcp_echo_server.c

c++ version: evpp/TcpServer_test.cpp

c++
#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;
}

text
#### tcp client
c version: examples/tcp_client_test.c

c++ version: evpp/TcpClient_test.cpp

c++
#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;
}

text

HTTP


#### http server
see examples/http_server_test.cpp

golang gin style

c++
#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;
}

text
#### http client
see examples/http_client_test.cpp

python requests style

c++
#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;
}

text

WebSocket


#### WebSocket server
see examples/websocket_server_test.cpp
c++
#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;
}

text
#### WebSocket client
see examples/websocket_client_test.cpp
c++
#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;
}

text

Redis


see examples/redis_client_test.cpp and examples/redis_subscriber_test.cpp

The Redis C++ module lives in the repository root redis/ and follows the same .h + .cpp split used by other libhv modules. Redis is disabled by default; enable it explicitly with ./configure --with-redis or cmake -DWITH_REDIS=ON.

shell
./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
shell
cd echo-servers
./build.sh
./benchmark.sh
text
throughput:
shell
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

text

iperf tcp_proxy_server

shell

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
text
Bandwidth:
shell
------------------------------------------------------------
[ 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

text

webbench

shell

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.

---