【传智播客】Libevent学习笔记(五):基本类型和函数

00. 目录

声明: 该博客来源于传智播客C++学院相关培训参考手册

头文件<event2/util.h> 定义了许多能够帮助我们利用libevent 实现可移植应用程序的函数。libevent会在库内部使用这些函数。

01. 基本类型

1.1 evutil_socket_t类型

在除Windows之外的大多数地方,套接字是个整数,操作系统按照数值次序进行处理。然而,使用Windows套接字API时,socket具有类型SOCKET,它实际上是个类似指针的句柄,收到这个句柄的次序是未定义的。在Windows中,libevent定义evutil_socket_t类型为整型指针,可以处理socket()或者accept()的输出,而没有指针截断的风险。

/**
 * A type wide enough to hold the output of "socket()" or "accept()".  On
 * Windows, this is an intptr_t; elsewhere, it is an int. */
#ifdef WIN32
#define evutil_socket_t intptr_t
#else
#define evutil_socket_t int
#endif

这个类型在2.0.1-alpha版本中引入。

1.2 标准类型

落后于21世纪的C系统常常没有实现C99标准规定的stdint.h头文件。考虑到这种情况,libevent定义了来自于stdint.h的、位宽度确定(bit-width-specific)的整数类型:

C99标准一样,这些类型都有明确的位宽度。

这些类型由1.4.0-alpha版本引入。MAX/MIN常量首次出现在2.0.4-alpha版本。

1.3 各种兼容性类型

