libevent學習筆記【使用篇】——6a. Bufferevents高級話題

原文:http://blog.csdn.net/windeal3203/article/details/52849236
譯自: http://www.wangafu.net/~nickm/libevent-book/Ref6a_advanced_bufferevents.html
本章描述bufferevent的一些對通常使用不必要的高級特徵。如果只想學習如何使用bufferevent,可以跳過這一章,直接閱讀下一章。

1. 成對的bufferevents

有時, 網絡程序可能需要和自己通信。 舉個例子:通過某些協議對用戶連接進行隧道操作的程序,有時候也需要通過同樣的協議對自身的連接進行隧道操作。當然,可以通過打開一個到自身監聽端口的連接,讓程序使用這個連接來達到這種目標。但是,通過網絡棧來與自身通信比較浪費資源。
  有一種替代方案是:創建一對成對的bufferevent。這樣,寫入到一個bufferevent的字節都被另一個接收(反過來也是),但是不需要使用真正的套接字。

int bufferevent_pair_new(struct event_base *base, int options,
    struct bufferevent *pair[2]);

調用bufferevent_pair_new()會把pair[0]pair[1] 設置爲一對相互連接的bufferevent。除了BEV_OPT_CLOSE_ON_FREE無效、BEV_OPT_DEFER_CALLBACKS總是打開的之外,所有通用的選項都是支持的。
  爲什麼bufferevent需要帶延遲迴調運行?通常某一方上的操作會調用一個通知另一方的回調,從而調用另一方的回調,如此這樣進行很多步。如果不延遲迴調,這種調用鏈常常會導致棧溢出或者餓死其他連接,而且還要求所有的回調是可重入的。
成對的bufferevent支持flush:設置模式參數爲BEV_NORMAL或者BEV_FLUSH會強制要求所有相關數據從對中的一個bufferevent傳輸到另一箇中,而忽略可能會限制傳輸的水位設置。增加BEV_FINISHED到模式參數中還會讓對端的bufferevent產生EOF事件。
釋放對中的任何一個成員不會自動釋放另一個,也不會產生EOF事件。釋放僅僅會使對中的另一個成員成爲斷開的。bufferevent一旦斷開,就不能再成功讀寫數據或者產生任何事件了。

struct bufferevent *bufferevent_pair_get_partner(struct bufferevent *bev)

有時候在給出了對的一個成員時,需要獲取另一個成員,這時候可以使用bufferevent_pair_get_partner()。如果bev是對的成員,而且對的另一個成員仍然存在,函數將返回另一個成員;否則,函數返回NULL。

bufferevent對由2.0.1-alpha版本引入,而bufferevent_pair_get_partner()函數由2.0.6版本引入。

2 過濾bufferevent

有時候需要轉換傳遞給某bufferevent對象的所有數據,這可以通過添加一個壓縮層,或者將協議包裝到另一個協議中進行傳輸來實現。

enum bufferevent_filter_result {
        BEV_OK = 0,
        BEV_NEED_MORE = 1,
        BEV_ERROR = 2
};
typedef enum bufferevent_filter_result (*bufferevent_filter_cb)(
    struct evbuffer *source, struct evbuffer *destination, ev_ssize_t dst_limit,
    enum bufferevent_flush_mode mode, void *ctx);


struct bufferevent *bufferevent_filter_new(struct bufferevent *underlying,
        bufferevent_filter_cb input_filter,
        bufferevent_filter_cb output_filter,
        int options,
        void (*free_context)(void *),
        void *ctx);

