• 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 ",evbuffer_get_length(in));

        printf("----- data ---- ");

        printf("%.*s ",(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 "

               "and self-signed certificate,run: "

               "  openssl genrsa -out pkey 2048 "

               "  openssl req -new -key pkey -outcert.req "

               "  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;

    }

  • 相关阅读:
    多个漂亮的按钮样式和图片集合
    纯CSS3实现3D跳动小球
    visual studio 查找/替换对话框
    CSS实现弹出导航菜单
    javascript使浏览器关闭前弹出确认
    使用CSS3制作立体效果的导航菜单
    多个精美的导航样式web2.0源码
    jQuery实现侧边导航栏效果
    jQ函数after、append、appendTo的区别
    ASP.NET使用jQuery AJAX实现MD5加密实例
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247256.html
Copyright © 2020-2023  润新知