本章描述的是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;

}

Libevent:8Bufferevents高级主题的更多相关文章

  1. Python:高级主题之(属性取值和赋值过程、属性描述符、装饰器)

    Python:高级主题之(属性取值和赋值过程.属性描述符.装饰器) 背景 学习了Javascript才知道原来属性的取值和赋值操作访问的“位置”可能不同.还有词法作用域这个东西,这也是我学习任何一门语 ...

  2. Mego开发文档 - 建模高级主题

    建模高级主题 在建模过程中我们还有许多其他情况,这里列出本框架中的有用特性来用于解决此类问题. 函数映射 我们可以将指定的CLR函数映射到数据库中的系统函数或自定义函数,该特性用于补充框架中未提供的数 ...

  3. Python——类的高级主题

    介绍关于类的一些高级主题,这些是可选的,在Python应用程序中,不会常常遇到. =========================================================== ...

  4. Redis高级主题

    Redis高级主题   持久化 Redis 支持持久化, 其持久化数据有两种方式. 两种可以同时使用. 如果同时使用, Reids 在重启时将使用 AOF 方式来还原数据. RDB 按照一定策略定时同 ...

  5. makefile实验四 编译本地的源文件 + 变量的高级主题一

    <一>编译本地的源文件 + 变量的模式替换    实验代码 root@ubuntu:~/Makefile_Test/5make_test# vim makefile target := t ...

  6. PowerShell_零基础自学课程_8_高级主题:WMI对象和COM组件

    本系列文章从最初的初识开始,基本上可以完成一些简单的系统管理了,为了更方便的管理系统,同时为了更好的发掘系统的性能,就需要用到系统提供 的一些高级特性,在Windows Server系列的OS中,如果 ...

  7. JavaEE Tutorials (21) - Java EE安全:高级主题

    21.1使用数字证书331 21.1.1创建服务器证书332 21.1.2向证书安全域增加用户334 21.1.3为GlassFish服务器使用一个不同的服务器证书33421.2认证机制335 21. ...

  8. Unity手游之路<九>自动寻路Navmesh之高级主题

    http://blog.csdn.net/janeky/article/details/17492531 之前我们一起学习了如何使用Navmesh组件来实现最基本的角色自动寻路.今天我们再继续深入探索 ...

  9. sql高级主题资料(网络复制)

    SQL Server 常用高级语法笔记   自从用了EF后很少写sql和存储过程了,今天需要写个比较复杂的报告,翻出了之前的笔记做参考,感觉这个笔记还是很有用的,因此发出来和大家分享. 1.case. ...

随机推荐

  1. hbase表内存的分布

  2. Vuejs实战项目五:数据列表

    1.在EasyMock 中添加数据列表模拟接口 请求url:/suyuan/list 请求方式:get 描述:数据列表 mock.js配置: 例: { "code": 2000, ...

  3. GYM100633J. Ceizenpok’s formula 扩展lucas模板

    J. Ceizenpok’s formula time limit per test 2.0 s memory limit per test 256 MB input standard input o ...

  4. sulin LuceneNet 搜索二

    1.添加引用dll using Lucene.Net.Search;using Lucene.Net.Analysis.PanGu;using PanGu;using PanGu.HighLight; ...

  5. 【html、CSS、javascript-4】新特征之增强表单

    一.input元素及属性 input元素的属性 type属性:指定输入内容的类型,默认为text:单行文本框 name属性:输入内容的识别名称,传递参数时候的参数名称 value属性:默认值 maxl ...

  6. 常用web字体的使用指南

    而真正的挑战在于中文字体,由于中文字体组成的特殊性导致其体积过于庞大,除了操作系统内置的字体之外,我们很难在网站上应用其他的字体.在可选性很差的前提之下,如何正确的使用中文字体呢? 首先,以下的字体声 ...

  7. 用javascript实现简单的用户登录验证

    用javascript实现简单的用户登录验证 <!DOCTYPE html> <html lang="en"> <head> <meta ...

  8. 20190921-雅礼Day1

    #error 此人太蒻无法编译 #include<iostream> main(){} Before 哦…… -O2 T1 序列问题:分块(莫队),树状数组,线段树,分治 离线 or 在线 ...

  9. C# Action 和Func

    https://www.cnblogs.com/LipeiNet/p/4694225.html

  10. Javaweb Form表单查询

    1.表单(form),是一种可以由用户输入,并提交给服务器端的一个图形界面,有如下性质: (1)表单中可以输入一些内容,这些输入功能由控件提供,叫做表单元素 (2)表单中一般都有一个按钮负责提交 (3 ...