Libevent:8Bufferevents高级主题

       本章描述的是Libevent的bufferevent实现的一些高级特性,这对于普通应用来说并非必须的。如果你只是学习如何使用bufferevent,则应该跳过本章去阅读evbuffer的章节。

 

一:成对的bufferevent

       有时,网络程序可能需要与自己本身进行对话。比如,某个程序用来在某些协议之上进行隧道用户链接,而有时它需要在这种协议之上,隧道与自己的连接。当然,这可以通过打开一个到自己监听端口的链接来实现,然而通过网络栈来实现与自己的对话,显然是浪费资源的。

       作为替代,可以创建一对“成对的”bufferevent(paired  bufferevents),写入一个bufferevent的字节都会在另一个bufferevent上接收到(反之亦然),但是不使用任何实际的socket平台。

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的状态,而这又会引起另一个bufferevent回调函数的调用,如此会一直循环往复下去。如果回调函数不被延迟,那么这种调用链条就会导致栈溢出,饿死其他链接,并且使得所有回调函数都折返。

       “bufferevent对”支持flush;无论是设置为BEV_NORMAL 还是BEV_FLUSH,都会使所有相关数据从bufferevent对的一端传送到另一端,而忽略水位线的限制。 设置BEV_FINISHED还会使对端bufferevent额外的产生EOF事件。

         释放“bufferevent对”的一端,不会使得另一端也自动释放或是产生EOF事件;这只会使得对端的bufferevent变为unlink。一旦bufferevent变为unlink状态,那它就再也不能进行读写数据,也不会产生任何事件了。

      

struct bufferevent  *bufferevent_pair_get_partner(struct  bufferevent  *bev)

       有时会需要在给定“bufferevent对”的一端的情况下,得到对端的bufferevent。这可以通过调用bufferevent_pair_get_partner函数进行实现。如果bev是“bufferevent对”的一个成员,而且对端bufferevent依然存在,则该函数会返回对端bufferevent,否则会返回NULL。

 

二:过滤型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之前都会经过输入过滤器进行转换,而且所有传送到底层bufferevent的数据,之前都会发送到过滤型bufferevent,通过输出过滤器进行转换。

       为一个底层bufferevent添加过滤,会替换底层bufferevent的回调函数。依然可以向底层bufferevent的evbuffers添加回调函数,但是如果希望过滤器还能工作的话,就不能设置bufferevent本身的回调函数。

 

       输入过滤器input_filter和输出过滤器output_filter函数在下面进行描述。options中支持所有常用选项。如果设置了BEV_OPT_CLOSE_ON_FREE,那么释放过滤型bufferevent也会释放底层bufferevent。ctx是一个传递给过滤函数的可选指针;如果提供了free_context函数的话,则在关闭过滤型bufferevent之前,该函数会在ctx上进行调用。

       当底层bufferevent的输入缓冲区中有新的可读数据时,就会调用输入过滤器函数。当过滤型bufferevent的输出缓冲区中有新的可写数据时,就会调用输出过滤器函数。每个过滤器函数都会接收一对evbuffers作为参数:从source evbuffer中读取数据,向destination evbuffer中写入数据。dst_limit参数描述了向destination中添加数据的上限。过滤器函数可以忽略该参数,但是这样做可能会违反高水位线或速率限制。如果dst_limit置为-1,则表示无限制。mode参数用于在输出时改变过滤器的行为。如果置为BEV_NORMAL,意味着便于转化的输出,置为BEV_FLUSH意味着尽可能多的输出,BEV_FINISHED意味着过滤器函数需要在流的末尾进行必要的清理工作。最后,过滤器函数的ctx参数是在调用函数bufferevent_filter_new()时提供的void指针。

       只要有任何数据成功的写入了目标buffer中,过滤器函数就必须返回BEV_OK,BEV_NEED_MORE意味着不能再向目标buffer写入更多的数据了,除非获得更多的输入,或者使用不同的flush模式。如果过滤器中发生了不可恢复的错误,则返回BEV_ERROR。

       创建过滤器会使能底层bufferevent上的读和写操作。无需亲自管理读写:当不再需要读取时,过滤器就会挂起底层bufferevent的读操作。对于2.0.8-rc以及之后的版本,允许独立于过滤器,对底层bufferevent的输入和输出操作进行使能或禁止操作。但是这样做的话,有可能会使得过滤器不能得到它想要的数据。

       输入过滤器和输出过滤器无需全部指定,如果省略了某个过滤器,则数据不会被转化而直接被转送。

 

三:限制单次读写最大量

       默认情况下,在每次event loop的调用中,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,那么将会设置最大量为默认值。这些函数成功时返回0,失败是返回-1.

       两个get函数返回当前每次loop调用时的读写最大量。

 

四:bufferevent的速率限制

       某些程序会希望限制单个bufferevent或者一组bufferevent所能使用的带宽。Libevent 2.0.4-alpha 和 Libevent 2.0.5-alpha增加了基本功能用来限制单个bufferevent,或者将bufferevent分配到一个“速率限制组”(rate-limited group)当中。

