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