bufferevent_filter_new()函數創建一個封裝現有的“底層”bufferevent的過濾bufferevent。所有通過底層bufferevent接收的數據在到達過濾bufferevent之前都會經過“input”過濾器的轉換;所有通過底層bufferevent發送的數據在被髮送到底層bufferevent之前都會經過“output”過濾器的轉換。
向底層bufferevent添加過濾器將替換其回調函數。可以向底層bufferevent的evbuffer添加回調函數,但是如果想讓過濾器正確工作,就不能再設置bufferevent本身的回調函數。
input_filteroutput_filter函數將在後面描述。options參數支持所有通常的選項。如果設置了BEV_OPT_CLOSE_ON_FREE,那麼釋放過濾bufferevent也會同時釋放底層bufferevent。ctx參數是傳遞給過濾函數的任意指針;如果提供了free_context 實參,則在釋放ctx之前它會被調用。
底層輸入緩衝區有數據可讀時,input filter函數會被調用;過濾器的輸出緩衝區有新的數據待寫入時,output filter函數會被調用。兩個過濾器函數都有一對evbuffer參數:從source evbuffer讀取數據;向destination evbuffer寫入數據,而dst_limit參數描述了可以寫入到destination的字節數上限。過濾器函數可以忽略這個參數,但是這樣可能會違背高水位或者速率限制。如果dst_limit是-1,則沒有限制。mode參數向過濾器描述了寫入的方式。值BEV_NORMAL表示應該在方便轉換的基礎上寫入儘可能多的數據;而BEV_FLUSH表示寫入儘可能多的數據;BEV_FINISHED表示過濾器函數應該在流的末尾執行額外的清理操作。最後,過濾器函數的ctx參數就是傳遞給bufferevent_filter_new()函數的指針(ctx參數)。
如果成功向目標緩衝區寫入了任何數據,過濾器函數應該返回BEV_OK;如果不獲得更多的輸入,或者不使用不同的清空(flush)模式,就不能向目標緩衝區寫入更多的數據,則應該返回BEV_NEED_MORE;如果過濾器上發生了不可恢復的錯誤,則應該返回BEV_ERROR
創建過濾器將啓用底層bufferevent的讀取和寫入。隨後就不需要自己管理讀取和寫入了:過濾器在不想讀取的時候會自動掛起底層bufferevent的讀取。從2.0.8-rc版本開始,可以在過濾器之外獨立地啓用/禁用底層bufferevent的讀取和寫入。然而,這樣可能會讓過濾器不能成功取得所需要的數據。
不需要同時指定輸入和輸出過濾器:沒有給定的過濾器將被一個不進行數據轉換的過濾器取代。

3 限制每次讀寫的最大值

默認情況下,bufferevent在每一次請求的時候,不會讀、寫(可能的)最大字節數,因爲這會導致不可預知的錯誤和資源匱乏。 然而,這種默認值,可能不一定所有的情況都生效。

int bufferevent_set_max_single_read(struct bufferevent *bev, size_t size);
int bufferevent_set_max_single_write(struct bufferevent *bev, size_t size);

ev_ssize_t bufferevent_get_max_single_read(struct bufferevent *bev);
ev_ssize_t bufferevent_get_max_single_write(struct bufferevent *bev);

兩個set 函數分別替換了當前讀寫的最大值。 如果size值爲0 或者大於EV_SSIZE_MAX, 則可讀、可寫的maxima被設置爲默認值。函數返回0表示成功,-1表示失敗。
兩個get函數分別用於返回當前每次讀寫的maxima。
這些函數在2.1.1-alpha開始被加入。

4 bufferevent和速率限制

某些程序需要限制單個或者一組bufferevent使用的帶寬。2.0.4-alpha和2.0.5-alpha版本添加了爲單個或者一組bufferevent設置速率限制的基本功能。

4.1 速率限制模型

libevent的速率限制使用記號存儲器(token bucket)算法確定在某時刻可以寫入或者讀取多少字節。每個速率限制對象在任何給定時刻都有一個讀存儲器(read bucket)和一個寫存儲器(write bucket),其大小決定了對象允許立即讀取或者寫入多少字節。每個bucket有一個填充速率,一個最大突發尺寸,和一個時間單位,或者說“滴答(tick)”。每當一個時間單位流逝後,存儲器被填充一些字節(決定於填充速率)——但是如果超過其突發尺寸,則超出的字節會丟失。
因此,填充速率決定了對象發送或者接收字節的最大平均速率,而突發尺寸決定了在單次突發中可以發送或者接收的最大字節數;時間單位則確定了傳輸的平滑程度。

4.2 在bufferevent上設置速率限制

#define EV_RATE_LIMIT_MAX EV_SSIZE_MAX
struct ev_token_bucket_cfg;
struct ev_token_bucket_cfg *ev_token_bucket_cfg_new(
        size_t read_rate, size_t read_burst,
        size_t write_rate, size_t write_burst,
        const struct timeval *tick_len);
void ev_token_bucket_cfg_free(struct ev_token_bucket_cfg *cfg);
int bufferevent_set_rate_limit(struct bufferevent *bev,
    struct ev_token_bucket_cfg *cfg);

