Libevent(5)之使用教程(4)工具函数
Author: Once Day Date: 2025年8月3日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦...
漫漫长路,有人对你微笑过嘛...
本文档翻译于:Fast portable non-blocking network programming with Libevent
全系列文章可参考专栏: 十年代码训练_Once-Day的博客-CSDN博客
参考文章:
文章目录
- Libevent(5)之使用教程(4)工具函数
-
-
-
- [7. 工具函数](#7. 工具函数)
-
- [7.1 evutil_socket_t](#7.1 evutil_socket_t)
- [7.2 标准整数类型](#7.2 标准整数类型)
- [7.3 其他兼容性类型](#7.3 其他兼容性类型)
- [7.4 定时器可移植性函数](#7.4 定时器可移植性函数)
- [7.5 套接字 API 兼容性](#7.5 套接字 API 兼容性)
- [7.6 可移植的字符串操作函数](#7.6 可移植的字符串操作函数)
- [7.7 与区域设置无关的字符串操作函数](#7.7 与区域设置无关的字符串操作函数)
- [7.8 IPv6 辅助与可移植性函数](#7.8 IPv6 辅助与可移植性函数)
- [7.9 结构宏可移植性函数](#7.9 结构宏可移植性函数)
- [7.10 安全随机数生成器](#7.10 安全随机数生成器)
-
-
7. 工具函数
7.1 evutil_socket_t
除 Windows 外,大多数系统中,套接字(socket)的类型是 int
,操作系统会按数字顺序分配它们。但在 Windows 套接字 API 中,套接字的类型是 SOCKET
,本质上是一种类似指针的操作系统句柄,其分配顺序是未定义的。我们定义 evutil_socket_t
类型为一种整数类型,确保在 Windows 系统中能够存储 socket()
或 accept()
的返回值,且不会出现指针截断风险。
c
#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif
该类型在 Libevent 2.0.1-alpha 版本中引入。
7.2 标准整数类型
有时你可能会遇到未实现 C99 标准 stdint.h
头文件的 C 系统。针对这种情况,Libevent 定义了自己的、与 stdint.h
中位宽特定的整数类型相对应的版本:
Libevent 类型 | 说明 |
---|---|
ev_int8_t |
8 位有符号整数 |
ev_uint8_t |
8 位无符号整数 |
ev_int16_t |
16 位有符号整数 |
ev_uint16_t |
16 位无符号整数 |
ev_int32_t |
32 位有符号整数 |
ev_uint32_t |
32 位无符号整数 |
ev_int64_t |
64 位有符号整数 |
ev_uint64_t |
64 位无符号整数 |
与 C99 标准一致,每种类型都有精确指定的位宽。
这些类型在 Libevent 1.4.0-beta 版本中引入。MAX
/MIN
常量首次出现在 Libevent 2.0.4-alpha 版本中。
7.3 其他兼容性类型
ev_ssize_t
类型:在支持 ssize_t
(有符号的 size_t
)的平台上,定义为 ssize_t
;在不支持的平台上,定义为一个合理的默认类型。ev_ssize_t
的最大值为 EV_SSIZE_MAX
,最小值为 EV_SSIZE_MIN
。(size_t
的最大值为 EV_SIZE_MAX
,适用于未定义 SIZE_MAX
的平台。)
ev_off_t
类型:用于表示文件或内存块中的偏移量。在 off_t
定义合理的平台上,定义为 off_t
;在 Windows 系统上,定义为 ev_int64_t
。
ev_socklen_t
类型:部分套接字 API 实现提供 socklen_t
长度类型,部分则不提供。该类型在支持 socklen_t
的平台上定义为该类型,否则定义为合理的默认类型。
ev_intptr_t
类型:一种有符号整数类型,其大小足以容纳指针且不丢失位。ev_uintptr_t
类型:一种无符号整数类型,其大小足以容纳指针且不丢失位。
ev_ssize_t
类型在 Libevent 2.0.2-alpha 版本中添加。ev_socklen_t
类型在 Libevent 2.0.3-alpha 版本中新增。ev_intptr_t
、ev_uintptr_t
类型以及 EV_SSIZE_MAX
/MIN
宏在 Libevent 2.0.4-alpha 版本中添加。ev_off_t
类型首次出现在 Libevent 2.0.9-rc 版本中。
7.4 定时器可移植性函数
并非所有平台都定义了标准的 timeval
操作函数,因此我们提供了自己的实现。
c
#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */
这些宏分别对前两个参数进行加减运算,并将结果存储在第三个参数中。
c
#define evutil_timerclear(tvp) /* ... */
#define evutil_timerisset(tvp) /* ... */
清空 timeval
会将其值设为零。检查其是否已设置时,若值非零则返回真,否则返回假。
c
#define evutil_timercmp(tvp, uvp, cmp)
evutil_timercmp
宏用于比较两个 timeval
,当它们满足关系运算符 cmp
所指定的关系时,返回真。例如,evutil_timercmp(t1, t2, <=)
表示 "t1
是否小于等于 t2
?"。请注意,与某些操作系统的版本不同,Libevent 的 timercmp
支持所有 C 语言的关系运算(即 <
、>
、==
、!=
、<=
和 >=
)。
c
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);
evutil_gettimeofday
函数将 tv
设置为当前时间。tz
参数未被使用。
c
struct timeval tv1, tv2, tv3;
/* Set tv1 = 5.5 seconds */
tv1.tv_sec = 5; tv1.tv_usec = 500*1000;
/* Set tv2 = now */
evutil_gettimeofday(&tv2, NULL);
/* Set tv3 = 5.5 seconds in the future */
evutil_timeradd(&tv1, &tv2, &tv3);
/* all 3 should print true */
if (evutil_timercmp(&tv1, &tv1, ==)) /* == "If tv1 == tv1" */
puts("5.5 sec == 5.5 sec");
if (evutil_timercmp(&tv3, &tv2, >=)) /* == "If tv3 >= tv2" */
puts("The future is after the present.");
if (evutil_timercmp(&tv1, &tv2, <)) /* == "If tv1 < tv2" */
puts("It is no longer the past.");
这些函数中,除 evutil_gettimeofday()
于 Libevent 2.0 版本引入外,其余均在 Libevent 1.4.0-beta 版本中引入。
注意 :在 Libevent 1.4.4 版本之前,使用 <=
或 >=
与 timercmp
搭配是不安全的。
7.5 套接字 API 兼容性
本节内容的存在源于一个历史原因:Windows 系统从未真正以良好兼容的方式实现过 Berkeleykeley 套接字 API。以下是一些可用于模拟这一 API 的函数。
c
int evutil_closesocket(evutil_socket_t s);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)
evutil_closesocket
函数用于关闭套接字。在 Unix 系统上,它是 close()
的别名;在 Windows 系统上,它会调用 closesocket()
(在 Windows 上不能对套接字使用 close()
,且其他系统也没有定义 closesocket()
)。
evutil_closesocket
函数在 Libevent 2.0.5-alpha 版本中引入。在此之前,需要调用 EVUTIL_CLOSESOCKET
宏。
c
#define EVUTIL_SOCKET_ERROR()
#define EVUTIL_SET_SOCKET_ERROR(errcode)
#define evutil_socket_geterror(sock)
#define evutil_socket_error_to_string(errcode)
以下宏用于访问和操作套接字错误码:EVUTIL_SOCKET_ERROR()
返回当前线程中最后一次套接字操作的全局错误码;evutil_socket_geterror()
返回特定套接字的错误码(在类 Unix 系统上,两者均等同于 errno
)。EVUTIL_SET_SOCKET_ERROR()
用于更改当前的套接字错误码(类似 Unix 系统中设置 errno
);evutil_socket_error_to_string()
返回给定套接字错误码的字符串表示(类似 Unix 系统中的 strerror()
)。
(我们需要这些函数是因为 Windows 系统不会将套接字函数的错误存储在 errno
中,而是使用 WSAGetLastError()
。)
注意,Windows 系统的套接字错误与标准 C 中 errno
里的错误并不相同,需格外留意。
c
int evutil_make_socket_nonblocking(evutil_socket_t sock);
甚至在套接字上执行非阻塞 IO 的调用在 Windows 上也不具备可移植性。evutil_make_socket_nonblocking()
函数接收一个新套接字(来自 socket()
或 accept()
)并将其转换为非阻塞套接字(在 Unix 上设置 O_NONBLOCK
,在 Windows 上设置 FIONBIO
)。
c
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
evutil_make_listen_socket_reuseable()
函数确保监听套接字使用的地址在套接字关闭后能立即被其他套接字使用(在 Unix 上设置 SO_REUSEADDR
,在 Windows 上不执行任何操作 ------ 在 Windows 上不应使用 SO_REUSEADDR
,其含义不同)。
c
int evutil_make_socket_closeonexec(evutil_socket_t sock);
evutil_make_socket_closeonexec()
调用告知操作系统,若调用 exec()
,则应关闭此套接字(在 Unix 上设置 FD_CLOEXEC
标志,在 Windows 上不执行任何操作)。
c
int evutil_socketpair(int family, int type, int protocol,
evutil_socket_t sv[2]);
evutil_socketpair()
函数的行为与 Unix 系统的 socketpair()
调用一致:创建两个相互连接的套接字,可用于常规套接字 IO 调用。它将两个套接字存储在 sv[0]
和 sv[1]
中,成功时返回 0,失败时返回 -1。
在 Windows 系统上,该函数仅支持 family
为 AF_INET
、type
为 SOCK_STREAM
、protocol
为 0 的情况。注意,在某些 Windows 主机上,若防火墙软件巧妙地封锁了 127.0.0.1 以阻止主机与自身通信,该函数可能会失败。
这些函数中,除 evutil_make_socket_closeonexec()
在 Libevent 2.0.4-alpha 版本中新增外,其余均在 Libevent 1.4.0-beta 版本中引入。
7.6 可移植的字符串操作函数
evutil_strtoll()
函数的行为与 strtol
类似,但能处理 64 位整数。在部分平台上,它仅支持十进制。
c
ev_int64_t evutil_strtoll(const char *s, char **endptr, int base);
这些 snprintf
替代函数的行为与标准的 snprintf
和 vsnprintf
接口一致。它们返回的是:若缓冲区足够大,本应写入缓冲区的字节数(不包含终止符 NUL 字节)。(这种行为符合 C99 标准的 snprintf()
,与 Windows 系统的 _snprintf()
不同 ------ 后者在字符串无法装入缓冲区时会返回负数。)
c
int evutil_snprintf(char *buf, size_t buflen, const char *format, ...);
int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);
evutil_strtoll()
函数从 Libevent 1.4.2-rc 版本起就已存在。其他这些函数则首次出现在 1.4.5 版本中。
7.7 与区域设置无关的字符串操作函数
在实现基于 ASCII 的协议时,有时你希望根据 ASCII 的字符类型概念来操作字符串,而不受当前区域设置(locale)的影响。Libevent 提供了一些函数来帮助实现这一点:
c
int evutil_ascii_strcasecmp(const char *str1, const char *str2);
int evutil_ascii_strncasecmp(const char *str1, const char *str2, size_t n);
evutil_ascii_strcasecmp()
和 evutil_ascii_strncasecmp()
函数的行为与 strcasecmp()
和 strncasecmp()
类似,但无论它们始终使用 ASCII 字符集进行比较,不受当前区域设置的影响。
evutil_ascii_strcasecmp()
和 evutil_ascii_strncasecmp()
函数在 Libevent 2.0.3-alpha 版本中首次公开。
7.8 IPv6 辅助与可移植性函数
c
const char *evutil_inet_ntop(int af, const void *src, char *dst, size_t len);
int evutil_inet_pton(int af, const char *src, void *dst);
这些函数的行为与标准的 inet_ntop()
和 inet_pton()
函数一致,用于解析和格式化 IPv4 及 IPv6 地址(如 RFC3493 所规定)。具体来说:
- 要格式化 IPv4 地址,调用
evutil_inet_ntop()
时需将af
设为AF_INET
,src
指向struct in_addr
,dst
指向大小为len
的字符缓冲区。 - 要格式化 IPv6 地址,
af
设为AF_INET6
,src
指向struct in6_addr
。 - 要解析 IPv4 地址,调用
evutil_inet_pton()
时af
设为AF_INET
或AF_INET6
,src
为待解析的字符串,dst
指向相应的in_addr
或in_addr6
。
evutil_inet_ntop()
失败时返回 NULL
,成功时返回指向 dst
的指针。evutil_inet_pton()
成功时返回 0,失败时返回 -1。
c
int evutil_parse_sockaddr_port(const char *str, struct sockaddr *out,
int *outlen);
evutil_parse_sockaddr_port()
函数从字符串 str
中解析地址,并将结果写入 out
。outlen
参数必须指向一个整数,该整数表示 out
中可用的字节数;函数执行后,outlen
会被修改为实际使用的字节数。此函数成功时返回 0,失败时返回 -1。它支持以下地址格式:
[ipv6]:port
(如[ffff::]:80
)ipv6
(如ffff::
)[ipv6]
(如[ffff::]
)ipv4:port
(如1.2.3.4:80
)ipv4
(如1.2.3.4
)
若未指定端口,结果 sockaddr
中的端口会被设为 0。
c
int evutil_sockaddr_cmp(const struct sockaddr *sa1,
const struct sockaddr *sa2, int include_port);
evutil_sockaddr_cmp()
函数用于比较两个地址:若 sa1
排在 sa2
之前,返回负数;若两者相等,返回 0;若 sa2
排在 sa1
之前,返回正数。该函数适用于 AF_INET
和 AF_INET6
地址,对于其他类型的地址,返回结果未定义。它能保证为这些地址提供一个全序关系,但排序方式可能在 Libevent 不同版本间发生变化。
若 include_port
参数为 false
,则两个 sockaddr
仅在端口不同时会被视为相等;否则,端口不同的 sockaddr
会被视为不相等。
这些函数中,除 evutil_sockaddr_cmp()
于 Libevent 2.0.3-alpha 版本引入外,其余均在 Libevent 2.0.1-alpha 版本中引入。
7.9 结构宏可移植性函数
c
#define evutil_offsetof(type, field) /* ... */
evutil_offsetof(type, field)
宏的功能与标准的 offsetof
宏相同,用于计算从 type
类型的起始位置到 field
字段的字节偏移量。
该宏在 Libevent 2.0.1-alpha 版本中引入。在 Libevent 2.0.3-alpha 之前的所有版本中,此宏存在缺陷。
7.10 安全随机数生成器
许多应用程序(包括 evdns)在安全性方面需要难以预测的随机数来源。
c
void evutil_secure_rng_get_bytes(void *buf, size_t n);
evutil_secure_rng_get_bytes(void *buf, size_t n)
函数会用 n
字节的随机数据填充 buf
指向的缓冲区。
如果平台提供 arc4random()
函数,Libevent 会使用该函数;否则,它会使用自己实现的 arc4random()
,并通过操作系统的熵池(Windows 上为 CryptGenRandom
,其他系统上为 /dev/urandom
)进行种子初始化。
c
int evutil_secure_rng_init(void);
void evutil_secure_rng_add_bytes(const char *dat, size_t datlen);
无需手动初始化安全随机数生成器,但如果想确保其已成功初始化,可以调用 evutil_secure_rng_init()
。该函数会为随机数生成器播种(如果尚未播种),成功时返回 0。若返回 -1,则表示 Libevent 无法在当前操作系统上找到可靠的熵源,此时若不自行初始化,就无法安全使用该随机数生成器。
如果程序运行在可能会降低权限的环境中(例如,通过 chroot()
运行),应在执行权限降低操作之前调用 evutil_secure_rng_init()
。
可以通过调用 evutil_secure_rng_add_bytes(const void *buf, size_t n)
自行向熵池添加更多随机字节;在典型使用场景中,这通常不是必需的。
这些函数均在 Libevent 2.0.4-alpha 版本中新增。

Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!
(。◕‿◕。)感谢您的阅读与支持~~~