1:速率限制模式

       Libevent的速率限制,使用令牌桶算法来决定每次读写的数据量。在任何给定时间,每一个速率限制对象,都有一个“读桶”和“写桶”,它们的大小决定了该对象能够立即读写的字节数。每个桶都有一个填充速率,一个突发量的最大值以及一个时间单元(或tick当经过了一个时间单元之后,桶按照填充速率产生新的令牌但是如果填充量大于突发量的话,多余的字节将会丢失。

       所以,填充速率决定了对象发送和接受字节的最大平均速率,而突发量决定了在单次突发中所能发送和接受的最大数据量。时间单元决定了流量的流畅度。

 

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或一组bufferevents的读写的对象。调用ev_token_bucket_cfg_new函数可以创建ev_token_bucket_cfg结构,调用该函数需要提供最大平均读速率,最大读突发量,最大写速率,最大写突发量,以及tick的长度。如果tick_len参数为NULL,则tick长度默认为一秒。如果发生错误,该函数返回NULL。

       注意,read_rate和write_rate参数按照每tick的字节数进行度量。也就是说,如果tick为1/10秒,并且read_rate为300,那么最大平均读速率为每秒3000个字节。不支持超过EV_RATE_LIMIT_MAX的速率和突发量。

       为了限制一个bufferevent的传输速率,可以以一个ev_token_bucket_cfg为参数来调用函数bufferevent_set_rate_limit。该函数成功时返回0,失败时返回-1。相同ev_token_bucket_cfg结构可以设置任意数量的bufferevent。如果以NULL为cfg参数调用函数bufferevent_set_rate_limit,则可以移除bufferevent的速率限制。

       调用ev_token_bucket_cfg_free函数可以释放ev_token_bucket_cfg结构。注意,直到没有任何bufferevent使用该ev_token_bucket_cfg结构时,释放它才是安全的。

 

3:设置一组bufferevent的速率限制

       如果想限制多个bufferevent的总带宽使用,可以将多个bufferevent分配到一个速率限制组(rate limiting group)。

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_remove_from_rate_limit_group,将bufferevent加入和退出改组。这些函数成功时返回0,失败是返回-1.

       同一时间,一个bufferevent只能属于一个速率限制组。一个bufferevent可以同时有一个独立的速率限制(通过bufferevent_set_rate_limit设置)以及一个组速率限制。当他们都被设置时,则使用较小值。

       调用函数bufferevent_rate_limit_group_set_cfg,可以改变一个组的速率限制。该函数成功时返回0,失败是返回-1.bufferevent_rate_limit_group_free函数释放一个速率限制组,并且移除其所有成员。

在Libevent2.0中,组速率限制保证总体上的公平,但是在实现上可能对于较小时间跨度来说是不公平的。如果你非常在意调度公平性,请帮助实现未来版本的补丁。

      

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的值超过分配值的话(刷新bufferevent),这些值可以为负数。

 

ev_ssize_t bufferevent_get_max_to_read(struct  bufferevent  *bev);

ev_ssize_t bufferevent_get_max_to_write(struct  bufferevent  *bev);

ev_ssize_t bufferevent_get_max_to_read(struct  bufferevent  *bev);

ev_ssize_t bufferevent_get_max_to_write(struct  bufferevent  *bev);

       这些函数根据应用到该bufferevent上的任何速率限制、它的速率限制组,以及由Libevent视为一个整体的任何每次读写最大值,该函数返回bufferevent当前正要读写的字节数。

 

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_out 和 *total_written_out为一个bufferevent组的读写字节总数。这些字节总数在group建立的时候置为0,当在组上再次调用bufferevent_rate_limit_group_reset_totals时,该值重置为0。

 

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或速率限制组的读写桶大小。注意这种减少是有符号的:如果希望增加一个桶容量,则可以传递一个负数。

 

6:设置速率限制组中的最小共享(the smallest share)

       一般不希望将每个tick中所有可得流量均匀的分布到速率限制组中的所有bufferevent上。比如,如果一个速率限制组有10,000个激活的bufferevent,每个tick总共有10,000个字节用来输出,因为系统调用以及TCP报文头的原因,每个bufferevent每个tick只能输出1个字节的话是很没有效率的。

       为了解决这种问题,每一个速率限制组都有“最小共享”(minimum share)的概念。在上面的情况中,不采用每个bufferevent每次tick写1个字节这种方式,而是允许每个tick中,10000/SHARE个bufferevent写SHARE个字节,而其余的bufferevent可以不输出任何字节。每一个tick中,哪些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,则将禁用最小共享的代码。

 

7:速率限制实现中的限制

       在Libevent 2.0中,需要知道速率限制的实现具有某些限制:

       l  不是所有bufferevent类型都能很好的支持速率限制,有些根本不支持。

       l  速率限制组不允许嵌套,并且一个bufferevent同一时间只能属于一个速率限制组。

       l  速率限制的实现仅仅对传输的TCP报文体重的字节数进行计数,不包含TCP报文头。

       l  读限制的实现依赖于TCP协议栈,注意,应用程序只能以一定的速率吸收数据,并且当缓冲区变满时,将数据推送到TCP链接的另一端。

       l  某些bufferevent的实现(特别是window的IOCP实现)可以过量使用(over-commit)

       l  令牌桶可以以一个完整tick的流量为开始。这意味着一个bufferevent可以立即开始读写操作,而不需要等待一个完整的tick过去之后才开始,这还意味着,如果一个bufferevent被限制速率为N.1个tick,他也可以传输N+1个tick的流量。

       l  ticks可以小于1毫秒,而且所有毫秒的小数部分将会被忽略。

 

五:bufferevent和SSL

       bufferevent可以使用OpenSSL库来实现SSL/TLS安全传输层。因为大多数应用不需要连接OpenSSL,所以该功能在一个独立的库:“libevent_openssl”中实现。未来版本的Libevent可以支持其他的SSL/TLS库,比如NSS或GnuTLS,但是当前只支持OpenSSL。

       注意,本节并非介绍OpenSSL,SSL/TLS或一般性密码学的教程。

       下面所有的函数都是在文件“event2/bufferevent_ssl.h”中声明。

 

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通信的过滤型bufferevent,或者一种基于socket的bufferevent,使OpenSSL直接与网络通信。每种类型都必须提供一个SSL对象以及对该对象状态的描述。如果SSL当前作为客户端,则状态应该是BUFFEREVENT_SSL_CONNECTING,如果SSL当前作为服务端,则状态是BUFFEREVENT_SSL_ACCEPTING,或者如果SSL的握手已经完成了,则SSL的状态是BUFFEREVENT_SSL_OPEN。

       可以使用常用的选项:BEV_OPT_CLOSE_ON_FREE使得在openssl bufferevent关闭时,也会关闭SSL对象以及底层fd或bufferevent。

       一旦握手建立,则会以BEV_EVENT_CONNECTED为标志产生新的bufferevent事件回调。

       如果创建一个基于socket的bufferevent,而且SSL对象已经有一个socket了,可以不需要提供socket了:直接传递-1即可。可以后续通过bufferevent_setfd设置fd。

       注意,当在SSL bufferevent上设置BEV_OPT_CLOSE_ON_FREE标志时,在SSL连接上不会执行干净的关闭。这就会有两个问题:一,链接看起来像是被另一端破坏了,而不是干净的关闭了:对端无法告知到底是关闭了链接,还是由攻击或者其他原因导致的断链。第二,OpenSSL会将会话视为“损坏”的,而且将会话从缓存中移除,这样就会导致负载下的SSL程序性能严重下降。

       当前唯一的解决办法就是进行手动的懒惰SSL关闭。尽管这违反了TLS RFC,但是这可以保证一旦关闭连接会话仍然保留在缓存中。下面是这种解决办法的代码实现:

SSL*ctx = bufferevent_openssl_get_ssl(bev);

 

/*

 * SSL_RECEIVED_SHUTDOWN tells SSL_shutdown toact as if we had already

 * received a close notify from the otherend.  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 realclose notify will never be

 * received. In effect, both sides will think that they have completed a

 * clean shutdown and keep their sessionsvalid.  This strategy will fail

 * if the socket is not ready for writing, inwhich case this hack will

 * lead to an unclean shutdown and lost sessionon 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协议的好版本(比如SSLv3,以及所有的TLS版本)都支持关闭认证操作,这可以在底层缓冲区中区分出到底是偶然的关闭还是恶意的终止。默认情况下,将除了正确关闭之外的所有关闭都视为链接错误。如果allow_dirty_shutdown标志为1,则将连接中的关闭视为 BEV_EVENT_EOF。

 

一个简单的基于SSL的回显服务

/*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>

 

staticvoid

ssl_readcb(structbufferevent * 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);

}

 

staticvoid

ssl_acceptcb(structevconnlistener *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);

}

 

staticSSL_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 nopoint 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 -outcert.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(intargc, 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;

}

 

 

2:多线程和OpenSSL的注意事项

         多线程机制的Libevent没有覆盖OpenSSL的锁。自从OpenSSL使用了大量的全局变量依赖,必须将OpenSSL配置为线程安全的。尽管该话题已经超出了Libevent的范围,但是还是值得深入讨论的。

 

         如何使OpenSSL为线程安全的简单例子

/*

 * Please refer to OpenSSL documentation toverify you are doing this correctly,

 * Libevent does not guarantee this code is thecomplete 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 */

staticunsigned long

get_thread_id_cb(void)

{

    return (unsigned long)pthread_self();

}

 

staticvoid

thread_lock_cb(intmode, 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;

}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值