一個ev_token_bucket_cfg結構體表示用於限制單個或者一組bufferevent的用於限制讀寫的一對記號存儲器的配置值。要創建一個ev_token_bucket_cfg,調用ev_token_bucket_cfg_new函數,並且提供最大平均讀取速率、最大突發讀取量、最大平均寫入速率、最大突發寫入量,以及一個滴答的長度。如果tick_len參數爲NULL,則默認的滴答長度爲一秒。如果發生錯誤,函數會返回NULL
注意:read_ratewrite_rate參數的單位是字節每滴答。也就是說,如果滴答長度是十分之一秒,read_rate是300,則最大平均讀取速率是3000字節每秒。此外,不支持大於EV_RATE_LIMIT_MAX的速率或者突發量。
要限制bufferevent的傳輸速率,使用一個ev_token_bucket_cfg,對其調用bufferevent_set_rate_limit()。成功時函數返回0,失敗時返回-1。可以對任意數量的bufferevent使用相同的ev_token_bucket_cfg。要移除速率限制,可以調用bufferevent_set_rate_limit(),傳遞NULL作爲cfg參數值。
調用ev_token_bucket_cfg_free()可以釋放ev_token_bucket_cfg。注意:當前在沒有任何bufferevent使用ev_token_bucket_cfg之前進行釋放是不安全的。

4.3 設置一組bufferevent的速率限制

如果要限制一組bufferevent總的帶寬使用,可以將它們分配到一個速率限制組中。

struct bufferevent_rate_limit_group;

struct bufferevent_rate_limit_group *bufferevent_rate_limit_group_new(
        struct event_base *base,
        const struct ev_token_bucket_cfg *cfg);
int bufferevent_rate_limit_group_set_cfg(
        struct bufferevent_rate_limit_group *group,
        const struct ev_token_bucket_cfg *cfg);
void bufferevent_rate_limit_group_free(struct bufferevent_rate_limit_group *);
int bufferevent_add_to_rate_limit_group(struct bufferevent *bev,
    struct bufferevent_rate_limit_group *g);
int bufferevent_remove_from_rate_limit_group(struct bufferevent *bev);

要創建速率限制組,使用一個event_base和一個已經初始化的ev_token_bucket_cfg作爲參數調用bufferevent_rate_limit_group函數。使用bufferevent_add_to_rate_limit_group將bufferevent添加到組中;使用bufferevent_remove_from_rate_limit_group從組中刪除bufferevent。這些函數成功時返回0,失敗時返回-1。
單個bufferevent在某時刻只能是一個速率限制組的成員。bufferevent可以同時有單獨的速率限制(通過bufferevent_set_rate_limit設置)和組速率限制。設置了這兩個限制時,對每個bufferevent,較低的限制將被應用。
調用bufferevent_rate_limit_group_set_cfg修改組的速率限制。函數成功時返回0,失敗時返回-1。bufferevent_rate_limit_group_free函數釋放速率限制組,移除所有成員。
在2.0版本中,組速率限制試圖實現總體的公平,但是具體實現可能在小的時間範圍內並不公平。如果你強烈關注調度的公平性,請幫助提供未來版本的補丁。

4.4 檢查當前速率限制

有時候需要得知應用到給定bufferevent或者組的速率限制,爲此,libevent提供了函數:

ev_ssize_t bufferevent_get_read_limit(struct bufferevent *bev);
ev_ssize_t bufferevent_get_write_limit(struct bufferevent *bev);
ev_ssize_t bufferevent_rate_limit_group_get_read_limit(
        struct bufferevent_rate_limit_group *);
ev_ssize_t bufferevent_rate_limit_group_get_write_limit(
        struct bufferevent_rate_limit_group *);

上述函數返回以字節爲單位的bufferevent或者組的讀寫記號存儲器大小。注意:如果bufferevent已經被強制超過其配置(清空(flush)操作就會這樣),則這些值可能是負數。

ev_ssize_t bufferevent_get_max_to_read(struct bufferevent *bev);
ev_ssize_t bufferevent_get_max_to_write(struct bufferevent *bev);

這些函數返回了一個bufferevent能夠立即讀寫的字節數, 這裏考慮了任何應用到bufferevent的速率限制, 比如組速率限制(如果有的話),以及libevent全局設置的maximum-to-read/write-at-a-time values。

void bufferevent_rate_limit_group_get_totals(
    struct bufferevent_rate_limit_group *grp,
    ev_uint64_t *total_read_out, ev_uint64_t *total_written_out);
void bufferevent_rate_limit_group_reset_totals(
    struct bufferevent_rate_limit_group *grp);

