libevent学习三
来源:互联网 发布:好玩的桌面软件 编辑:程序博客网 时间:2024/05/21 08:58
设定Libevent
Libevent有一些贯穿于整个程序的全局设定。它们影响着整个库。你必须提前确定这些设定,否则可能会造成不一致的状态。
Libevent的日志消息
Libevent可以记录内部的错误和经过。当然编译时增加了日志支持,它也会记录调试信息。默认情况下,这些信息输出到标准错误。你可以使用自己的调试方法去重写它。
#define EVENT_LOG_DEBUG 0#define EVENT_LOG_MSG 1#define EVENT_LOG_WARN 2#define EVENT_LOG_ERR 3/* Deprecated; see note at the end of this section */#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG#define _EVENT_LOG_MSG EVENT_LOG_MSG#define _EVENT_LOG_WARN EVENT_LOG_WARN#define _EVENT_LOG_ERR EVENT_LOG_ERRtypedef void (*event_log_cb)(int severity, const char *msg);void event_set_log_callback(event_log_cb cb);event_set_log_callback就是注册日志输出回调的方法。如果传入NULL,它就会默认的输出到标准错误。重写一个event_log_cb去定制自己的日志记录。
例子:
#include <event2/event.h>#include <stdio.h>static void discard_cb(int severity, const char *msg){ /* This callback does nothing. */}static FILE *logfile = NULL;static void write_to_file_cb(int severity, const char *msg){ const char *s; if (!logfile) return; switch (severity) { case _EVENT_LOG_DEBUG: s = "debug"; break; case _EVENT_LOG_MSG: s = "msg"; break; case _EVENT_LOG_WARN: s = "warn"; break; case _EVENT_LOG_ERR: s = "error"; break; default: s = "?"; break; /* never reached */ } fprintf(logfile, "[%s] %s\n", s, msg);}/* Turn off all logging from Libevent. */void suppress_logging(void){ event_set_log_callback(discard_cb);}/* Redirect all Libevent log messages to the C stdio file 'f'. */void set_logfile(FILE *f){ logfile = f; event_set_log_callback(write_to_file_cb);}注:如果你在event_log_cb内部去调用Libevent函数,很可能是不安全的。比如,如果你在一个log回调中使用bufferevents去向一个socket写入一些警告信息,那你可能会遇到很怪异的bug。这一限制可能会在将来的版本中被解决。
默认情况下,debug日志是未被启用的,不会被发送到日志回调中去。你可以手动的去开启它。
接口
#define EVENT_DBG_NONE 0#define EVENT_DBG_ALL 0xffffffffuvoid event_enable_debug_logging(ev_uint32_t which);
debug信息冗长,而且在大多数情况下,没有什么用。EVENT_DBG_NONE默认不开启debug日志。EVENT_DBG_ALL会开启对debug日志的支持。
处理致命错误
当Libevent检测到一个致命的内部错误(如一个被破坏的结构),它的默认行为是调用exit()或abort()去终止进程。你可以提供一个自己的处理函数,去覆盖Libevent对这种致命错误的处理。
接口
typedef void (*event_fatal_cb)(int err);void event_set_fatal_callback(event_fatal_cb cb);
提供你自己的event_fatal_cb版本,传到event_set_fatal_callback()里面。当有致命错误发生时,它就会调用你提供的方法了。在你的方法里不能再把控制权交给Libevent,一旦你的方法被调用,不要在里面再调用任何Libevent方法。
默认情况下,Libevent使用C语言库提供的内存相关方法(malloc,realloc,free)去从堆上分配内存。当你有更高效的内存管理方案,或者你想去查询内存泄露的时候,Libevent允许你提供自己的malloc,realloc,free去管理内存。
接口
void event_set_mem_functions(void *(*malloc_fn)(size_t sz), void *(*realloc_fn)(void *ptr, size_t sz), void (*free_fn)(void *ptr));下面有个简单的例子,它统计被使用的内存大小。在实际环境中,你可能需要加锁去避免一些由于多线程造成的错误。
#include <event2/event.h>#include <sys/types.h>#include <stdlib.h>/* This union's purpose is to be as big as the largest of all the * types it contains. */union alignment { size_t sz; void *ptr; double dbl;};/* We need to make sure that everything we return is on the right alignment to hold anything, including a double. */#define ALIGNMENT sizeof(union alignment)/* We need to do this cast-to-char* trick on our pointers to adjust them; doing arithmetic on a void* is not standard. */#define OUTPTR(ptr) (((char*)ptr)+ALIGNMENT)#define INPTR(ptr) (((char*)ptr)-ALIGNMENT)static size_t total_allocated = 0;static void *replacement_malloc(size_t sz){ void *chunk = malloc(sz + ALIGNMENT); if (!chunk) return chunk; total_allocated += sz; *(size_t*)chunk = sz; return OUTPTR(chunk);}static void *replacement_realloc(void *ptr, size_t sz){ size_t old_size = 0; if (ptr) { ptr = INPTR(ptr); old_size = *(size_t*)ptr; } ptr = realloc(ptr, sz + ALIGNMENT); if (!ptr) return NULL; *(size_t*)ptr = sz; total_allocated = total_allocated - old_size + sz; return OUTPTR(ptr);}static void replacement_free(void *ptr){ ptr = INPTR(ptr); total_allocated -= *(size_t*)ptr; free(ptr);}void start_counting_bytes(void){ event_set_mem_functions(replacement_malloc, replacement_realloc, replacement_free);}注:
1. 替换内存管理方法会影响所有的内存分配,重分配和释放。所以必须确保在Libevent被使用前设置你提供的方法。否则你可能会用你提供的释放函数去释放从C库分配来的内存。
2. 你提供的malloc和realloc函数需要返回与C库相同对齐的内存块。
3. 你的realloc方法需要注意处理realloc(NULL, sz)(也就是malloc(sz))。
4. 你的realloc方法需要注意处理realloc(ptr,0)(也就是free(ptr))。
5. 你的free方法不需要处理free(NULL)。注:没必要的系统调用
6. 你的malloc不需要处理malloc(0)。注:没必要的系统调用
7. 如果使用了多线程,需要注意线程安全。
8. 如果你提供了malloc和realloc,那么你同时也需要提供对应版本的free函数。
锁和线程
如果你有多线程的经验,那你可能知道,同一时间多个线程访问同一个数据可能是不安全的。
在多线程环境下,Libevent的数据一般以三种方式存在。
1. 一些数据只用在单线程中:多个线程同时使用它,一定是不安全的。
2. 一些数据可以被选择性的锁定:在使用这些结构时,你可以告诉它是否用在多线程。
3. 一些数据总是被锁定:运行在多线程下,它们总是安全的。
要在Libevent中获得锁,你必须提供相关锁方法给Libevent。并且要确保在数据被多线程使用之前就必须提供好。
如果你想使用pthread库或Windows本地线程库,这里提供了一些预定义的方法去让Libevent正确安装pthread或Windows方法。
接口
#ifdef WIN32int evthread_use_windows_threads(void);#define EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED#endif#ifdef _EVENT_HAVE_PTHREADSint evthread_use_pthreads(void);#define EVTHREAD_USE_PTHREADS_IMPLEMENTED#endif这两个方法发生错误时返回-1,成功时返回0。
如果你不想使用Libevent线程库,那你需要做一些额外的工作。你需要定义一些方法:
Locks
locking
unlocking
lock allocation
lock destruction
Conditions
condition variable creation
condition variable destruction
waiting on a condition variable
signaling/broadcasting to a condition variable
Threads
thread ID detection
然后你可以通过evthread_set_lock_callbacks和evthread_set_id_callback接口去向libevent注册这些方法。
接口
#define EVTHREAD_WRITE 0x04#define EVTHREAD_READ 0x08#define EVTHREAD_TRY 0x10#define EVTHREAD_LOCKTYPE_RECURSIVE 1#define EVTHREAD_LOCKTYPE_READWRITE 2#define EVTHREAD_LOCK_API_VERSION 1struct evthread_lock_callbacks { int lock_api_version; unsigned supported_locktypes; void *(*alloc)(unsigned locktype); void (*free)(void *lock, unsigned locktype); int (*lock)(unsigned mode, void *lock); int (*unlock)(unsigned mode, void *lock);};int evthread_set_lock_callbacks(const struct evthread_lock_callbacks *);void evthread_set_id_callback(unsigned long (*id_fn)(void));struct evthread_condition_callbacks { int condition_api_version; void *(*alloc_condition)(unsigned condtype); void (*free_condition)(void *cond); int (*signal_condition)(void *cond, int broadcast); int (*wait_condition)(void *cond, void *lock, const struct timeval *timeout);};int evthread_set_condition_callbacks( const struct evthread_condition_callbacks *);evthread_lock_callbacks结构体描述了锁的相关回调。对于上边描述的版本,lock_api_versionbi必须被设置成EVTHREAD_LOCK_API_VERSION。supported_locktypes字段描述的是锁的类型,必须被设置为EVTHREAD_LOCKTYPE_*。alloc方法必须返回一个指定locktype的锁。free方法必须释放一个指定锁的所有资源。lock方法必须实现去以指定模式获得某个锁,成功返回0,失败返回非0。unlock方法必须实现尝试去解锁,成功返回0,失败返回非0。
公认的锁类型:
0:常规的,不需要可重入的锁
EVTHREAD_LOCKTYPE_RECURSIVE:如果一个线程已经持有了这个锁,再次请求不会发生死锁。一旦持有锁的这个线程解锁的次数和初始加锁的次数相同,其他线程可以获得这个锁。
EVTHREAD_LOCKTYPE_READWRITE: 这个锁允许多个线程同时去读,但是只允许一个线程去写。
公认的锁模式:
EVTHREAD_READ:用于读写锁,获得或释放锁用于读
EVTHREAD_WRITE: 用于读写锁,获得或释放锁用于写
EVTHREAD_TRY:探查锁是否可以被获取
id_fn参数是一个方法,它可以返回当前正在运行的线程的id,返回值是一个无符号长整型类型。同一个线程返回相同的数。不同的线程一定返回不同的数。
evthread_condition_callbacks结构描述条件变量的相关回调。lock_api_version字段必须是EVTHREAD_CONDITION_API_VERSION。alloc_condition方法必须返回一个指向新条件变量的指针,它的参数是0。free_condition函数用来释放被条件变量所持有资源。wait_condition函数有3个参数:1.被alloc_condition分配的条件变量, 2. 被evthread_lock_callbacks.alloc分配的锁变量,3. 可选的超时时间。函数被调用的时候,它一定持有着锁;然后释放锁,一直等待到接收了条件变量的信号或等待timeout时间溢出。wait_condition发生错误返回-1,被条件变量信号唤醒返回0,超时返回1。在它返回之前,必须确保它再次持有锁。最后,如果broadcast为false,signal_function会唤醒一个等待在此条件变量上的线程,如果broadcast为true,唤醒所有等待在此条件变量上的线程。
调试锁功能
Libevent提供了“调试锁”的特性。调试锁是用来捕获锁调用出现的一些特定错误:
1. 解锁一个当前线程未持有的锁。
2. 重锁一个非重入锁。
如果发生了以上的错误,Libevent会直接退出。
接口
void evthread_enable_lock_debugging(void);#define evthread_enable_lock_debuging() evthread_enable_lock_debugging()注:这个方法必须在锁被创建或使用前就调用。可以在设置线程功能后就调用它。
调试事件功能
对于事件的一些常规错误,Libevent是可以检测并且报告给你的。他们包括:
1. 使用一个未初始化的事件结构体
2. 二次初始化一个事件的结构体
追踪事件是否被初始化,需要额外的内存和CPU,所以仅需在调试阶段的时候才需要去开启它。
接口
void event_enable_debug_mode(void);这个方法必须在event_base创建前就被调用。
在使用调试模式时,如果你的程序使用了大量由event_assign()[而不是event_new()]创建的event,那可能会造成内存溢出。这是因为当使用event_assign()去创建一个event时,它可能是在栈上的一个对象,Libevent没办法辨别这个event什么时候不再被使用。(而当使用event_new()创建event的时候,它是一个堆内存上的数据,Libevent是可以通过调用event_free()去知道这个event是无效的)。当然如果你想在调试的时候不出现内存溢出,你可以显示的告诉Libevent,这些events不再被当做是分配的。
接口
void event_debug_unassign(struct event *ev);当调试模式被禁止时,对event_debug_unassign()的调用是没影响的。
例子:
#include <event2/event.h>#include <event2/event_struct.h>#include <stdlib.h>void cb(evutil_socket_t fd, short what, void *ptr){ /* We pass 'NULL' as the callback pointer for the heap allocated * event, and we pass the event itself as the callback pointer * for the stack-allocated event. */ struct event *ev = ptr; if (ev) event_debug_unassign(ev);}/* Here's a simple mainloop that waits until fd1 and fd2 are both * ready to read. */void mainloop(evutil_socket_t fd1, evutil_socket_t fd2, int debug_mode){ struct event_base *base; struct event event_on_stack, *event_on_heap; if (debug_mode) event_enable_debug_mode(); base = event_base_new(); event_on_heap = event_new(base, fd1, EV_READ, cb, NULL); event_assign(&event_on_stack, base, fd2, EV_READ, cb, &event_on_stack); event_add(event_on_heap, NULL); event_add(&event_on_stack, NULL); event_base_dispatch(base); event_free(event_on_heap); event_base_free(base);}调试的详细情况可以通过在编译时添加'-DUSE_DEBUG'参数来启用。如果这个标志被启用,那么使用Libevent程序的服务器端会输出一个非常冗长的日志。其中包括:
1. event 增加
2. event 删除
3. 平台相关的event通知信息
这个功能不能通过API去启用或禁用,所有只能被用在开发阶段。
释放全局Libevent数据
当你释放了所有Libevent的数据,可一些全局的数据依然被留下来。正常情况下是没问题的,因为一旦程序退出,内核会自动释放进程的所有内存。但是如果你使用一些内存泄露的检测工具,来检查内存泄露的情况时,这些全局数据会被当做未释放的内存被检测出来,影响你的调试。你可以通过调用以下接口:
接口:
void libevent_global_shutdown(void);这个方法不会释放那些通过Libevent方法返回给你的数据。你需要自己手动的去释放它们。
这个方法应该在最后调用,就是调用它后,不要再使用任何Libevent的方法。
- libevent学习三
- libevent 学习笔记 三
- libevent源码学习(三)信号evsignal
- libevent学习
- libevent学习
- libevent 学习
- Libevent学习
- libevent学习
- libevent学习
- Libevent源码学习(三) 事件内部流程解析上部
- libevent学习之三:简单的服务器和客户端
- (三)libevent源文件结构
- Libevent参考手册第一章:设置libevent (三)
- libevent源码学习研究(libevent-0.1)
- libevent学习笔记 -1 设置 Libevent库
- libevent学习总结
- libevent 学习比较
- <<libevent学习资料>>
- UIWebView与Javascript交互
- 【Android入门 二】Activity的理解
- poj1087 网络最大流
- 占位
- 如何在android下使用binder
- libevent学习三
- 求两个单链表的第一个交点(人搜)
- php 自制基于simple_html_dom的爬虫一只v1.0
- HTML----常用HEAD头标
- 周鸿祎对企业的深刻反思,太有借鉴意义了!
- C语言 二叉树相关
- 二维码读取与扫描
- android 帧布局实现跑马灯
- 如何修改单卡和双卡工程的默认数据连接开关