【傳智播客】Libevent學習筆記(五):基本類型和函數

00. 目錄

聲明: 該博客來源於傳智播客C++學院相關培訓參考手冊c++

頭文件<event2/util.h> 定義了許多可以幫助咱們利用libevent 實現可移植應用程序的函數。libevent會在庫內部使用這些函數。api

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版本中引入。app

1.2 標準類型

落後於21世紀的C系統經常沒有實現C99標準規定的stdint.h頭文件。考慮到這種狀況,libevent定義了來自於stdint.h的、位寬度肯定(bit-width-specific)的整數類型:
less

C99標準同樣,這些類型都有明確的位寬度。dom

這些類型由1.4.0-alpha版本引入。MAX/MIN常量首次出如今2.0.4-alpha版本。socket

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版本。ide

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

相關文章
相關標籤/搜索