1、libevent的使用方法
初始化 struct event_base main_base = event_init(); //多孔插座
事件设置 event_set(&ev, listen_fd, EV_READ | EV_PERSIST, cb, *arg) //设置灯泡
event_base_set(main_base, &ev);//设置灯泡属于哪个插座
事件添加 event_add(&ev, NULL) //插上电板
进入事件循环 event_base_loop(main_base, 0); //进入事件循环
struct event {
TAILQ_ENTRY (event) ev_next;
TAILQ_ENTRY (event) ev_active_next;
TAILQ_ENTRY (event) ev_signal_next;
unsigned int min_heap_idx; /* for managing timeouts */
struct event_base *ev_base;
int ev_fd;
short ev_events;
short ev_ncalls;
short *ev_pncalls; /* Allows deletes in callback */
struct timeval ev_timeout;
int ev_pri; /* smaller numbers are higher priority */
void (*ev_callback)(int, short, void *arg);
void *ev_arg;
int ev_res; /* result passed to event callback */
int ev_flags;
};
ev_events:event关注的事件类型,它可以是以下3种类型:I/O事件: EV_WRITE和EV_READ;定时事件:EV_TIMEOUT;信号: EV_SIGNAL;辅助选项:EV_PERSIST,表明是一个永久事件。
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10 /* Persistant event */
2、主程序详细流程
(1)初始化阶段
settings_init()初始化settings结构体。
static void settings_init(void) {
settings.use_cas = true;
settings.access = 0700;
settings.port = 11211;
settings.udpport = 11211;
/* By default this string should be NULL for getaddrinfo() */
settings.inter = NULL;
settings.maxbytes = 64 * 1024 * 1024; /* default is 64MB */
settings.maxconns = 1024; /* to limit connections-related memory to about 5MB */
settings.verbose = 0;
settings.oldest_live = 0;
settings.evict_to_free = 1; /* push old items out of cache when memory runs out */
settings.socketpath = NULL; /* by default, not using a unix socket */
settings.factor = 1.25;
settings.chunk_size = 48; /* space for a modest key and value */
settings.num_threads = 4; /* N workers */
settings.num_threads_per_udp = 0;
settings.prefix_delimiter = ':';
settings.detail_enabled = 0;
settings.reqs_per_event = 20;
settings.backlog = 1024;
settings.binding_protocol = negotiating_prot;
settings.item_size_max = 1024 * 1024; /* The famous 1MB upper limit. */
settings.maxconns_fast = false;
settings.hashpower_init = 0;
settings.slab_reassign = false;
settings.slab_automove = 0;
}
setbuf(stderr, NULL);设置stderr为无缓冲。
int getopt(int argc, char* const argv[ ], const char* optstring);获取参数用于设置settings结构体。
查看和设置资源限制(getrlimit(), setrlimit()),放弃root权利等。
event_init();
stats_init();初始化struct stats(global stats)。
assoc_init(settings.hashpower_init);->初始化primary_hashtable(Main hash table, 类型为static item**)。
conn_init();->初始化freeconns(Free list management for connections, 类型为static conn)为全0,初始化freecurr为0。
slabs_init(settings.maxbytes, settings.factor, preallocate);初始化slab class,详见数据结构一节。
(2)线程创建阶段
thread_init(settings.num_threads, main_base);
初始化锁和条件变量,尤其是item_locks[ ],初始化static LIBEVENT_THREAD *threads和static LIBEVENT_DISPATCHER_THREAD dispatcher_thread(即为主线程),之后调用setup_thread(&threads[i])和create_worker()。其中setup_thread()设置事件处理函数thread_libevent_process,但尚未进入事件循环,也还没有创建线程;之后初始化new_conn_queue成员(queue of new connections to handle);之后调用cache_create()初始化suffix_cache成员,创建了一个cache,name为“suffix”。create_worker(worker_libevent, &threads[i])创建4个线程,线程函数为worker_libevent()。worker_libevent()只是简单的进入事件循环。
注意:利用条件变量使得,thread_init()会阻塞,直到所有线程都已创建完毕。
关于其中的pthread_key_create()和pthread_setspecific(),可见http://blog.csdn.net/yangzhiloveyou/article/details/8043573.
/*
* Initializes the thread subsystem, creating various worker threads.
*
* nthreads Number of worker event handler threads to spawn
* main_base Event base for main thread
*/
void thread_init(int nthreads, struct event_base *main_base) {
int i;
int power;
pthread_mutex_init(&cache_lock, NULL);
pthread_mutex_init(&stats_lock, NULL);
pthread_mutex_init(&init_lock, NULL);
pthread_cond_init(&init_cond, NULL);
pthread_mutex_init(&cqi_freelist_lock, NULL);
cqi_freelist = NULL;
/* Want a wide lock table, but don't waste memory */
if (nthreads < 3) {
power = 10;
} else if (nthreads < 4) {
power = 11;
} else if (nthreads < 5) {
power = 12;
} else {
/* 8192 buckets, and central locks don't scale much past 5 threads */
power = 13;
}
item_lock_count = hashsize(power);
item_locks = calloc(item_lock_count, sizeof(pthread_mutex_t));
if (! item_locks) {
perror("Can't allocate item locks");
exit(1);
}
for (i = 0; i < item_lock_count; i++) {
pthread_mutex_init(&item_locks[i], NULL);
}
pthread_key_create(&item_lock_type_key, NULL); //用于在线程间传递当前锁类型,全局锁还是细粒度锁
pthread_mutex_init(&item_global_lock, NULL);
threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
if (! threads) {
perror("Can't allocate thread descriptors");
exit(1);
}
dispatcher_thread.base = main_base;
dispatcher_thread.thread_id = pthread_self();
for (i = 0; i < nthreads; i++) {
int fds[2];
if (pipe(fds)) {
perror("Can't create notify pipe");
exit(1);
}
threads[i].notify_receive_fd = fds[0];
threads[i].notify_send_fd = fds[1];
setup_thread(&threads[i]);
/* Reserve three fds for the libevent base, and two for the pipe */
stats.reserved_fds += 5;
}
/* Create threads after we've done all the libevent setup. */
for (i = 0; i < nthreads; i++) {
create_worker(worker_libevent, &threads[i]);
}
/* Wait for all the threads to set themselves up before returning. */
pthread_mutex_lock(&init_lock);
wait_for_thread_registration(nthreads);
pthread_mutex_unlock(&init_lock);
}
start_assoc_maintenance_thread() 创建线程maintenance_tid,线程函数为assoc_maintenance_thread()。
assoc_maintenance_thread()在expanding(如何expanding暂不讨论)之后,调用switch_item_lock_type(ITEM_LOCK_GRANULAR),使得所有线程开始使用细粒度锁(通过管道通知各线程,并阻塞等待各线程设置完毕);之后调用slabs_rebalancer_resume()解锁slabs_rebalance_lock锁(什么时候上的锁?什么作用?);最后调用pthread_cond_wait(&maintenance_cond, &cache_lock);阻塞,等待其他线程使用pthread_cond_signal()唤醒(注意pthread_cond_signal()不能在pthread_cond_wait()之前执行,否则唤醒信号丢失)。
如果settings.slab_reassign为true,调用start_slab_maintenance_thread()创建相关线程。此处为false,所以不调用,暂不讨论。
clock_handler(0, 0, 0);调用evtimer_add()添加计数器clockevent,定时一秒,处理函数为clock_handler(),即自身。可以看出clock_handler()的作用就是每一秒钟更新一次current_time。
(3)socket创建阶段
如果settings.socketpath != NULL,就调用server_socket_unix()创建Unix套接字;此处为NULL,不执行,暂不讨论。
server_sockets(settings.port, tcp_transport, portnumber_file)调用server_socket()。server_socket()中调用new_socket()创建socket,并设置为非阻塞模式;设置SO_REUSEADDR;对于TCP,设置SO_KEEPALIVE,SO_LINGER,TCP_NODELAY;对于TCP,调用conn_new()(对于UDP,调用dispatch_conn_new())。
conn_new()先调用conn_from_freelist()从freelist获得一个connection(此时freelist为空,所以返回NULL);初始化一个struct conn;设置listen套接字sfd的事件处理函数为event_handler();如果事件添加失败,则将已初始化好的conn加入freelist(conn_add_to_freelist())。
此处server_sockets()创建了两个监听端口,一个IPV4,一个IPV6,两个struct conn最终链入static conn* listen_conn链表。
注意:在<netdb.h>中,#define NI_MAXSERV 32; #define NI_MAXHOST 1025。
SO_REUSEADDR:允许重启的监听服务器bind其众所周知端口,即使以前建立的将该端口用作它们本地端口的连接仍存在。允许进程绑定一个处于TIME_WAIT的端口。
SO_KEEPALIVE:如果两小时无数据交换,发送存活探测分节。
SO_LINGER:设置close之后对缓冲区和套接口的处理;此处设置l_onff为0,即在套接口上不能再发出发送和接收请求,套接口发送缓冲区中内容被发送到对端。
TCP_NODELAY:将数据包直接发送,而不是组成大的分组再发送。
server_sockets(settings.udpport, udp_transport, portnumber_file)针对UDP,同样调用server_socket()。server_socket()中创建socket并调用maximize_sndbuf()设置发送缓存为最大值。之后bind()。最后调用dispatch_conn_new()。
dispatch_conn_new()首先调用cqi_new()从cqi_freelist获得一个CQ_ITEM(struct conn_queue_item,An item in the connection queue),初始化CQ_ITEM;分配一个thread,调用cq_push(thread->new_conn_queue, item)将该item链入thread的conn_queue中;最后写一个‘c’到thread,这将使thread调用事件处理函数thread_libevent_process(),进一步调用conn_new(item->sfd, item->init_state, item->event_flags, item->read_buffer_size, item->transport, me->base))。可以看出,实际上主进程将item传送给thread,thread通过这个item来创建一个conn。
/*
* Dispatches a new connection to another thread. This is only ever called
* from the main thread, either during initialization (for UDP) or because
* of an incoming connection.
*/
void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
int read_buffer_size, enum network_transport transport) {
CQ_ITEM *item = cqi_new();
int tid = (last_thread + 1) % settings.num_threads;
//以此种方式来取出线程
LIBEVENT_THREAD *thread = threads + tid;
last_thread = tid;
item->sfd = sfd;
item->init_state = init_state;
item->event_flags = event_flags;
item->read_buffer_size = read_buffer_size;
item->transport = transport;
//将新item放至threads的new_conn_queue队列中
cq_push(thread->new_conn_queue, item);
MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
//写一个字节启动新的线程
if (write(thread->notify_send_fd, "", 1) != 1) {
perror("Writing to thread notify pipe");
}
}
至此,创建了两个tcp socket(分别用于ipv4和ipv6),主进程创建conn并监听;主进程创建了两个udp socket(分别用于ipv4和ipv6),4个子进程创建conn并各自管理。如下:
<28 server listening (auto-negotiate)
<29 server listening (auto-negotiate)
<30 send buffer was 112640, now 268435456
<30 server listening (udp)
<30 server listening (udp)
<30 server listening (udp)
<30 server listening (udp)
<31 send buffer was 112640, now 268435456
<31 server listening (udp)
<31 server listening (udp)
<31 server listening (udp)
<31 server listening (udp)
(5)最后阶段
event_base_loop(main_base, 0)主进程进入事件循环。
小结一下:主线程中的事件主要是针对两个tcp socket的,事件处理函数为event_handler();还有一个计数器事件clockevent,定时一秒,处理函数为clock_handler()。四个工作线程的事件一样,都是一个管道notify_receive_fd(事件处理函数为thread_libevent_process()),一个udp ipv4 socket,一个udp ipv6 socket,这两个的事件处理函数为event_handler() 。注意四个工作线程之前就进入事件循环了。
至此,程序就完全跑起来了。
(6)清理阶段
默认情况下,event_base_loop()会一直循环,等待事件触发,然后调用他们的回调函数。直到没有添加的事件,或者调用了event_base_loopbreak()或者event_base_loopexit()。如果event_base_loop()退出,主进程将会执行一些清理过程,如下:
stop_assoc_maintenance_thread()简单的设置do_run_maintenance_thread = 0;线程maintenance_tid检测到do_run_maintenance_thread = 0就会自动退出。......
3、当client发起连接
执行命令telnet localhost 11211,向memcached发起tcp连接。memcached调用event_handler()处理。
event_handler(const int fd, const short which, void *arg)其中which传入event.ev_events,为事件类型。event_handler()调用drive_machine(conn *c)。drive_machine()之后介绍。