每個bufferevent_rate_limit_group跟蹤經其發送的總的字節數,這可用於跟蹤組中所有bufferevent總的使用情況。對一個組調用bufferevent_rate_limit_group_get_totals會分別設置total_read_outtotal_written_out爲組的總讀取和寫入字節數。組創建的時候這些計數從0開始,調用bufferevent_rate_limit_group_reset_totals會復位計數爲0。

4.5 手動調整速率限制

對於有複雜需求的程序,可能需要手動調整記號存儲器的當前值。比如說,如果程序不通過使用bufferevent的方式產生一些通信量時。

int bufferevent_decrement_read_limit(struct bufferevent *bev, ev_ssize_t decr);
int bufferevent_decrement_write_limit(struct bufferevent *bev, ev_ssize_t decr);
int bufferevent_rate_limit_group_decrement_read(
        struct bufferevent_rate_limit_group *grp, ev_ssize_t decr);
int bufferevent_rate_limit_group_decrement_write(
        struct bufferevent_rate_limit_group *grp, ev_ssize_t decr);

這些函數減小某個bufferevent或者速率限制組的當前讀或者寫存儲器。注意:減小是有符號的。如果要增加存儲器,就傳入負值。

4.6 設置速率限制組的最小可能共享

通常,不希望在每個滴答中爲速率限制組中的所有bufferevent平等地分配可用的字節。比如說,有一個含有10000個活動bufferevent的速率限制組,它在每個滴答中可以寫入10000字節,那麼,因爲系統調用和TCP頭部的開銷,讓每個bufferevent在每個滴答中僅寫入1字節是低效的。
爲解決此問題,速率限制組有一個“最小共享(minimum share)”的概念。在上述情況下,不是允許每個bufferevent在每個滴答中寫入1字節,而是在每個滴答中允許某個bufferevent寫入指定(最小共享)字節,而其餘的bufferevent將不允許寫入。允許哪個bufferevent寫入將在每個滴答中隨機選擇。
默認的最小共享值具有較好的性能,當前(2.0.6-rc版本)其值爲64。可以通過這個函數調整最小共享值:

int bufferevent_rate_limit_group_set_min_share(
        struct bufferevent_rate_limit_group *group, size_t min_share);

設置min_share爲0將會完全禁止最小共享。
速率限制功能從引入開始就具有最小共享了,而修改最小共享的函數在2.0.6-rc版本首次引入。

4.7 速率限制實現的限制

就2.0版本來說,libevent的速率限制具有一些實現上的限制:

  • 不是每種bufferevent類型都良好地或者說完整地支持速率限制。
  • bufferevent速率限制組不能嵌套,一個bufferevent在某時刻只能屬於一個速率限制組。
  • 速率限制實現僅計算TCP分組傳輸的數據,不包括TCP頭部。
  • 讀速率限制實現依賴於TCP棧通知應用程序僅僅以某速率消費數據,並且在其緩衝區滿的時候將數據推送到TCP連接的另一端。
  • 某些bufferevent實現(特別是Windows中的IOCP實現)可能調撥過度。
  • 存儲器開始於一個滴答的通信量。這意味着bufferevent可以立即開始讀取或者寫入,而不用等待一個滴答的時間。但是這也意味着速率被限制爲N.1個滴答的bufferevent可能傳輸N+1個滴答的通信量。
  • 滴答不能小於1毫秒,毫秒的小數部分都被忽略。

5 Bufferevent 和 SSL

bufferevent可以使用OpenSSL庫實現SSL/TLS安全傳輸層。因爲很多應用不需要或者不想鏈接OpenSSL,這部分功能在單獨的libevent_openssl庫中實現。未來版本的libevent可能會添加其他SSL/TLS庫,如NSS或者GnuTLS,但是當前只有OpenSSL。
OpenSSL功能在2.0.3-alpha版本引入,然而直到2.0.5-beta和2.0.6-rc版本才能良好工作。
這一節不包含對OpenSSL、SSL/TLS或者密碼學的概述。
這一節描述的函數都在event2/bufferevent_ssl.h中聲明。

5.1 創建和使用基於OpenSSL的bufferevent

enum bufferevent_ssl_state {
        BUFFEREVENT_SSL_OPEN = 0,
        BUFFEREVENT_SSL_CONNECTING = 1,
        BUFFEREVENT_SSL_ACCEPTING = 2
};

struct bufferevent *
bufferevent_openssl_filter_new(struct event_base *base,
    struct bufferevent *underlying,
    SSL *ssl,
    enum bufferevent_ssl_state state,
    int options);

struct bufferevent *
bufferevent_openssl_socket_new(struct event_base *base,
    evutil_socket_t fd,
    SSL *ssl,
    enum bufferevent_ssl_state state,
    int options);

