使用libevent编写linux服务

 libevent简介

libevent是一个支持Windowslinuxbsd等平台的网络事件驱动程序库。它支持多种I/O服用机制,按照优先级从高到低依次为:evportkqueueepolldevpollrtsigpollselect。它可根据操作系统,按照优先级从高到底自主选择驱动。

     用户可以通过http://www.monkey.org/~provos/libevent/来获取libevent的源码、libevent出现的背景、以及其他一些详细资料。

 libevent的使用

1 初始化事件  

我们首先完成对libenvent的事件初始化和事件驱动模型的选择。(在使用多线程的情况下,一般我们需获取所返回的事件根基)

 

main_base = event_init();

  

event_init函数返回的是一个event_base对象,该对象包括了事件处理过程中的一些全局变量,其结构为:

struct  event_base {
    
const   struct  eventop  * evsel;
    
void   * evbase;
    
int  event_count;         /*  counts number of total events  */
    
int  event_count_active;  /*  counts number of active events  */

    int  event_gotterm;       /*  Set to terminate loop  */
    
int  event_break;         /*  Set to terminate loop immediately  */

    
/*  active event management  */
    
struct  event_list  ** activequeues;
    
int  nactivequeues;

    
/*  signal handling info  */
    
struct  evsignal_info sig;

    
struct  event_list eventqueue;
    
struct  timeval event_tv;

    
struct  min_heap timeheap;

    
struct  timeval tv_cache;
};


2 添加事件

        在事件初始化完毕后,我们可以使用event_set设置事件,然后使用event_add将其加入。

   这里我们首先完成socket的监听,然后将其加入的事件队列中(这里我们对所有的异常都不做考虑)。

1socket监听

struct  sockaddr_in listen_addr;

int  port  =   10000 // socket监听端口

int  listen_fd  =  socket(AF_INET, SOCK_STREAM,  0 );

memset(
& listen_addr,  0 sizeof (listen_addr));

listen_addr.sin_family 
=  AF_INET;
listen_addr.sin_addr.s_addr 
=  INADDR_ANY;
listen_addr.sin_port 
=  htons(port)

reuseaddr_on 
=   1 ;

setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, 
& reuseaddr_on,  sizeof (reuseaddr_on)) #支持端口复用

bind(listen_fd, (
struct  sockaddr  * & listen_addr,  sizeof (listen_addr)) '

listen(listen_fd, 
1024 );

/* 将描述符设置为非阻塞 */

int  flags  =  fcntl(listen_fd,, F_GETFL);

flags 
|=  O_NONBLOCK;

fcntl(listen_fd, F_SETFL, flags);


2)事件设置

      socket服务建立后,就可以进行事件设置。我们使用event_set来设置事件对象,其传入参数包括事件根基(event_base对象),描述符,事件类型,事件发生时的回调函数,回调函数传入参数。其中事件类型包括EV_READEV_WRITEEV_PERSISTEV_PERSIST和前两者结合使用,表示该事件为持续事件。

struct event ev;

event_set(&ev, listen_fd, EV_READ | EV_PERSIST, accept_handle, (void *)&ev);

3)事件添加与删除

         事件设置好后,就可以将其加入事件队列。event_add用来将事件加入,它接受两个参数:要添加的事件和时间的超时值。 如果需要将事件删除,可以使用event_del来完成。event_del函数会取消所指定的事件。

event_add(&ev, NULL)

3 进入事件循环

    事件成功添加后就是万事具备只欠东风了,libevent提供了多种方式来进入事件循环,我个人常用的是event_dispatchevent_base_loop,前者最后实际是使用当前事件根基来调用event_base_loop

   event_base_loop(main_base, 0);

4 处理连接

     到这里为止,大家已经完成了事件的设置、事件的添加并进入到了事件循环。但是当事件发生时(这里就是连接建立)如何处理呢? 聪明的用户会想到前面我们在事件设置时指定的回调函数accept_handle。没错,当连接建立时回调函数accept_handle会自动的得到调用。

       对于缓冲区的读写在非阻塞式网络编程中是一个难以处理的问题,幸运的是libevent提供了buffereventevbuf来替我们完成该项工作。这里我们采用bufferevent来处理。

(1)生成bufferevent对象

      使用bufferevent_new对象来生成bufferevent对象,并分别指定读、写、连接错误时的处理函数和函数传入参数。

(2) 设置读取量

bufferevent的读事件激活以后,即使用户没有读取完bufferevent缓冲区中的数据, bufferevent读事件也不会再次被激活。因为bufferevent的读事件是由其所监控的描述符的读事件激活的,只有描述符可读,读事件才会被激活。可通过设置wm_read.high来控制bufferevent从描述符缓冲区中读取的数据量。

(3) 将事件加入事件队列

和前面一样,在事件设置好后,需将事件加入到事件队列中, 不过bufferevent的有自己专门的加入函数bufferevent_base_set和激活函数bufferevent_enable

bufferevent接收两个参数事件根基个事件对象,前者用来指定事件将加入到哪个事件根基中,后者说明需将那个bufferevnet事件加入。(在多线程的情况下,每个线程可能有自己单独的事件根基)

bufferevent初始化完毕后,可以使用bufferevent_enablebufferevent_disable反复的激活与禁止事件,其接收参数为事件对象和事件标志。其中标志参数为EV_READEV_WRITE

 

void  accept_handle( const   int  sfd,  const   short   event void   * arg)
{

   
struct  sockaddr_in addr;

   socklen_t addrlen 
=   sizeof (addr);

   
int  fd  =  accept(sfd, ( struct  sockaddr  * & addr,  & addrlen);  // 处理连接

    buf_ev 
=  bufferevent_new(fd,   buffered_on_read, NULL, NULL, fd)

    buf_ev
-> wm_read.high  =   4096

    bufferevent_base_set(main_base, buf_ev);

    bufferevent_enable(buf_ev, EV_READ);

}

5 读取缓冲区

当缓冲区读就绪时会自动激活前面注册的缓冲区读函数,我们可以使用bufferevent_read函数来读取缓冲区

bufferevent_read函数参数分别为:所需读取的事件缓冲区,读入数据的存放地,希望读取的字节数。函数返回实际读取的字节数。

注意:及时缓冲区未读完,事件也不会再次被激活(除非再次有数据)。因此此处需反复读取直到全部读取完毕。

6 写回客户端

    bufferevent系列函数不但支持读取缓冲区,而且支持写缓冲区(即将结果返回给客户端)。

 

void  buffered_on_read( struct  bufferevent  * bev,  void   *  arg){

    
char  buffer[ 4096 ]

    ret 
=  bufferevent_read(bev,  & buffer,  4096 );

    bufferevent_write(bef, (
void   * ) & buffer,  4096 );

}

 

 结束语

    至此我们已经可以使用libevent编写非阻塞的事件驱动服务器,它支持连接建立、socket可读等事件的处理。

但在实际的使用事件驱动的服务器中,通常是使用一个线程处理连接,然后使用多个线程来处理请求。后面我将继续介绍如何使用libevent来编写多线程的服务器。

 

你可能感兴趣的:(c,linux,libevent,高性能,休闲)