一起读读libevent的源代码:Libevent 第一章 设置libevent
Posted WenYao.Huang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起读读libevent的源代码:Libevent 第一章 设置libevent相关的知识,希望对你有一定的参考价值。
某人曾提醒我要多读源代码,我就选了libevent 2.1.8稳定版的源代码来读。
读了一会,纯看源代码里面的东西,还挺无聊的。所以我就开始,便看他们的编程教程:
http://www.wangafu.net/~nickm/libevent-book/
然后每遇到实现,我就跑去源代码中看别人怎么做到的。
这样还是比较有趣的,一个一个小目标的去做,直到这个事情是为什么而做。
我之前,已经把编程的指导粗略看过一边,也是边犯困边看,再开始看源代码,昨天睡了十次八次,才看了很小的一部分。
这样太慢了,而且很无聊。所以换了这样的方式,省去细枝末节,按功能来进行逐个翻看。
下面是我个人做这个事的笔记
ps:这里面前面代码里面有数字,是代码的数字,可以参考我之前的设置LXR来查看这些源代码,会比较方便。
如何设置log messag:
可以通过以下的接口:
Interface #define EVENT_LOG_DEBUG 0 #define EVENT_LOG_MSG 1 #define EVENT_LOG_WARN 2 #define EVENT_LOG_ERR 3 typedef void (*event_log_cb)(int severity, const char *msg); void event_set_log_callback(event_log_cb cb);
上面的宏定义,是一个severity的等级,下面的是写日记的回调函数。下面是将这个回调函数设置到eventbase里面的。让我们来看看这些函数都是怎么写的。
0719 /** 0720 A callback function used to intercept Libevent‘s log messages. 0721 0722 @see event_set_log_callback 0723 */ 0724 typedef void (*event_log_cb)(int severity, const char *msg);
这是它原本的定义,只是一个原型的定义。它在/include/event2/event.h和/log.c中,都会被引用到:
在头文件event.h的引用,也是一个函数原型:
0725 /** 0726 Redirect Libevent‘s log messages. 0727 0728 @param cb a function taking two arguments: an integer severity between 0729 EVENT_LOG_DEBUG and EVENT_LOG_ERR, and a string. If cb is NULL, 0730 then the default log is used. 0731 0732 NOTE: The function you provide *must not* call any other libevent 0733 functionality. Doing so can produce undefined behavior. 0734 */ 0735 EVENT2_EXPORT_SYMBOL 0736 void event_set_log_callback(event_log_cb cb);
在log.c当中,有两处的引用:
0219 static event_log_cb log_fn = NULL; 0221 void 0222 event_set_log_callback(event_log_cb cb) 0223 { 0224 log_fn = cb; 0225 }
这里的代码也很明显,就是设置了一个函数,我继续深挖,它这个函数long_fn,是如何决定它作为记录的函数的?
挖下还是会有的,就在同一个文件当中,有一个 event_log 的函数,用于决定运用哪个函数:我们设置的log_fn 还是 fprintf
0227 static void 0228 event_log(int severity, const char *msg) 0229 { 0230 if (log_fn) 0231 log_fn(severity, msg); 0232 else { 0233 const char *severity_str; 0234 switch (severity) { 0235 case EVENT_LOG_DEBUG: 0236 severity_str = "debug"; 0237 break; 0238 case EVENT_LOG_MSG: 0239 severity_str = "msg"; 0240 break; 0241 case EVENT_LOG_WARN: 0242 severity_str = "warn"; 0243 break; 0244 case EVENT_LOG_ERR: 0245 severity_str = "err"; 0246 break; 0247 default: 0248 severity_str = "???"; 0249 break; 0250 } 0251 (void)fprintf(stderr, "[%s] %s\n", severity_str, msg); 0252 } 0253 }
要注意的是,在一个用户提供的 eent_log_cb 的回调函数里面,调用libevent的函数,是不安全的。比如说,你要在回调函数里面将错误信息写socket,就不要使用libevent的buffervent这些功能,会产生怪异的,并且难以调试的错误。
This restriction may be removed for some functions in a future version of Libevent.
默认情况下,debug logs 是不会启用的。需要通过以下接口,才能够打开他们:
Interface #define EVENT_DBG_NONE 0 #define EVENT_DBG_ALL 0xffffffffu void event_enable_debug_logging(ev_uint32_t which);
这个函数的用法,就是使用 event_enable_debug_logging 这个函数,设置调试的等级为上面的两个等级之间的其中一个。可以是没有调试信息 EVENT_DBG_NONE 或者是所有的它i傲视信息 EVENT_DBG_ALL
event_enable_debug_logging 函数的定义是在/log.c里面:
0085 event_enable_debug_logging(ev_uint32_t which) 0086 { 0087 #ifdef EVENT_DEBUG_LOGGING_ENABLED 0088 event_debug_logging_mask_ = which; 0089 #endif 0090 } 0044 #if !defined(EVENT__DISABLE_DEBUG_MODE) || defined(USE_DEBUG) 0045 #define EVENT_DEBUG_LOGGING_ENABLED 0046 #endif 0073 ev_uint32_t event_debug_logging_mask_ = DEFAULT_MASK; 0065 #ifdef EVENT_DEBUG_LOGGING_ENABLED 0066 #ifdef USE_DEBUG 0067 #define DEFAULT_MASK EVENT_DBG_ALL 0068 #else 0069 #define DEFAULT_MASK 0 0070 #endif
在这里,event_enable_debug_logging 的函数的行为,是根据 EVENT_DEBUG_LOGGING_ENABLED 来进行抉择的。
event_debug_logging_mask_ 也是根据 EVENT_DEBUG_LOGGING_ENABLED 来进行决定DEFAULT_MASK 是0 还是所有。
EVENT_DEBUG_LOGGING_ENABLED 的定义, 是根据 EVENT__DISABLE_DEBUG_MODE 宏和 USE_DEBUG宏来决定的。
这两个宏,在配置文件中是没有的,那么,只要我们在编译的时候,不设置EVENT__DISABLE_DEBUG_MODE 或者是自己设置了USER_DEBUG,就可以有这些功能,也就是,默认是会有这些功能的。
如何处理致命的错误:
可以使用自己的错误处理函数来处理致命的错误:
Interface typedef void (*event_fatal_cb)(int err); void event_set_fatal_callback(event_fatal_cb cb);
首先,你需要定义一个新的函数,这个函数是Libevent出现致命错误的时候,进行调用的,函数的原型就是 event_fatal_cb。然后再使用event_set_fatal_callback来进行设置。
首先来看一下:event_fatal_cb 的定义和引用的情况:
// /include/event2/event.h 0738 /** 0739 A function to be called if Libevent encounters a fatal internal error. 0740 0741 @see event_set_fatal_callback 0742 */ 0743 typedef void (*event_fatal_cb)(int err); 0745 /** 0746 Override Libevent‘s behavior in the event of a fatal internal error. 0747 0748 By default, Libevent will call exit(1) if a programming error makes it 0749 impossible to continue correct operation. This function allows you to supply 0750 another callback instead. Note that if the function is ever invoked, 0751 something is wrong with your program, or with Libevent: any subsequent calls 0752 to Libevent may result in undefined behavior. 0753 0754 Libevent will (almost) always log an EVENT_LOG_ERR message before calling 0755 this function; look at the last log message to see why Libevent has died. 0756 */ 0757 EVENT2_EXPORT_SYMBOL 0758 void event_set_fatal_callback(event_fatal_cb cb); // /log.c 0063 static event_fatal_cb fatal_fn = NULL; 0092 void 0093 event_set_fatal_callback(event_fatal_cb cb) 0094 { 0095 fatal_fn = cb; 0096 }
但是, 在这里, 默认行为就是不处理。
0063 static event_fatal_cb fatal_fn = NULL;
Memory management
默认情况下,Libevent使用C语言库的内存分配函数来进行分配。可以用下面的接口来进行自定义:
// //mm-internal.h 0035 #ifndef EVENT__DISABLE_MM_REPLACEMENT 0036 /* Internal use only: Memory allocation functions. We give them nice short 0037 * mm_names for our own use, but make sure that the symbols have longer names 0038 * so they don‘t conflict with other libraries (like, say, libmm). */ 0039 0040 /** Allocate uninitialized memory. 0041 * 0042 * @return On success, return a pointer to sz newly allocated bytes. 0043 * On failure, set errno to ENOMEM and return NULL. 0044 * If the argument sz is 0, simply return NULL. 0045 */ 0046 void *event_mm_malloc_(size_t sz); 0047 0048 /** Allocate memory initialized to zero. 0049 * 0050 * @return On success, return a pointer to (count * size) newly allocated 0051 * bytes, initialized to zero. 0052 * On failure, or if the product would result in an integer overflow, 0053 * set errno to ENOMEM and return NULL. 0054 * If either arguments are 0, simply return NULL. 0055 */ 0056 void *event_mm_calloc_(size_t count, size_t size); 0057 0058 /** Duplicate a string. 0059 * 0060 * @return On success, return a pointer to a newly allocated duplicate 0061 * of a string. 0062 * Set errno to ENOMEM and return NULL if a memory allocation error 0063 * occurs (or would occur) in the process. 0064 * If the argument str is NULL, set errno to EINVAL and return NULL. 0065 */ 0066 char *event_mm_strdup_(const char *str); 0067 0068 void *event_mm_realloc_(void *p, size_t sz); 0069 void event_mm_free_(void *p); 0070 #define mm_malloc(sz) event_mm_malloc_(sz) 0071 #define mm_calloc(count, size) event_mm_calloc_((count), (size)) 0072 #define mm_strdup(s) event_mm_strdup_(s) 0073 #define mm_realloc(p, sz) event_mm_realloc_((p), (sz)) 0074 #define mm_free(p) event_mm_free_(p) 0075 #else 0076 #define mm_malloc(sz) malloc(sz) 0077 #define mm_calloc(n, sz) calloc((n), (sz)) 0078 #define mm_strdup(s) strdup(s) 0079 #define mm_realloc(p, sz) realloc((p), (sz)) 0080 #define mm_free(p) free(p) 0081 #endif
取决于 EVENT__DISABLE_MM_REPLACEMENT 的定义,这个定义可以在 /WIN32-Code/nmake/event2/event-config.h下面
/* Define if libevent should not allow replacing the mm functions */ /* #undef EVENT__DISABLE_MM_REPLACEMENT */
定义了这个,就会在建造的时候,无法代替这些内存分配函数。
如果没有定义,那么就会定义下面这几个函数的接口:
void *event_mm_malloc_(size_t sz); void *event_mm_calloc_(size_t count, size_t size); char *event_mm_strdup_(const char *str); void *event_mm_realloc_(void *p, size_t sz); void event_mm_free_(void *p);
这些函数的定义,也都在/event.c下面
malloc
3432 void * 3433 event_mm_malloc_(size_t sz) 3434 { 3435 if (sz == 0) 3436 return NULL; 3437 3438 if (mm_malloc_fn_) 3439 return mm_malloc_fn_(sz); 3440 else 3441 return malloc(sz); 3442 }
calloc函数
3444 void * 3445 event_mm_calloc_(size_t count, size_t size) 3446 { 3447 if (count == 0 || size == 0) 3448 return NULL; 3449 3450 if (mm_malloc_fn_) { 3451 size_t sz = count * size; 3452 void *p = NULL; 3453 if (count > EV_SIZE_MAX / size) 3454 goto error; 3455 p = mm_malloc_fn_(sz); 3456 if (p) 3457 return memset(p, 0, sz); 3458 } else { 3459 void *p = calloc(count, size); 3460 #ifdef _WIN32 3461 /* Windows calloc doesn‘t reliably set ENOMEM */ 3462 if (p == NULL) 3463 goto error; 3464 #endif 3465 return p; 3466 } 3467 3468 error: 3469 errno = ENOMEM; 3470 return NULL; 3471 }
strdup的函数,作用是复制一个字符串
3473 char * 3474 event_mm_strdup_(const char *str) 3475 { 3476 if (!str) { 3477 errno = EINVAL; 3478 return NULL; 3479 } 3480 3481 if (mm_malloc_fn_) { 3482 size_t ln = strlen(str); 3483 void *p = NULL; 3484 if (ln == EV_SIZE_MAX) 3485 goto error; 3486 p = mm_malloc_fn_(ln+1); 3487 if (p) 3488 return memcpy(p, str, ln+1); 3489 } else 3490 #ifdef _WIN32 3491 return _strdup(str); 3492 #else 3493 return strdup(str); 3494 #endif 3495 3496 error: 3497 errno = ENOMEM; 3498 return NULL; 3499 }
这两个函数realloc和free函数
3501 void * 3502 event_mm_realloc_(void *ptr, size_t sz) 3503 { 3504 if (mm_realloc_fn_) 3505 return mm_realloc_fn_(ptr, sz); 3506 else 3507 return realloc(ptr, sz); 3508 } 3509 3510 void 3511 event_mm_free_(void *ptr) 3512 { 3513 if (mm_free_fn_) 3514 mm_free_fn_(ptr); 3515 else 3516 free(ptr); 3517 }
不定义的话, 就可以替换 ,使用下面的接口来进行替换
Interface void event_set_mem_functions(void *(*malloc_fn)(size_t sz), void *(*realloc_fn)(void *ptr, size_t sz), void (*free_fn)(void *ptr));
在上面的实现的基础上,如何实现这个函数,就会比较简单了。只要设置代面里面的 mm_*_fn 就好了
3519 void 3520 event_set_mem_functions(void *(*malloc_fn)(size_t sz), 3521 void *(*realloc_fn)(void *ptr, size_t sz), 3522 void (*free_fn)(void *ptr)) 3523 { 3524 mm_malloc_fn_ = malloc_fn; 3525 mm_realloc_fn_ = realloc_fn; 3526 mm_free_fn_ = free_fn; 3527 }
要注意的是,内存分享函数,是会影响所有需要allocate,resize,或free的函数功能的。因此,必须要在任何这些Libevent函数之前。来进行设置。否则,就会一半是用默认的,一半用自己的。
自定义的分配函数,必须是要返回内存的alignment得和C的标准库里面的一样。
你的分配函数必须要正确处理realloc(NULL, sz) 正确,处理方式就是malloc(sz)。
必须要处理realloc(ptr, 0) 的方式为 free(ptr)
free函数不需要处理free(NULL)
不需要处理malloc(0)
内存分配函数必须是threadsafe的
如果替换了malloc,也请使用替换的free来进行释放,因为Libevent会使用这些函数来进行分配和回收。
Locks and threading
多线程的程序,多个线程不可能总是安全的去访问同一个数据。
Libevent 结构一般可以用三种方式来在多个线程中进行工作:
1、一些结构体固有的单个进程的:也就是说,它们都是不能够被多个进程来进行同时访问的
2、一些结构体是可选的locked的:你可以告诉Libevent,哪些object是需要多个进程访问。
3、一些结构体总是被锁上的,因为Libevent总是访问使用锁来进行访问它的。
为了能够在Libevent进行上所,你必须告诉Libevent使用哪个Lock函数。这必须在使用任何函数之前先设置好这个。
如果你使用的是pthreads库,那么你就是幸运的。因为在那个库里面,有与定义的函数,可以用来设置Libevent来使用right pthreads。
接口如下:
Interface #ifdef _EVENT_HAVE_PTHREADS int evthread_use_pthreads(void); #define EVTHREAD_USE_PTHREADS_IMPLEMENTED #endif
这些函数的定义和说明如下:
// /include/event2/thread.h 0209 /** Sets up Libevent for use with Pthreads locking and thread ID functions. 0210 Unavailable if Libevent is not build for use with pthreads. Requires 0211 libraries to link against Libevent_pthreads as well as Libevent. 0212 0213 @return 0 on success, -1 on failure. */ 0214 EVENT2_EXPORT_SYMBOL 0215 int evthread_use_pthreads(void);
// /evthread_pthread.c 0163 int 0164 evthread_use_pthreads(void) 0165 { 0166 struct evthread_lock_callbacks cbs = { 0167 EVTHREAD_LOCK_API_VERSION, 0168 EVTHREAD_LOCKTYPE_RECURSIVE, 0169 evthread_posix_lock_alloc, 0170 evthread_posix_lock_free, 0171 evthread_posix_lock, 0172 evthread_posix_unlock 0173 }; 0174 struct evthread_condition_callbacks cond_cbs = { 0175 EVTHREAD_CONDITION_API_VERSION, 0176 evthread_posix_cond_alloc, 0177 evthread_posix_cond_free, 0178 evthread_posix_cond_signal, 0179 evthread_posix_cond_wait 0180 }; 0181 /* Set ourselves up to get recursive locks. */ 0182 if (pthread_mutexattr_init(&attr_recursive)) 0183 return -1; 0184 if (pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE)) 0185 return -1; 0186 0187 evthread_set_lock_callbacks(&cbs); 0188 evthread_set_condition_callbacks(&cond_cbs); 0189 evthread_set_id_callback(evthread_posix_get_id); 0190 return 0; 0191 }
在这个 evthread_use_pthreads 函数当中它做了如下的事情:
struct evthread_lock_callbacks 这个结构体,是用来描述一个多线程库的接口的,这会被用于进行上锁:
0091 /** This structure describes the interface a threading library uses for 0092 * locking. It‘s used to tell evthread_set_lock_callbacks() how to use 0093 * locking on this platform. 0094 */ 0095 struct evthread_lock_callbacks { 0096 /** The current version of the locking API. Set this to 0097 * EVTHREAD_LOCK_API_VERSION */ 0098 int lock_api_version; 0099 /** Which kinds of locks does this version of the locking API 0100 * support? A bitfield of EVTHREAD_LOCKTYPE_RECURSIVE and 0101 * EVTHREAD_LOCKTYPE_READWRITE. 0102 * 0103 * (Note that RECURSIVE locks are currently mandatory, and 0104 * READWRITE locks are not currently used.) 0105 **/ 0106 unsigned supported_locktypes; 0107 /** Function to allocate and initialize new lock of type ‘locktype‘. 0108 * Returns NULL on failure. */ 0109 void *(*alloc)(unsigned locktype); 0110 /** Funtion to release all storage held in ‘lock‘, which was created 0111 * with type ‘locktype‘. */ 0112 void (*free)(void *lock, unsigned locktype); 0113 /** Acquire an already-allocated lock at ‘lock‘ with mode ‘mode‘. 0114 * Returns 0 on success, and nonzero on failure. */ 0115 int (*lock)(unsigned mode, void *lock); 0116 /** Release a lock at ‘lock‘ using mode ‘mode‘. Returns 0 on success, 0117 * and nonzero on failure. */ 0118 int (*unlock)(unsigned mode, void *lock); 0119 };
另外一个结构体是struct evthread_condition_callbacks, 这个结构体是用来描写一个线程库的接口用来作条件变量,这是用来告诉 evthread_set_condition_callbacks 要怎么使用locking 在这个平台上:
以上是关于一起读读libevent的源代码:Libevent 第一章 设置libevent的主要内容,如果未能解决你的问题,请参考以下文章
一起读读libevent的源代码:Libevent 第二章 创建Event base
我可以将 C++11 lambda 与 libevent 一起使用吗?