libevent简介[翻译]8 工具函数和可移植函数
http://www.wangafu.net/~nickm/libevent-book/Ref5_evutil.html
libevent的帮助函数和类型
<event2/util.h>
头文件中定义了很多函数,你可以使用,帮助你实现可移植性的程序使用libevent。libevent内部也是使用这些类型。
基本类型
evutil_socket_t
除了Windows下,socket都是一个int,系统通过数字顺序操作它。使用Windows socket API的时候,socket是一个SOCKET类型,类似于指向系统句柄的指针,你接收它的顺序是不明确的。我们定义了evutil_socket_t
类型,这是一个整数,可以保存socket()
或是accept()
的输出值,可以无风险的在Windows系统下转换。
定义
#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif
标准的整数类型
你经常会发现自己迷失在21世纪的C语言系统中,因为没有实现C99标准的头文件stdint.h
。为了解决这个问题,libevent定义了自己的bit-width-specific的整数。
Type | Width | Signed | Maximum | Minimum |
---|---|---|---|---|
ev_uint64_t | 64 | No | EV_UINT64_MAX | 0 |
ev_int64_t | 64 | Yes | EV_INT64_MAX | EV_INT64_MIN |
ev_uint32_t | 32 | No | EV_UINT32_MAX | 0 |
ev_int32_t | 32 | Yes | EV_INT32_MAX | EV_INT32_MIN |
ev_uint16_t | 16 | No | EV_UINT16_MAX | 0 |
ev_int16_t | 16 | Yes | EV_INT16_MAX | EV_INT16_MIN |
ev_uint8_t | 8 | No | EV_UINT8_MAX | 0 |
ev_int8_t | 8 | Yes | EV_INT8_MAX | EV_INT8_MIN |
在C99的标准下,每个类型都是正好定义的宽度
混杂的兼容类型
ev_ssize_t
类型定义于ssize_t
(signed size_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
。
一些socket的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,设置为0。如果设置了不为0,返回true,否则返回false。
接口
#define evutil_timercmp(tvp, uvp, cmp)
evutil_timercmp
宏比较两个timeval数值,如果他们处在cmp指定的关系中,返回true。比如evutil_timercmp(t1, t2, ⇐)
表示t1是否小于等于t2,注意,不想一些操作系统的版本,libevent的timercmp支持所有的C语言比较操作(<, >, ==, !=, ⇐, and >=)。
接口
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.");
Socket API的兼容性
这一章节存在是因为一些历史原因。Windows并没有完全实现兼容伯克利标准的socket API。这里有一些函数用来封装这些差别。
接口
int evutil_closesocket(evutil_socket_t s);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)
这个函数关闭一个socket。在Unix下,就是close()
的别名;在Windows下,会调用closesocket()
,你不能在Windows下调用close()
,也没有人额外定义closesocket()
。
接口
#define EVUTIL_SOCKET_ERROR()
#define EVUTIL_SET_SOCKET_ERROR(errcode)
#define evutil_socket_geterror(sock)
#define evutil_socket_error_to_string(errcode)
这些宏提供了各种socket出错时获取错误代码的操作。EVUTIL_SOCKET_ERROR()
返回当前线程socket全局的错误代码。evutil_socket_geterror()
用作更详细的socket相关的错误说明。在类Unix操作系统上,这两个是errno
。EVUTIL_SET_SOCKET_ERROR()
修改当前的socket的错误代码,类似于设置errno
在Unix上。evutil_socket_error_to_string()
返回对应socket错误代码的文字描述信息,类似于strerror()
在Unix上。
我们需要这些函数,因为Windows下并没有errno
,取而代之的是WSAGetLastError()
。
注意,在Windows上socket的错误代码并不是可以在errno
中看到的标准C语言的错误代码。
接口
int evutil_make_socket_nonblocking(evutil_socket_t sock);
由于在Windows上设置非阻塞的IO是非跨平台的。evutil_make_socket_nonblocking()
函数可以把一个新的socket(从socket()
或是accept()
获得的)转换成非阻塞IO,在Unix下设置为O_NONBLOCK
,在Windows下设置为FIONBIO
。
接口
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);
这个函数使监听socket的地址立马可用,对于已经关闭的socket。在Unix上,设置为SO_REUSEADDR
,在Windows上没有任何效果。不要想使用SO_REUSEADDR
在Windows上,意义完全不同。
接口
`cpp
int evutil_make_socket_closeonexec(evutil_socket_t sock);
告诉系统,socket将要关闭,在Unix上,会设置`FD_CLOSEXEC`,在Windows下,没任何作用。
### 接口
```cpp
int evutil_socketpair(int family, int type, int protocol,
evutil_socket_t sv[2]);
这个函数在Unix下,类似于socketpair()
,是两个socket相互连接,可以使用正常的socket IO调用。把两个socket分别放到sv[0]
和sv[1]
。成功返回0,失败返回-1.
在Windows下,只支持AF_INET
、SOCK_STREAM
和端口0.记住,由于Windows的一些防火墙,这个函数可能执行失败。
可移植的字符串操作函数
接口
ev_int64_t evutil_strtoll(const char *s, char **endptr, int base);
这个函数类似于strtol
,但是需要64位的整数。在一些平台上,只支持10位。
接口
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替换函数,行为与标准的snprintf
和vsnprintf
接口一样。返回一个整数,表示写入到缓存中多少个字节,这个字节足够长写入,但并不好办结束符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帮助和跨平台函数
接口
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_INET
,src是一个指向in_addr
结构体的指针,dst是一个指向字符串缓冲的指针,长度是len。如果是IPv6,设置af为AF_INET6
,并且src是一个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);
从str解析地址,放到out中。outlen的长度必须是能够保存输出内容的大小,在函数返回时会被修改,表示实际占用的大小。可以解析下面的格式
ipv6 (as in "ffff:😊
ipv6 (as in "[ffff::]")
ipv4:port (as in "1.2.3.4:80")
ipv4 (as in "1.2.3.4")
如果没有给定端口,默认设置端口为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,两个地址除了端口不同的话,也会认为相等,就是不考虑端口判断。不然的话,如果两个地址相等,就必须端口也相等。
结构宏可移植函数
接口
#define evutil_offsetof(type, field) /* ... */
与标准的offsetof比较,这个宏返回从类型开始的字节数。
安全的随机数生成器
很多程序包括evdns,需要一个难预知的随机数生成器为了安全。
接口
void 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()
。它会设置RNG种子,如果没有设置的话,返回0表示成功,-1表示失败。libevent不能在你的系统上找到更好的加密源,你就不可以设置RNG初始化。
如果你运行的环境,你的程序可能会删除特权,比如运行chroot()
,你可以调用evutil_secure_rng_init()
在你做上面的事情之前。
你可以增加多个随机数字节在随机数池中,通过调用evutil_secure_rng_add_bytes()
;但是这不是常见的用法。