Libevent:8Bufferevents高级主题

摘要:
本章描述的是Libevent的bufferevent实现的一些高级特性,这对于普通应用来说并非必须的。“bufferevent对”支持flush;无论是设置为BEV_NORMAL还是BEV_FLUSH,都会使所有相关数据从bufferevent对的一端传送到另一端,而忽略水位线的限制。设置BEV_FINISHED还会使对端bufferevent额外的产生EOF事件。如果bev是“bufferevent对”的一个成员,而且对端bufferevent依然存在,则该函数会返回对端bufferevent,否则会返回NULL。为一个底层bufferevent添加过滤,会替换底层bufferevent的回调函数。

本章描述的是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状态,那它就再也不能进行读写数据,也不会产生任何事件了。

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

};

typedefenum bufferevent_filter_result (*bufferevent_filter_cb)(

struct evbuffer * source, struct evbuffer*destination, ev_ssize_t dst_limit,

enum bufferevent_flush_mode mode, void*ctx);

structbufferevent *bufferevent_filter_new(struct bufferevent *underlying,

bufferevent_filter_cbinput_filter,

bufferevent_filter_cboutput_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_tsize);

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

ev_ssize_tbufferevent_get_max_single_read(struct bufferevent *bev);

ev_ssize_tbufferevent_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的速率限制

#defineEV_RATE_LIMIT_MAX EV_SSIZE_MAX

structev_token_bucket_cfg;

structev_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)。

structbufferevent_rate_limit_group;

structbufferevent_rate_limit_group *bufferevent_rate_limit_group_new(

struct event_base *base,

conststruct 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(structbufferevent *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_tbufferevent_get_read_limit(struct bufferevent *bev);

ev_ssize_tbufferevent_get_write_limit(struct bufferevent *bev);

ev_ssize_tbufferevent_rate_limit_group_get_read_limit(

struct bufferevent_rate_limit_group *);

ev_ssize_tbufferevent_rate_limit_group_get_write_limit(

struct bufferevent_rate_limit_group *);

上述函数返回一个bufferevent或一个组的读/写令牌桶的当前字节数。注意,如果某个bufferevent的值超过分配值的话(刷新bufferevent),这些值可以为负数。

ev_ssize_tbufferevent_get_max_to_read(struct bufferevent *bev);

ev_ssize_tbufferevent_get_max_to_write(struct bufferevent *bev);

ev_ssize_tbufferevent_get_max_to_read(struct bufferevent *bev);

ev_ssize_tbufferevent_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_tdecr);

int bufferevent_rate_limit_group_decrement_write(

struct bufferevent_rate_limit_group *grp, ev_ssize_tdecr);

这些函数减少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

};

structbufferevent *

bufferevent_openssl_filter_new(structevent_base *base,

struct bufferevent *underlying,

SSL *ssl,

enum bufferevent_ssl_state state,

int options);

structbufferevent *

bufferevent_openssl_socket_new(structevent_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。

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

void bufferevent_openssl_set_allow_dirty_shutdown(structbufferevent *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;

}

免责声明:文章转载自《Libevent:8Bufferevents高级主题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇为什么每个前端开发者都要理解页面的渲染?Scala(七)集合下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MySQL4:存储过程和函数

什么是存储过程 简单说,存储过程就是一条或多条SQL语句的集合,可视为批文件,但是起作用不仅限于批处理。本文主要讲解如何创建存储过程和存储函数以及变量的使用,如何调用、查看、修改、删除存储过程和存储函数等。使用的数据库和表还是之前写JDBC用的数据库和表: create databaseschool; useschool; create tablest...

VS环境下用thrift-C/C++接口开发hbase应用

一、前言 用C/C++开发hbase应用,需要用到thrift接口。在windows平台使用Visual Studio时,要比在linux平台复杂一些,主要是因为一些依赖库无法做到自动安装(类似yum install或 apt-get install那样),其次是因为Linux本来就是hbase及其依赖库的原生平台。但总体而言,两个平台的过程大体相似。 开...

超全!iOS 面试题汇总

作者:Job_Yang 之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家。(题目来源于网络,侵删) 1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承;可以实现多个接口,通过实现多...

QT基础三

4.1The Central Widget QMainWindow的中央区域可以被任何类型的widget占据。 4.2Subclassing QTableWidget QTableWidget会自动创建QTableWidgetItem来存储用户的输入。 QTableWidgetItem类并不是widget,而是一个纯粹的data class。 QTabeW...

[置顶] 游戏开发技术总结(经典之作)第八集 脚踏实地游戏角色自动寻路、路径搜索算法

8-1 任务让游戏的角色能够自动寻路; 并让所有的动物都动起来。另外我们在这一章将介绍图形光标技术。 8-2 设置障碍 8-2-1 场景中的障碍点 游戏中加入了山石、树木场景后,就应该有对游戏角色行走路线的限制。这种限制是我们通过在游戏场景中设置障碍点来实现的。不管是人还是动物,遇到障碍点,都必须绕道而行。 图8-1在我们不希望角色经过或占据的地方做上...

PHP支付宝接口RSA验证

这两天一直困扰的PHP RSA签名验证问题终于解决了,由于之前RSA接触的不多,再加上官方至今还未有PHP的SDK可供参考,因此走了一些弯路,写在这里和大家分享。     虽然支付宝官方还未提供相关SDK,PHP确实可以实现RSA方式的签名,这点其实很重要,由于不熟悉,在遇到困难的时候,经常会不由自主地想到是否PHP不支持RSA签名,干脆用MD5得了,这样...