☰
libhv 基础函数库 hbase 完全指南:内存、字符串、文件路径与配置解析 API 详解
2026/9/28 2:49:53 网站建设 项目流程
  • 网络
  • 后端
  • 通信

【免费下载链接】libhv

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

项目地址:https://gitcode.com/gh_mirrors/li/libhv
点击查看免费下载

本指南以 libhv 仓库中 docs/cn/hbase.md 文档为骨架,结合 base/hbase.c 源码实现与 unittest/hbase_test.c 单元测试,系统讲解 libhv 基础函数库hbase的四大类 API:内存分配、字符串操作、文件与目录工具、随机数与配置解析。读完本文,你将掌握每个函数的语义、边界行为与典型应用场景,并能在自己的 C/C++ 项目中直接复用以替代手写轮子。

hbase.h是 libhv 的基础模块(base 目录中声明"基础函数"),它不依赖任何网络或事件循环组件,被 libhv 的日志、命令行解析、HTTP、MQTT 等模块广泛复用,是所有上层功能的地基。从 base/README.md 可以看到,base 目录中 base/hbase.h 与array.h、hatomic.h、hlog.h等并列,共同构成 libhv 的公共基础设施。

一、内存分配与释放:带计数的安全封装

hbase提供了一组与标准库同名同义的内存函数,但额外增加了分配/释放计数,便于排查内存泄漏。

void* hv_malloc(size_t size); void* hv_realloc(void* oldptr, size_t newsize, size_t oldsize); void* hv_calloc(size_t nmemb, size_t size); void* hv_zalloc(size_t size); void hv_free(void* ptr); // 使用hv分配内存次数 long hv_alloc_cnt(); // 使用hv释放内存次数 long hv_free_cnt();

从 base/hbase.c 的实现可以看到几个关键行为:

  • 失败即退出:hv_malloc、hv_realloc、hv_calloc、hv_zalloc在底层malloc返回NULL时会向stderr打印malloc failed!并exit(-1),避免空指针在高层代码中继续传播。
  • hv_zalloc自动清零:hv_zalloc在malloc后立即memset(ptr, 0, size),等价于calloc,是 libhv 内部最常用的分配方式。
  • hv_realloc的三参数签名:与标准realloc不同,它要求调用者额外传入oldsize。当newsize > oldsize时,会对新增的尾部区域清零,保证扩展后的内存是干净的(见 base/hbase.c)。
  • 原子计数:分配计数s_alloc_cnt与释放计数s_free_cnt使用hatomic_t原子变量维护(base/hbase.c),因此在多线程并发分配时hv_alloc_cnt()/hv_free_cnt()的统计是准确的。

配套的调试宏定义在 base/hbase.h:

#define HV_ALLOC(ptr, size) // 分配并打印调用位置 #define HV_ALLOC_SIZEOF(ptr) // 按指针所指类型大小分配 #define HV_FREE(ptr) // 释放并将指针置 NULL #define HV_MEMCHECK // atexit 注册 hv_memcheck

其中HV_FREE(ptr)在释放后会把指针置为NULL,可有效防止悬空指针;HV_MEMCHECK通过atexit注册hv_memcheck(),程序退出时自动打印Memcheck => alloc:N free:N,当两个计数不相等时即可定位内存泄漏。此外STACK_OR_HEAP_ALLOC/HV_STACK_ALLOC系列宏实现了"小对象用栈、大对象用堆"的分配策略,默认栈缓冲阈值为HV_DEFAULT_STACKBUF_SIZE = 1024字节。

二、字符串操作:原地修改与安全拷贝

1. 大小写转换与翻转

char* hv_strupper(char* str); // 转大写 char* hv_strlower(char* str); // 转小写 char* hv_strreverse(char* str); // 翻转

