Libevent 的辅助函数和类型

<event2/util.h>头文件定义了许多函数,您可能会发现这些函数对使用 Libevent 实现可移植应用程序很有帮助。 libevent 在内部使用这些类型和函数。

基本类型

evutil_socket_t

在除了Windows之外的大部分地方,socket是一个int值,操作系统按数字的顺序处理它们。但是在Windows上,socket的类型为SOCKET,这是一个类似指针的操作系统句柄,并且你接收到它们的顺序是未定义的。我们将 evutil_socket_t 类型定义为一个整数,它可以保存 socket()accept() 的输出,而不会在 Windows 上指针截断的风险。

#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif

标准整数类型

在较古老的系统上,没有C99的stdint.h头文件,为了应对这种情况,libevent定义了自己版本的特定位宽的整数。

TypeWidthSignedMaximumMinimum
ev_uint64_t64NoEV_UINT64_MAX0
ev_int64_t64YesEV_INT64_MAXEV_INT64_MIN
ev_uint32_t32NoEV_UINT32_MAX0
ev_int32_t32YesEV_INT32_MAXEV_INT32_MIN
ev_uint16_t16NoEV_UINT16_MAX0
ev_int16_t16YesEV_INT16_MAXEV_INT16_MIN
ev_uint8_t8NoEV_UINT8_MAX0
ev_int8_t8YesEV_INT8_MAXEV_INT8_MIN

与 C99 标准一样,每种类型都具有指定的宽度(以位为单位)。

其他兼容类型

如果平台上包含ssize_t,则ev_ssize_t 类型被定义为 ssize_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

套接字 API 的某些实现提供了长度类型 socklen_t,而有些则不提供。 ev_socklen_t 在它存在的平台上定义为这种类型,否则为合理的默认值。

ev_intptr_t 类型是一个有符号整数,它大到足以容纳一个指针而不会丢失位。 ev_uintptr_t 类型是一个无符号整数,大到足以容纳一个指针而不会丢失位。

定时器函数

并非每个平台都定义了标准的 timeval 操作函数,因此我们提供了自己的实现。

#define evutil_timeradd(tvp, uvp, vvp) /* ... */
#define evutil_timersub(tvp, uvp, vvp) /* ... */

这些宏分别添加或减去它们的前两个参数,并将结果存储在第三个参数中。

#define evutil_timerclear(tvp) /* ... */
#define evutil_timerisset(tvp) /* ... */

清除 timeval 会将其值设置为零。如果它是非零的,则检查它是否被设置返回true,否则返回false。

#define evutil_timercmp(tvp, uvp, cmp)

evutil_timercmp 宏比较两个时间值,如果它们处于关系运算符 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.");

套接字 API 兼容性

本节之所以存在,是因为由于历史原因,Windows 从未真正以良好兼容(并且非常兼容)的方式实现 Berkeley 套接字 API。以下是您可以用来假装它具有的一些功能。

int evutil_closesocket(evutil_socket_t s);

#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)

这个函数关闭一个套接字。在 Unix 上,它是 close() 的别名;在 Windows 上,它调用 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没有使用socket错误使用errno,取而代之的是WSAGetLastError()。

注意,Windows 套接字错误与您在 errno 中看到的标准 C 错误不同。

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

此函数确保监听套接字使用的地址在套接字关闭后立即可用于另一个套接字。 (它在 Unix 上设置 SO_REUSEADDR 而在 Windows 上什么也不做。你不想在 Windows 上使用 SO_REUSEADDR;它在那里意味着不同的东西。)

int evutil_make_socket_closeonexec(evutil_socket_t sock);

这个调用告诉操作系统如果我们调用 exec() 就应该关闭这个套接字。它在 Unix 上设置 FD_CLOEXEC 标志,而在 Windows 上什么也不做。

int evutil_socketpair(int family, int type, int protocol,
        evutil_socket_t sv[2]);

这个函数的行为就像 Unix socketpair()调用:它创建两个相互连接的套接字,可以与普通的套接字 IO 调用一起使用。它将两个套接字存储在 sv[0] sv[1] 中,成功返回 0,失败返回 -1。

在 Windows 上,这仅支持家族 AF_INET、类型 SOCK_STREAM 和协议 0。请注意,这可能会在某些 Windows 主机上失败,其中防火墙软件巧妙地对 127.0.0.1 进行了防火墙保护,以防止主机与自身对话。

字符串操作函数

ev_int64_t evutil_strtoll(const char *s, char **endptr, int base);

此函数的行为与 strtol 相同,但处理 64 位整数。在某些平台上,它仅支持十进制。

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

这些 snprintf 替换函数的行为与标准 snprintfvsnprintf 接口相同。如果缓冲区足够长,它们返回将写入缓冲区的字节数,不计算终止的 NUL 字节。 (此行为符合 C99 snprintf() 标准,与 Windows _snprintf() 形成对比,后者在字符串不适合缓冲区时返回负数。)

与语言环境无关的字符串操作函数

有时,在实现基于 ASCII 的协议时,您希望根据 ASCII 的字符类型概念来操作字符串,而不管您当前的语言环境如何。 Libevent 提供了一些函数来帮助解决这个问题:

int evutil_ascii_strcasecmp(const char *str1, const char *str2);
int evutil_ascii_strncasecmp(const char *str1, const char *str2, size_t n);

这些函数的行为与 strcasecmp() strncasecmp() 一样,不同之处在于它们总是使用 ASCII 字符集进行比较,而不管当前的语言环境如何。

IPV6相关

因为暂时用不到,所以没有翻译。

结构宏可移植函数

#define evutil_offsetof(type, field) /* ... */

作为标准的 offsetof 宏,这个宏产生从字段出现的类型开始的字节数。

安全随机数生成器

许多应用程序(包括 evdns)需要一个难以预测的随机数来源以确保其安全性。

void evutil_secure_rng_get_bytes(void *buf, size_t n);

此函数用 n 字节的随机数据填充 buf 的 n 字节缓冲区。

如果您的平台提供 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 无法在您的操作系统上找到良好的熵源,并且您无法在不自行初始化的情况下安全地使用 RNG。

如果您在程序可能会放弃特权的环境中运行(例如,通过运行 chroot()),您应该在这样做之前调用 evutil_secure_rng_init()

您可以通过调用 evutil_secure_rng_add_bytes() 自己向熵池添加更多随机字节;这在典型使用中不是必需的。

标签: libevent

添加新评论