memcached通过epoll(使用libevent)实现异步服务,主要由"主线程"和多个"worker线程"构成,主线程负责监听网络链接,并且accept连接。当监听到连接accept成功后,把连接句柄FD传给其中的一个空闲work线程处理。空闲的worker线程接收到主线程传过来的连接句柄DF后,将其加入自己的epoll监听队列并处理该连接的读写事件。
博主从memcahced剥离了基于线程池的收发包框架代码,linux下直接make编译可运行:https://github.com/donaldhuang/memcached_network
用libevent做事件监听使用起来非常简单:
event_flags=EV_READ | EV_PERSIST;//监听可读事件
base=(event_base*)event_init();//初始化
event_add(event, time_tv);//把事件加入到事件基地进行监听,time_tv为epoll没有可读事件的超时时间
event_base_loop(main_base,flag);//进入监听事件的循环,flag分为阻塞和非阻塞
1)主线程根据用户的设置创建n个worker线程,每个线程有一个待处理的连接句柄队列conn_queue,且每个线程都使用libevent监听一个管道并设置了处理函数thread_libevent_process
2)主线程首先使用libevent监听一个主端口。当有连接事件到达时,accept获得一个连接句柄FD,把该FD封装到一个CQ_ITEM,然后选择一个空闲的Worker线程,将CQ_ITEM压入Worker线程的待处理的连接句柄队列中,并往Worker线程家庭的管道中写入一个"c"字符。
3) Worker线程监听到了管道可读事件,触发thread_libevent_process处理函数。从待处理的连接句柄队列中取出一个CQ_ITEM,然后用libevent监听CQ_ITEM里面的连接句柄的可读事件,并设置事件处理函数event_handler
博主从memcahced剥离了基于线程池的收发包框架代码,linux下直接make编译可运行:https://github.com/donaldhuang/memcached_network
用libevent做事件监听使用起来非常简单:
event_flags=EV_READ | EV_PERSIST;//监听可读事件
base=(event_base*)event_init();//初始化
event_set(event, sfd, event_flags, base_event_handler,argvs);
//创建监听sfd句柄的可读事件,处理函数是base_event_handler,argvs是传入处理函数的参数
event_base_set(base, event);//为创建的事件event指定事件基地event_add(event, time_tv);//把事件加入到事件基地进行监听,time_tv为epoll没有可读事件的超时时间
event_base_loop(main_base,flag);//进入监听事件的循环,flag分为阻塞和非阻塞
1)主线程根据用户的设置创建n个worker线程,每个线程有一个待处理的连接句柄队列conn_queue,且每个线程都使用libevent监听一个管道并设置了处理函数thread_libevent_process
threads=(LIBEVENT_THREAD*)calloc(nthreads,sizeof(LIBEVENT_THREAD)); //LIBEVENT_THREAD,加入libevent元素的thread结构 “数组”
if(!threads)
{
perror("can't allocate thread des");
exit(1);
}
for(i=0;i<nthreads;i++) //设置thread和thread中的libevent所需属性
{
int fds[2];
if(pipe(fds)) //thread和主线程的通信pipe
{
perror("can't create notify pipe");
exit(1);
}
threads[i].notify_receive_fd=fds[0];
threads[i].notify_send_fd=fds[1];
setup_event_thread(&threads[i]); //设置thread和thread中的libevent所需属性
printf("init thread:%d\n",i);
}
for(i=0;i<nthreads;i++)
{
create_worker(worker_libevent,&threads[i]); //启动thread
}
2)主线程首先使用libevent监听一个主端口。当有连接事件到达时,accept获得一个连接句柄FD,把该FD封装到一个CQ_ITEM,然后选择一个空闲的Worker线程,将CQ_ITEM压入Worker线程的待处理的连接句柄队列中,并往Worker线程家庭的管道中写入一个"c"字符。
CQ_ITEM *item=cqi_new();
int tid=(last_thread+1)%settings.num_threads; //轮询选出workerThread(数组)
LIBEVENT_THREAD *thread=workerThreads->threads+tid;
last_thread=tid;
item->sfd=sfd; //封装必要的信息到item结构,后面会利用item封装为conn
item->init_state=init_state;
item->event_flags=event_flags;
item->read_buffer_size=read_buffer_size;
item->transport=transport;
write(thread->notify_send_fd,"c",1);//主线程和workerThread的通信方式,写入到notify_send_fd告诉辅助线程item准备好了,可以处理
last_thread++;
3) Worker线程监听到了管道可读事件,触发thread_libevent_process处理函数。从待处理的连接句柄队列中取出一个CQ_ITEM,然后用libevent监听CQ_ITEM里面的连接句柄的可读事件,并设置事件处理函数event_handler
LIBEVENT_THREAD *me=(LIBEVENT_THREAD*)arg;
CQ_ITEM *item;
char buf[1];
if(read(fd,buf,1)!=1)
fprintf(stderr,"can't read from libevent pipe\n");
item=cq_pop(me->new_conn_queue);
if(NULL!=item)
{
conn *c= conn_new (item->sfd,item->init_state,item->event_flags,
item->read_buffer_size,item->transport,me->base);
if(NULL==c)
{
if( IS_UDP(item->transport))
{
fprintf(stderr,"can't listen for events on UDP\n");
exit(1);
}
else
{
fprintf(stderr,"can't listen for events on fd %d\n",item->sfd);
close(item->sfd);
}
}
else
{
c->thread=me;
}
cqi_free(item);
}
conn *conn_new(const int sfd,enum conn_states init_state,const int event_flags,
const int read_buffer_size,enum network_transport transport,
struct event_base *base)
{
c->sfd=sfd;
event_set(&c->event,sfd,event_flags,event_handler,(void*)c);
event_base_set(base,&c->event);
c->ev_flags=event_flags;
if(event_add(&c->event,0)==-1)
{
if( conn_add_to_freelist(c))
conn_free(c);
perror("event_add");
return NULL;
}
return c;
}