三个函数均为原地修改并返回原指针。从 base/hbase.c 可见,大小写转换通过位运算*p &= ~0x20/*p |= 0x20实现,仅处理 ASCII 字母。

2. 前后缀与包含判断

bool hv_strstartswith(const char* str, const char* start); bool hv_strendswith(const char* str, const char* end); bool hv_strcontains(const char* str, const char* sub);
  • hv_strstartswith逐字符比较,当start指针走到结尾时返回true,因此空前缀恒为真(base/hbase.c)。
  • hv_strendswith先分别遍历求长度,若str长度小于end直接返回false,再逆序比对(base/hbase.c)。
  • hv_strcontains本质就是strstr(str, sub) != NULL。

此外头文件中还提供了通配符匹配函数hv_wildcard_match(str, pattern),支持*匹配任意字符序列,配合 unittest/hbase_test.c 中的断言可以看到典型用法:

assert(hv_wildcard_match("www.example.com", "*.example.com")); assert(hv_wildcard_match("www.example.com", "www.*.com")); assert(hv_wildcard_match("www.example.com", "www.example.*"));

3. 安全的 strncpy / strncat

char* hv_strncpy(char* dest, const char* src, size_t n); char* hv_strncat(char* dest, const char* src, size_t n);

这两个函数是 libhv 对strncpy/strncat的安全性修正。base/hbase.h 的注释点明了差异:

  • 标准strncpy要求n = sizeof(dest_buf) - 1;
  • hv_strncpy直接传n = sizeof(dest_buf),函数内部循环条件为--n > 0,保证无论如何都会写入结尾的'\0'(base/hbase.c)。

也就是说,hv_strncpy永远不会产生非'\0'结尾的缓冲区,杜绝了"字符串未截断"这一经典 C 陷阱。在!HAVE_STRLCPY环境下,strlcpy被定义为hv_strncpy、strlcat被定义为hv_strncat(base/hbase.h),可作为可移植替代。

4. 字符查找与路径/文件名提取

char* hv_strnchr(const char* s, char c, size_t n); // 前 n 个字符内查找 #define hv_strrchr_dot(str) strrchr(str, '.') // 找最后一个点 char* hv_strrchr_dir(const char* filepath); // 找最后一个路径分隔符 const char* hv_basename(const char* filepath); // 取文件名 const char* hv_suffixname(const char* filename); // 取后缀
  • hv_strnchr在限定的n个字符范围内查找,避免越界(base/hbase.c)。
  • hv_strrchr_dir在 Windows 上同时识别/与\\两种分隔符(base/hbase.c),实现了跨平台路径解析。
  • hv_basename与hv_suffixname分别基于hv_strrchr_dir与hv_strrchr_dot实现;hv_suffixname在无.时返回空字符串""。

实际调用场景很常见:libhv 命令行解析模块 base/hmain.c 用hv_basename(argv[0])获取程序名;HTTP 静态文件服务 examples/httpd/handler.cpp 与 http/HttpMessage.h 用hv_suffixname判断请求文件类型;multipart 表单解析 http/http_content.cpp 用hv_basename从完整路径中提取上传文件名。

三、文件与目录工具

int hv_mkdir_p(const char* dir); // 等价 mkdir -p int hv_rmdir_p(const char* dir); // 等价 rmdir -p bool hv_exists(const char* path); bool hv_isdir(const char* path); bool hv_isfile(const char* path); bool hv_islink(const char* path); size_t hv_filesize(const char* filepath);
  • hv_mkdir_p模拟mkdir -p语义:若目录已存在返回EEXIST;否则逐级创建各级子目录,成功返回0,底层mkdir失败返回EPERM(base/hbase.c)。仓库自带的可直接运行示例 unittest/mkdir_test.c 展示了其用法:

    // 命令行: mkdir_test /a/b/c/d const char* dir = argv[1]; return hv_mkdir_p(dir);
  • hv_rmdir_p模拟rmdir -p语义:目录不存在返回ENOENT;删除失败返回EPERM;删除成功后会继续向上尝试删除空父目录,直到遇到非空目录为止(base/hbase.c)。

  • hv_exists/hv_isdir/hv_isfile分别基于access(path, 0)与stat的S_ISDIR/S_ISREG宏判断;hv_islink在非 Windows 平台使用lstat的S_ISLNK判断(base/hbase.c)。

  • hv_filesize直接读取stat的st_size字段返回文件字节数。

四、可执行文件信息与运行目录

char* get_executable_path(char* buf, int size); // 如 /usr/local/bin/httpd char* get_executable_dir(char* buf, int size); // 如 /usr/local/bin char* get_executable_file(char* buf, int size); // 如 httpd char* get_run_dir(char* buf, int size); // 如 /home/www/html

这几个函数常用于服务程序的启动目录与配置文件定位。从 base/hbase.c 可以看到它们的平台实现差异:

  • Linux:通过readlink("/proc/self/exe", ...)获取可执行文件绝对路径;
  • macOS:通过_NSGetExecutablePath(来自<mach-o/dyld.h>,见 base/hbase.c);
  • Windows:通过GetModuleFileName(NULL, ...)。

get_executable_dir与get_executable_file内部先调用get_executable_path,再用hv_strrchr_dir拆分目录与文件名;get_run_dir则直接返回getcwd(buf, size)的当前工作目录。

五、随机数与随机字符串

int hv_rand(int min, int max); // [min, max] 闭区间随机整数 char* hv_random_string(char* buf, int len); // 随机字母数字字符串
  • hv_rand内部以time(NULL)作为种子首次调用时初始化srand(base/hbase.c),返回[min, max]闭区间内的整数,且断言max > min。
  • hv_random_string从 62 个字符(大写字母 + 小写字母 + 数字)中随机抽取len个字符,并在末尾补'\0'(base/hbase.c)。注意len是字符个数,因此传入的缓冲区实际需要len + 1字节容量。

典型场景:MQTT 客户端 mqtt/mqtt_client.c 在未指定client_id时用hv_random_string(cli->client_id, cid_len)自动生成客户端 ID。单元测试 unittest/hbase_test.c 中也有直接调用:

char buf[16] = {0}; printf("hv_rand(10, 99) -> %d\n", hv_rand(10, 99)); printf("hv_random_string(buf, 10) -> %s\n", hv_random_string(buf, 10));

六、配置解析三件套:布尔、容量与时长

这组函数专为配置文件与命令行参数的字符串解析设计,也是 libhv 各模块读取配置的高频入口。

1. hv_getboolean:布尔语义归一化

// 1 y on yes true enable 返回 true(通常用于配置文件) bool hv_getboolean(const char* str);

从 base/hbase.c 的实现看,它按字符串长度分派:单字符时1/y/Y为真;两个字符时on为真;三个字符时yes为真;四个字符时true为真;六个字符时enable为真。除单字符分支外均使用stricmp比较,因此不区分大小写;空串、NULL及一切其他值返回false。

真实使用示例:HTTP 服务器 examples/httpd/httpd.cpp 用它解析配置中的日志 fsync 开关,examples/httpd/httpd.cpp 用它解析访问日志开关,从而把yes/no/true/false/on/off/enable/1/0等常见写法统一处理。

2. hv_parse_size:复合容量解析

// 1T2G3M4K5B => ?B size_t hv_parse_size(const char* str);

支持按字节单位叠加的容量字符串,单位大小写均可:K/k=1024、M/m=1024²、G/g=1024³、T/t=1024⁴,未带单位的裸数字按字节处理。实现上逐字符扫描,遇到单位时用移位n <<= 10/20/30/40换算后累加(base/hbase.c)。

unittest/hbase_test.c 给出了明确的验证:

assert(hv_parse_size("256") == 256); assert(hv_parse_size("1K") == 1024); assert(hv_parse_size("1G2M3K4B") == 1*1024*1024*1024 + 2*1024*1024 + 3*1024 + 4);

3. hv_parse_time:复合时长解析

// 1w2d3h4m5s => ?s time_t hv_parse_time(const char* str);

与hv_parse_size结构完全对称,支持s(秒)、m(分)、h(时)、d(天)、w(周)五种单位,裸数字按秒计,换算关系见 base/hbase.c(1m=60s、1h=3600s、1d=86400s、1w=604800s)。测试断言:

assert(hv_parse_time("30") == 30); assert(hv_parse_time("1m") == 60); assert(hv_parse_time("1d2h3m4s") == 1*24*60*60 + 2*60*60 + 3*60 + 4);

这类解析让配置写法非常人性化,例如timeout=5m、maxsize=2G,无需在配置中写冗长的秒数或字节数。

七、URL 解析:hv_parse_url 与 hurl_t

URL 解析是 HTTP 客户端的基础能力,hbase提供了一个零拷贝的轻量级解析器:

int hv_parse_url(hurl_t* stURL, const char* strURL);

hurl_t 的数据结构设计

hurl_t定义在 base/hbase.h,不复制字符串内容,而是用(off, len)偏移对记录每个字段在原始字符串中的位置:

typedef enum { HV_URL_SCHEME, // 协议 HV_URL_USERNAME, // 用户名 HV_URL_PASSWORD, // 密码 HV_URL_HOST, // 主机 HV_URL_PORT, // 端口 HV_URL_PATH, // 路径 HV_URL_QUERY, // 查询参数 HV_URL_FRAGMENT, // 锚点 HV_URL_FIELD_NUM, } hurl_field_e; typedef struct hurl_s { struct { unsigned short off; unsigned short len; } fields[HV_URL_FIELD_NUM]; unsigned short port; // 解析出的端口号(数值形式) } hurl_t;

解析完成后,通过strURL + stURL->fields[HV_URL_SCHEME].off即可用%.*s精确打印对应片段。支持的完整格式为:

scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

关键解析规则(来自 base/hbase.c)

  • 返回值:成功返回0;stURL或strURL为空返回-1;URL 总长度超过 65535 返回-2。
  • 默认端口:未显式写端口时,http://默认80,https://默认443。
  • IPv6 支持:以[开头的 host 按 IPv6 处理,自动剥离方括号。
  • 多段解析:依次分离scheme://、user:pswd@、host:port、/path、?query、#fragment,每一段都可能在 URL 中缺省。

unittest/hbase_test.c 对 8 种 URL 形态(带认证信息、带端口、仅路径等)逐一断言字段长度与端口值,是理解解析规则的最佳参考:

const char* test_urls[] = { "http://user:pswd@[www.example.com]:80/path?query#fragment", "http://user:pswd@www.example.com:80/path?query#fragment", "http://user:pswd@www.example.com/path?query#fragment", "http://www.example.com/path?query#fragment", "http://www.example.com/path?query", "http://www.example.com/path", "www.example.com/path", "/path", }; // 全部断言 stURL.port == 80,且各字段长度正确

注意hurl_t字段类型为unsigned short,因此单字段偏移/长度最大 65535,这也与解析器end - begin > 65535返回-2的限制相呼应。

八、如何验证与使用:单元测试与集成方式

hbase的完整测试见 unittest/hbase_test.c,涵盖随机数、布尔解析、通配符匹配、容量/时长解析、URL 解析五大类断言;目录相关函数另有 unittest/mkdir_test.c 演示hv_mkdir_p。仓库通过 Makefile / CMake 组织构建,运行单元测试时可参考 scripts/unittest.sh 与 unittest/CMakeLists.txt 了解测试的构建与执行方式。

使用上,只要#include "hbase.h"即可获得上述全部能力。这些函数被 libhv 各模块广泛依赖,例如:

  • 日志模块对配置开关的处理(examples/httpd/httpd.cpp);
  • 静态文件服务器按后缀返回 Content-Type(examples/httpd/handler.cpp);
  • MQTT 客户端自动生成 Client ID(mqtt/mqtt_client.c);
  • 命令行解析器从argv[0]提取程序名(base/hmain.c)。

总结

hbase是 libhv 的"瑞士军刀"式基础函数库:内存层提供带失败保护与原子计数的分配器及HV_MEMCHECK泄漏检测机制;字符串层提供原地大小写/翻转、前后缀判断、通配符匹配和保证'\0'结尾的安全拷贝;文件层提供跨平台路径拆分、mkdir -p/rmdir -p语义与文件属性查询;配置层提供人性化的布尔、容量、时长字符串解析;URL 层提供零拷贝的轻量级解析器。这些函数语义明确、有源码实现与单元测试双重背书,非常适合在自有项目中作为可移植的基础工具直接复用。

  • 网络
  • 后端
  • 通信

【免费下载链接】libhv

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

项目地址:https://gitcode.com/gh_mirrors/li/libhv
点击查看免费下载

相关推荐

上一篇:MultipleWindow3dScene未来展望:探索WebRTC和WebSocket的扩展可能性
下一篇:Predis位操作高级应用:基于BITCOUNT/BITOP的用户行为分析系统

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询