Libevent(5)之使用教程(4)工具函數
Author: Once Day Date: 2025年8月3日
一位熱衷于Linux學習和開發的菜鳥,試圖譜寫一場冒險之旅,也許終點只是一場白日夢…
漫漫長路,有人對你微笑過嘛…
本文檔翻譯于:Fast portable non-blocking network programming with Libevent
全系列文章可參考專欄: 十年代碼訓練_Once-Day的博客-CSDN博客
參考文章:
- 詳解libevent網絡庫(一)—框架的搭建_libevent詳解-CSDN博客
- 深度思考高性能網絡庫Libevent,從13個維度來解析Libevent到底是怎么回事 - 知乎
- 深入淺出理解libevent——2萬字總結_libev 堆-CSDN博客
- Fast portable non-blocking network programming with Libevent
- libevent
- C++網絡庫:Libevent網絡庫的原理及使用方法 - 知乎
- 深入理解libevent事件庫的原理與實踐技巧-騰訊云開發者社區-騰訊云
文章目錄
- Libevent(5)之使用教程(4)工具函數
- 7. 工具函數
- 7.1 evutil_socket_t
- 7.2 標準整數類型
- 7.3 其他兼容性類型
- 7.4 定時器可移植性函數
- 7.5 套接字 API 兼容性
- 7.6 可移植的字符串操作函數
- 7.7 與區域設置無關的字符串操作函數
- 7.8 IPv6 輔助與可移植性函數
- 7.9 結構宏可移植性函數
- 7.10 安全隨機數生成器
7. 工具函數
7.1 evutil_socket_t
除 Windows 外,大多數系統中,套接字(socket)的類型是 int
,操作系統會按數字順序分配它們。但在 Windows 套接字 API 中,套接字的類型是 SOCKET
,本質上是一種類似指針的操作系統句柄,其分配順序是未定義的。我們定義 evutil_socket_t
類型為一種整數類型,確保在 Windows 系統中能夠存儲 socket()
或 accept()
的返回值,且不會出現指針截斷風險。
#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
操作函數,因此我們提供了自己的實現。
#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */
這些宏分別對前兩個參數進行加減運算,并將結果存儲在第三個參數中。
#define evutil_timerclear(tvp) /* ... */
#define evutil_timerisset(tvp) /* ... */
清空 timeval
會將其值設為零。檢查其是否已設置時,若值非零則返回真,否則返回假。
#define evutil_timercmp(tvp, uvp, cmp)
evutil_timercmp
宏用于比較兩個 timeval
,當它們滿足關系運算符 cmp
所指定的關系時,返回真。例如,evutil_timercmp(t1, t2, <=)
表示 “t1
是否小于等于 t2
?”。請注意,與某些操作系統的版本不同,Libevent 的 timercmp
支持所有 C 語言的關系運算(即 <
、>
、==
、!=
、<=
和 >=
)。
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);
evutil_gettimeofday
函數將 tv
設置為當前時間。tz
參數未被使用。
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 的函數。
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
宏。
#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
里的錯誤并不相同,需格外留意。
int evutil_make_socket_nonblocking(evutil_socket_t sock);
甚至在套接字上執行非阻塞 IO 的調用在 Windows 上也不具備可移植性。evutil_make_socket_nonblocking()
函數接收一個新套接字(來自 socket()
或 accept()
)并將其轉換為非阻塞套接字(在 Unix 上設置 O_NONBLOCK
,在 Windows 上設置 FIONBIO
)。
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
evutil_make_listen_socket_reuseable()
函數確保監聽套接字使用的地址在套接字關閉后能立即被其他套接字使用(在 Unix 上設置 SO_REUSEADDR
,在 Windows 上不執行任何操作 —— 在 Windows 上不應使用 SO_REUSEADDR
,其含義不同)。
int evutil_make_socket_closeonexec(evutil_socket_t sock);
evutil_make_socket_closeonexec()
調用告知操作系統,若調用 exec()
,則應關閉此套接字(在 Unix 上設置 FD_CLOEXEC
標志,在 Windows 上不執行任何操作)。
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 位整數。在部分平臺上,它僅支持十進制。
ev_int64_t evutil_strtoll(const char *s, char **endptr, int base);
這些 snprintf
替代函數的行為與標準的 snprintf
和 vsnprintf
接口一致。它們返回的是:若緩沖區足夠大,本應寫入緩沖區的字節數(不包含終止符 NUL 字節)。(這種行為符合 C99 標準的 snprintf()
,與 Windows 系統的 _snprintf()
不同 —— 后者在字符串無法裝入緩沖區時會返回負數。)
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 提供了一些函數來幫助實現這一點:
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 輔助與可移植性函數
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。
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。
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 結構宏可移植性函數
#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)在安全性方面需要難以預測的隨機數來源。
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
)進行種子初始化。
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
也信美人終作土,不堪幽夢太匆匆......
如果這篇文章為您帶來了幫助或啟發,不妨點個贊👍和關注,再加上一個小小的收藏?!
(。???。)感謝您的閱讀與支持~~~