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_MINsize_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操作系统上,这两个是errnoEVUTIL_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_INETSOCK_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替换函数,行为与标准的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帮助和跨平台函数

接口

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_INETAF_INET6,需要解析的字符串在src指向的内容,dst指向in_addrin_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();但是这不是常见的用法。

posted @ 2020-07-07 17:37  秋来叶黄  阅读(408)  评论(0编辑  收藏  举报