可以創建兩種類型的SSL bufferevent:基於過濾器的、在另一個底層bufferevent之上進行通信的buffervent;或者基於套接字的、直接使用OpenSSL進行網絡通信的bufferevent。這兩種bufferevent都要求提供SSL對象及其狀態描述。如果SSL當前作爲客戶端在進行協商,狀態應該是BUFFEREVENT_SSL_CONNECTING;如果作爲服務器在進行協商,則是BUFFEREVENT_SSL_ACCEPTING;如果SSL握手已經完成,則狀態是BUFFEREVENT_SSL_OPEN
接受通常的選項。BEV_OPT_CLOSE_ON_FREE表示在關閉openssl bufferevent對象的時候同時關閉SSL對象和底層fd或者bufferevent。
一旦握手完成,新的bufferevent的事件回調會被喚醒,並攜帶了BEV_EVENT_CONNECTED標誌。
創建基於套接字的bufferevent時,如果SSL對象已經設置了套接字,就不需要提供套接字了:只要傳遞-1就可以。也可以隨後調用bufferevent_setfd()來設置。
/// TODO: 一旦bufferevent_shutdownAPI完成時,移除以下內容。
注意: 當BEV_OPT_CLOSE_ON_FREE被設置在SSL bufferevent時, SSL 連接並不會執行clean shutdown。 這會導致兩個問題: 首先,在對端看起來,連接似乎是broken了,而不是被清潔後關閉: 對端不會被告是你關閉了連接, 或者是連接受到了第三方的攻擊而broken了; 其次, OpenSSL會將這個會話視爲“bad”, 並且在session cache中被移除。 這會導致SSL應用程序性能的嚴重惡化。
目前,僅有的workground是使用lasy SSL 手動shutdowns。 雖然它不符合TLS RFC, 但可以保證會話被關閉時也會保留在cache中。 下面是當前workgroud的實現:

SSL *ctx = bufferevent_openssl_get_ssl(bev);

/*
 * SSL_RECEIVED_SHUTDOWN tells SSL_shutdown to act as if we had already
 * received a close notify from the other end.  SSL_shutdown will then
 * send the final close notify in reply.  The other end will receive the
 * close notify and send theirs.  By this time, we will have already
 * closed the socket and the other end's real close notify will never be
 * received.  In effect, both sides will think that they have completed a
 * clean shutdown and keep their sessions valid.  This strategy will fail
 * if the socket is not ready for writing, in which case this hack will
 * lead to an unclean shutdown and lost session on the other end.
 */
SSL_set_shutdown(ctx, SSL_RECEIVED_SHUTDOWN);
SSL_shutdown(ctx);
bufferevent_free(bev);
SSL *bufferevent_openssl_get_ssl(struct bufferevent *bev);

這個函數返回OpenSSL bufferevent使用的SSL對象。如果bev不是一個基於OpenSSL的bufferevent,則返回NULL

unsigned long bufferevent_get_openssl_error(struct bufferevent *bev);

這個函數返回給定bufferevent的第一個未決的OpenSSL錯誤;如果沒有未決的錯誤,則返回0。錯誤值的格式與openssl庫中的ERR_get_error()返回的相同。

int bufferevent_ssl_renegotiate(struct bufferevent *bev);

調用這個函數要求SSL重新協商,bufferevent會調用合適的回調函數。這是個高級功能,通常應該避免使用,除非你確實知道自己在做什麼,特別是有些SSL版本具有與重新協商相關的安全問題。

int bufferevent_openssl_get_allow_dirty_shutdown(struct bufferevent *bev);
void bufferevent_openssl_set_allow_dirty_shutdown(struct bufferevent *bev,
    int allow_dirty_shutdown);

所有好的設計的SSL協議(SSLv 和 TLS)都支持 在shutdown操作時進行權限認證, 這能夠將主動(有意的)關閉 與 意外,或者遭受攻擊的關閉區別開來。 默認情況下,我們將所有shutdown都視爲連接上的error。 如果allow_dirty_shutdown被設置爲1, 我們會將連接的關閉視爲BEV_EVENT_EOF

allow_dirty_shutdown 函數在Libevent 2.1.1-alpha中加入。

* Example: 一個簡單的基於SSL的echo服務器*

/* Simple echo server using OpenSSL bufferevents */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>

#include <event.h>
#include <event2/listener.h>
#include <event2/bufferevent_ssl.h>