​ 在有ssize_t(有符号的size_t)类型的平台上,ev_ssize_t定义为ssize_t;而在没有的平台上,则定义为某合理的默认类型。ev_ssize_t类型的最大可能值是EV_SSIZE_MAX;最小可能值是EV_SSIZE_MIN。(在平台没有定义SIZE_MAX的时候,size_t类型的最大可能值是EV_SIZE_MAX
  ev_off_t用于代表文件或者内存块中的偏移量。在有合理off_t类型定义的平台,它被定义为off_t;在Windows上则定义为ev_int64_t
  某些套接字API定义了socklen_t长度类型,有些则没有定义。在有这个类型定义的平台中,ev_socklen_t定义为socklen_t,在没有的平台上则定义为合理的默认类型。
  ev_intptr_t是一个有符号整数类型,足够容纳指针类型而不会产生截断;而ev_uintptr_t则是相应的无符号类型。
  ev_ssize_t类型由2.0.2-alpha版本加入。ev_socklen_t类型由2.0.3-alpha版本加入。ev_intptr_t与ev_uintptr_t类型,以及EV_SSIZE_MAX/MIN宏定义由2.0.4-alpha版本加入。ev_off_t类型首次出现在2.0.9-rc版本。

02. 可移植的定时器函数

不是每个平台都定义了标准timeval操作函数,所以libevent也提供了自己的实现。

这些宏分别对前两个参数进行加或者减运算,将结果存放到第三个参数中。

/**
 * @name Manipulation macros for struct timeval.
 *
 * We define replacements
 * for timeradd, timersub, timerclear, timercmp, and timerisset.
 *
 * @{
 */
#ifdef _EVENT_HAVE_TIMERADD
#define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp))
#define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp))
#else
#define evutil_timeradd(tvp, uvp, vvp)                  \
    do {                                \
        (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec;      \
        (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec;       \
        if ((vvp)->tv_usec >= 1000000) {            \
            (vvp)->tv_sec++;                \
            (vvp)->tv_usec -= 1000000;          \
        }                           \
    } while (0)
#define evutil_timersub(tvp, uvp, vvp)                  \
    do {                                \
        (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec;      \
        (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec;   \
        if ((vvp)->tv_usec < 0) {               \
            (vvp)->tv_sec--;                \
            (vvp)->tv_usec += 1000000;          \
        }                           \
    } while (0)
#endif /* !_EVENT_HAVE_HAVE_TIMERADD */

#ifdef _EVENT_HAVE_TIMERCLEAR
#define evutil_timerclear(tvp) timerclear(tvp)
#else
#define evutil_timerclear(tvp)  (tvp)->tv_sec = (tvp)->tv_usec = 0
#endif

清除timeval会将其值设置为0evutil_timerisset宏检查timeval是否已经设置,如果已经设置为非零值,返回ture,否则返回false

#ifdef _EVENT_HAVE_TIMERCLEAR
#define evutil_timerclear(tvp) timerclear(tvp)
#else
#define evutil_timerclear(tvp)  (tvp)->tv_sec = (tvp)->tv_usec = 0
#endif

#ifdef _EVENT_HAVE_TIMERISSET
#define evutil_timerisset(tvp) timerisset(tvp)
#else
#define evutil_timerisset(tvp)  ((tvp)->tv_sec || (tvp)->tv_usec)
#endif

evutil_timercmp宏比较两个timeval,如果其关系满足cmp关系运算符,返回true。比如说,evutil_timercmp(t1,t2,<=)的意思是“是否t1<=t2?”。注意:与某些操作系统版本不同的是,libevent的时间比较支持所有C关系运算符(也就是<、>、==、!=、<=和>=)。

/** Return true iff the tvp is related to uvp according to the relational
 * operator cmp.  Recognized values for cmp are ==, <=, <, >=, and >. */
#define evutil_timercmp(tvp, uvp, cmp)                  \
    (((tvp)->tv_sec == (uvp)->tv_sec) ?             \
     ((tvp)->tv_usec cmp (uvp)->tv_usec) :              \
     ((tvp)->tv_sec cmp (uvp)->tv_sec))

evutil_gettimeofdy()函数设置tv为当前时间,tz参数未使用。

/** Replacement for gettimeofday on platforms that lack it. */
#ifdef _EVENT_HAVE_GETTIMEOFDAY
#define evutil_gettimeofday(tv, tz) gettimeofday((tv), (tz))
#else
struct timezone;
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);
#endif

参考示例:

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()由2.0版本引入外,这些函数由1.4.0-beta版本引入。

注意:在1.4.4之前的版本中使用<=或者>=是不安全的。

03. 套接字API兼容性

本节由于历史原因而存在:Windows从来没有以良好兼容的方式实现Berkeley套接字API。 下面是一些用户使其兼容的函数接口

/** Do the platform-specific call needed to close a socket returned from
    socket() or accept().

    @param sock The socket to be closed
    @return 0 on success, -1 on failure
 */
int evutil_closesocket(evutil_socket_t sock);
#define EVUTIL_CLOSESOCKET(s) evutil_closesocket(s)

这个接口用于关闭socket套接字。在Unix中,它是close()的别名;在Windows中,它调用closesocket()。(在Windows中不能将close()用于套接字,也没有其他系统定义了closesocket()
evutil_closesocket()函数在2.0.5-alpha版本引入。在此之前,需要使用EVUTIL_CLOSESOCKET宏。

#ifdef WIN32
/** Return the most recent socket error.  Not idempotent on all platforms. */
#define EVUTIL_SOCKET_ERROR() WSAGetLastError()
/** Replace the most recent socket error with errcode */
#define EVUTIL_SET_SOCKET_ERROR(errcode)		\
	do { WSASetLastError(errcode); } while (0)
/** Return the most recent socket error to occur on sock. */
int evutil_socket_geterror(evutil_socket_t sock);
/** Convert a socket error to a string. */
const char *evutil_socket_error_to_string(int errcode);
#elif defined(_EVENT_IN_DOXYGEN)
/**
   @name Socket error functions

   These functions are needed for making programs compatible between
   Windows and Unix-like platforms.

   You see, Winsock handles socket errors differently from the rest of
   the world.  Elsewhere, a socket error is like any other error and is
   stored in errno.  But winsock functions require you to retrieve the
   error with a special function, and don't let you use strerror for
   the error codes.  And handling EWOULDBLOCK is ... different.

   @{
*/
/** Return the most recent socket error.  Not idempotent on all platforms. */
#define EVUTIL_SOCKET_ERROR() ...
/** Replace the most recent socket error with errcode */
#define EVUTIL_SET_SOCKET_ERROR(errcode) ...
/** Return the most recent socket error to occur on sock. */
#define evutil_socket_geterror(sock) ...
/** Convert a socket error to a string. */
#define evutil_socket_error_to_string(errcode) ...
/**@}*/
#else
#define EVUTIL_SOCKET_ERROR() (errno)
#define EVUTIL_SET_SOCKET_ERROR(errcode)		\
		do { errno = (errcode); } while (0)
#define evutil_socket_geterror(sock) (errno)
#define evutil_socket_error_to_string(errcode) (strerror(errcode))
#endif

这些宏用于访问和操作套接字错误代码。EVUTIL_SOCKET_ERROR()返回本线程最后一次套接字操作的全局错误号,evutil_socket_geterror()则返回某特定套接字的错误号。(在类Unix系统中都是errnoEVUTIL_SET_SOCKET_ERROR()修改当前套接字错误号(与设置Unix中的errno类似),evutil_socket_error_to_string()返回代表某给定套接字错误号的字符串(与Unix中的strerror()类似)。
(因为对于来自套接字函数的错误,Windows不使用errno,而是使用WSAGetLastError(),所以需要这些函数。)
注意:Windows套接字错误与从errno看到的标准C错误是不同的。

/** Do platform-specific operations as needed to make a socket nonblocking.

    @param sock The socket to make nonblocking
    @return 0 on success, -1 on failure
 */
int evutil_make_socket_nonblocking(evutil_socket_t sock);

用于对套接字进行非阻塞IO的调用也不能移植到Windows中。evutil_make_socket_nonblocking()函数要求一个套接字(来自socket()或者accept())作为参数,将其设置为非阻塞的。(设置Unix中的O_NONBLOCK标志和Windows中的FIONBIO标志)

/** Do platform-specific operations to make a listener socket reusable.

    Specifically, we want to make sure that another program will be able
    to bind this address right after we've closed the listener.

    This differs from Windows's interpretation of "reusable", which
    allows multiple listeners to bind the same address at the same time.

    @param sock The socket to make reusable
    @return 0 on success, -1 on failure
 */
int evutil_make_listen_socket_reuseable(evutil_socket_t sock);

这个函数确保关闭监听套接字后,它使用的地址可以立即被另一个套接字使用。(在Unix中它设置SO_REUSEADDR标志,在Windows中则不做任何操作。不能在Windows中使用SO_REUSEADDR标志:它有另外不同的含义(译者注:多个套接字绑定到相同地址))

/** Do platform-specific operations as needed to close a socket upon a
    successful execution of one of the exec*() functions.

    @param sock The socket to be closed
    @return 0 on success, -1 on failure
 */
int evutil_make_socket_closeonexec(evutil_socket_t sock);

这个函数告诉操作系统,如果调用了exec(),应该关闭指定的套接字。在Unix中函数设置FD_CLOEXEC标志,在Windows上则没有操作。

/** Create two new sockets that are connected to each other.

    On Unix, this simply calls socketpair().  On Windows, it uses the
    loopback network interface on 127.0.0.1, and only
    AF_INET,SOCK_STREAM are supported.

    (This may fail on some Windows hosts where firewall software has cleverly
    decided to keep 127.0.0.1 from talking to itself.)

    Parameters and return values are as for socketpair()
*/
int evutil_socketpair(int d, int type, int protocol, evutil_socket_t sv[2]);

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

在Windows中,这个函数仅能支持AF_INET协议族、SOCK_STREAM类型和0协议的套接字。注意:在防火墙软件明确阻止127.0.0.1,禁止主机与自身通话的情况下,函数可能失败。
除了evutil_make_socket_closeonexec()由2.0.4-alpha版本引入外,这些函数都由1.4.0-alpha版本引入。

04. 可移植的字符串函数

/* big-int related functions */
/** Parse a 64-bit value from a string.  Arguments are as for strtol. */
ev_int64_t evutil_strtoll(const char *s, char **endptr, int base);

这个函数与strtol行为相同,只是用于64位整数。在某些平台上,仅支持十进制。

/** Replacement for snprintf to get consistent behavior on platforms for
    which the return value of snprintf does not conform to C99.
 */
int evutil_snprintf(char *buf, size_t buflen, const char *format, ...)
#ifdef __GNUC__
	__attribute__((format(printf, 3, 4)))
#endif
;
/** Replacement for vsnprintf to get consistent behavior on platforms for
    which the return value of snprintf does not conform to C99.
 */
int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap)
#ifdef __GNUC__
	__attribute__((format(printf, 3, 0)))
#endif
;

这些snprintf替代函数的行为与标准snprintfvsnprintf接口相同。函数返回在缓冲区足够长的情况下将写入的字节数,不包括结尾的NULL字节。(这个行为遵循C99snprintf()标准,但与Windows的_snprintf()相反:如果字符串无法放入缓冲区,_snprintf()会返回负数)

evutil_strtoll()从1.4.2-rc版本就存在了,其他函数首次出现在1.4.5版本中。

05. 区域无关的字符串操作函数

实现基于ASCII的协议时,可能想要根据字符类型的ASCII记号来操作字符串,而不管当前的区域设置。libevent为此提供了一些函数:

/** As strcasecmp, but always compares the characters in locale-independent
    ASCII.  That's useful if you're handling data in ASCII-based protocols.
 */
int evutil_ascii_strcasecmp(const char *str1, const char *str2);
/** As strncasecmp, but always compares the characters in locale-independent
    ASCII.  That's useful if you're handling data in ASCII-based protocols.
 */
int evutil_ascii_strncasecmp(const char *str1, const char *str2, size_t n);

这些函数与strcasecmp()strncasecmp()的行为类似,只是它们总是使用ASCII字符集进行比较,而不管当前的区域设置。这两个函数首次在2.0.3-alpha版本出现。

06. IPv6辅助和兼容性函数


/** Replacement for inet_ntop for platforms which lack it. */
const char *evutil_inet_ntop(int af, const void *src, char *dst, size_t len);
/** Replacement for inet_pton for platforms which lack it. */
int evutil_inet_pton(int af, const char *src, void *dst);

这些函数根据RFC 3493的规定解析和格式化IPv4与IPv6地址,与标准inet_ntop()inet_pton()函数行为相同。要格式化IPv4地址,调用evutil_inet_ntop(),设置afAF_INETsrc指向in_addr结构体,dst指向大小为len的字符缓冲区。对于IPv6地址,af应该是AF_INET6src则指向in6_addr结构体。要解析IP地址,调用evutil_inet_pton(),设置afAF_INET或者AF_INET6src指向要解析的字符串,dst指向一个in_addr或者in_addr6结构体。
失败时evutil_inet_ntop()返回NULL,成功时返回到dst的指针。成功时evutil_inet_pton()返回0,失败时返回-1。

/** Parse an IPv4 or IPv6 address, with optional port, from a string.

    Recognized formats are:
    - [IPv6Address]:port
    - [IPv6Address]
    - IPv6Address
    - IPv4Address:port
    - IPv4Address

    If no port is specified, the port in the output is set to 0.

    @param str The string to parse.
    @param out A struct sockaddr to hold the result.  This should probably be
       a struct sockaddr_storage.
    @param outlen A pointer to the number of bytes that that 'out' can safely
       hold.  Set to the number of bytes used in 'out' on success.
    @return -1 if the address is not well-formed, if the port is out of range,
       or if out is not large enough to hold the result.  Otherwise returns
       0 on success.
*/
int evutil_parse_sockaddr_port(const char *str, struct sockaddr *out, int *outlen);

这个接口解析来自str的地址,将结果写入到out中。outlen参数应该指向一个表示out中可用字节数的整数;函数返回时这个整数将表示实际使用了的字节数。成功时函数返回0,失败时返回-1。函数识别下列地址格式:

l [ipv6]:端口号(如[ffff::]:80

l ipv6(如ffff::

l [ipv6](如[ffff::]

l ipv4:端口号(如1.2.3.4:80

l ipv4(如1.2.3.4

如果没有给出端口号,结果中的端口号将被设置为0

/** Compare two sockaddrs; return 0 if they are equal, or less than 0 if sa1
 * preceeds sa2, or greater than 0 if sa1 follows sa2.  If include_port is
 * true, consider the port as well as the address.  Only implemented for
 * AF_INET and AF_INET6 addresses. The ordering is not guaranteed to remain
 * the same between Libevent versions. */
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地址;对于其他地址,返回值未定义。函数确保考虑地址的完整次序,但是不同版本中的次序可能不同。

​ 如果include_port参数为false,而两个地址只有端口号不同,则它们被认为是相等的。否则,具有不同端口号的地址被认为是不等的。

​ 除evutil_sockaddr_cmp()在2.0.3-alpha版本引入外,这些函数在2.0.1-alpha版本中引入。

07. 结构体可移植性函数

/** Replacement for offsetof on platforms that don't define it. */
#ifdef offsetof
#define evutil_offsetof(type, field) offsetof(type, field)
#else
#define evutil_offsetof(type, field) ((off_t)(&((type *)0)->field))
#endif

跟标准offsetof宏一样,这个宏返回从type类型开始处到field字段的字节数。
这个宏由2.0.1-alpha版本引入,但2.0.3-alpha版本之前是有bug的。

08. 安全随机数生成器

很多应用(包括evdns)为了安全考虑需要很难预测的随机数。

/** Generate n bytes of secure pseudorandom data, and store them in buf.
 *
 * Current versions of Libevent use an ARC4-based random number generator,
 * seeded using the platform's entropy source (/dev/urandom on Unix-like
 * systems; CryptGenRandom on Windows).  This is not actually as secure as it
 * should be: ARC4 is a pretty lousy cipher, and the current implementation
 * provides only rudimentary prediction- and backtracking-resistance.  Don't
 * use this for serious cryptographic applications.
 */
void evutil_secure_rng_get_bytes(void *buf, size_t n);

这个函数用随机数据填充buf处的n个字节。
如果所在平台提供了arc4random(),libevent会使用这个函数。否则,libevent会使用自己的arc4random()实现,种子则来自操作系统的熵池(entropy pool)(Windows中的CryptGenRandom,其他平台中的/dev/urandom

/**
 * Seed the secure random number generator if needed, and return 0 on
 * success or -1 on failure.
 *
 * It is okay to call this function more than once; it will still return
 * 0 if the RNG has been successfully seeded and -1 if it can't be
 * seeded.
 *
 * Ordinarily you don't need to call this function from your own code;
 * Libevent will seed the RNG itself the first time it needs good random
 * numbers.  You only need to call it if (a) you want to double-check
 * that one of the seeding methods did succeed, or (b) you plan to drop
 * the capability to seed (by chrooting, or dropping capabilities, or
 * whatever), and you want to make sure that seeding happens before your
 * program loses the ability to do it.
 */
int evutil_secure_rng_init(void);
/**
 * Set a filename to use in place of /dev/urandom for seeding the secure
 * PRNG. Return 0 on success, -1 on failure.
 *
 * Call this function BEFORE calling any other initialization or RNG
 * functions.
 *
 * (This string will _NOT_ be copied internally. Do not free it while any
 * user of the secure RNG might be running. Don't pass anything other than a
 * real /dev/...random device file here, or you might lose security.)
 *
 * This API is unstable, and might change in a future libevent version.
 */
int evutil_secure_rng_set_urandom_device_file(char *fname);

/** Seed the random number generator with extra random bytes.

    You should almost never need to call this function; it should be
    sufficient to invoke evutil_secure_rng_init(), or let Libevent take
    care of calling evutil_secure_rng_init() on its own.

    If you call this function as a _replacement_ for the regular
    entropy sources, then you need to be sure that your input
    contains a fairly large amount of strong entropy.  Doing so is
    notoriously hard: most people who try get it wrong.  Watch out!

    @param dat a buffer full of a strong source of random numbers
    @param datlen the number of bytes to read from datlen
 */
void evutil_secure_rng_add_bytes(const char *dat, size_t datlen);

  不需要手动初始化安全随机数发生器,但是如果要确认已经成功初始化,可以调用evutil_secure_rng_init()。函数会播种RNG(如果没有播种过),并在成功时返回0。函数返回-1则表示libevent无法在操作系统中找到合适的熵源(source of entropy),如果不自己初始化RNG,就无法安全使用RNG了。
  如果程序运行在可能会放弃权限的环境中(比如说,通过执行chroot()),在放弃权限前应该调用evutil_secure_rng_init()
  可以调用evutil_secure_rng_add_bytes()向熵池加入更多随机字节,但通常不需要这么做。
这些函数是2.0.4-alpha版本引入的。

08. 参考

相关书籍: http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html

官方参考网站: https://www.monkey.org/~provos/libevent/doxygen-2.0.1/index.html

posted @ 2019-06-05 22:25  沧海一笑_DJ  阅读(762)  评论(0编辑  收藏  举报