static void
ssl_readcb(struct bufferevent * bev, void * arg)
{
    struct evbuffer *in = bufferevent_get_input(bev);

    printf("Received %zu bytes\n", evbuffer_get_length(in));
    printf("----- data ----\n");
    printf("%.*s\n", (int)evbuffer_get_length(in), evbuffer_pullup(in, -1));

    bufferevent_write_buffer(bev, in);
}

static void
ssl_acceptcb(struct evconnlistener *serv, int sock, struct sockaddr *sa,
             int sa_len, void *arg)
{
    struct event_base *evbase;
    struct bufferevent *bev;
    SSL_CTX *server_ctx;
    SSL *client_ctx;

    server_ctx = (SSL_CTX *)arg;
    client_ctx = SSL_new(server_ctx);
    evbase = evconnlistener_get_base(serv);

    bev = bufferevent_openssl_socket_new(evbase, sock, client_ctx,
                                         BUFFEREVENT_SSL_ACCEPTING,
                                         BEV_OPT_CLOSE_ON_FREE);

    bufferevent_enable(bev, EV_READ);
    bufferevent_setcb(bev, ssl_readcb, NULL, NULL, NULL);
}

static SSL_CTX *
evssl_init(void)
{
    SSL_CTX  *server_ctx;

    /* Initialize the OpenSSL library */
    SSL_load_error_strings();
    SSL_library_init();
    /* We MUST have entropy, or else there's no point to crypto. */
    if (!RAND_poll())
        return NULL;

    server_ctx = SSL_CTX_new(SSLv23_server_method());

    if (! SSL_CTX_use_certificate_chain_file(server_ctx, "cert") ||
        ! SSL_CTX_use_PrivateKey_file(server_ctx, "pkey", SSL_FILETYPE_PEM)) {
        puts("Couldn't read 'pkey' or 'cert' file.  To generate a key\n"
           "and self-signed certificate, run:\n"
           "  openssl genrsa -out pkey 2048\n"
           "  openssl req -new -key pkey -out cert.req\n"
           "  openssl x509 -req -days 365 -in cert.req -signkey pkey -out cert");
        return NULL;
    }
    SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv2);

    return server_ctx;
}

int
main(int argc, char **argv)
{
    SSL_CTX *ctx;
    struct evconnlistener *listener;
    struct event_base *evbase;
    struct sockaddr_in sin;

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(9999);
    sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */

    ctx = evssl_init();
    if (ctx == NULL)
        return 1;
    evbase = event_base_new();
    listener = evconnlistener_new_bind(
                         evbase, ssl_acceptcb, (void *)ctx,
                         LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 1024,
                         (struct sockaddr *)&sin, sizeof(sin));

    event_base_loop(evbase, 0);

    evconnlistener_free(listener);
    SSL_CTX_free(ctx);

    return 0;
}

5.2 OpenSSL 與 線程 的一些注意事項

Libevent內建的線程機制不支持Openssl鎖。 因爲Openssl使用了大量的全局變量, 你必須把OpenSSL配置爲線程安全。 這部分內容不屬於Libevent的範疇, 這一話題還有待討論。

* Example: 一個很簡單的關於如何開啓Openssl線程安全的例子*

/*
 * Please refer to OpenSSL documentation to verify you are doing this correctly,
 * Libevent does not guarantee this code is the complete picture, but to be used
 * only as an example.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <pthread.h>
#include <openssl/ssl.h>
#include <openssl/crypto.h>

pthread_mutex_t * ssl_locks;
int ssl_num_locks;

/* Implements a thread-ID function as requied by openssl */
static unsigned long
get_thread_id_cb(void)
{
    return (unsigned long)pthread_self();
}

static void
thread_lock_cb(int mode, int which, const char * f, int l)
{
    if (which < ssl_num_locks) {
        if (mode & CRYPTO_LOCK) {
            pthread_mutex_lock(&(ssl_locks[which]));
        } else {
            pthread_mutex_unlock(&(ssl_locks[which]));
        }
    }
}

int
init_ssl_locking(void)
{
    int i;

    ssl_num_locks = CRYPTO_num_locks();
    ssl_locks = malloc(ssl_num_locks * sizeof(pthread_mutex_t));
    if (ssl_locks == NULL)
        return -1;

    for (i = 0; i < ssl_num_locks; i++) {
        pthread_mutex_init(&(ssl_locks[i]), NULL);
    }

    CRYPTO_set_id_callback(get_thread_id_cb);
    CRYPTO_set_locking_callback(thread_lock_cb);

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章