libevent服务端设计(添加服务端发送消息)

 

1 libevent介绍和安装

介绍

libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的I/O复用技术进行了封装,当我们编译库的代码时,编译的脚本将会根据OS支持的处理事件机制,来编译相应的代码,从而在libevent接口上保持一致。

在当前的服务器上,面对的主要问题就是要能处理大量的连接。而通过libevent这个网络库,我们就可以调用它的API来很好的解决上面的问题。首先,可以来回顾一下,对这个问题的传统解决方法。

问题: 如何处理多个客户端连接

解决方案1:I/O复用技术

这几种方式都是同步I/O,即当读写事件就绪,他们自己需要负责进行读写,这个读写过程是阻塞的,而异步I/O则不需要自己负责读写,只需要通知负责读写的程序就可以了。

  • 循环
    假设当前我服务器有多个网络连接需要看管,那么我就循环遍历打开的网络连接的列表,来判断是否有要读取的数据。这种方法的缺点很明显,那就是 1.速度缓慢(必须遍历所有的网络连接) 2.效率低 (处理一个连接时可能发生阻塞,妨碍其他网络连接的检查和处理)

  • select方式
    select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。
    select返回后,需要逐一检查关注的描述符是否被SET(事件是否发生)。(select支持的文件描述符数量太小了,默认是1024)。

  • poll方式
    poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
    poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
    poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。

  • epoll方式
    epoll通过epoll_create创建一个用于epoll轮询的描述符,通过epoll_ctl添加/修改/删除事件,通过epoll_wait检查事件,epoll_wait的第二个参数用于存放结果。
    epoll与select、poll不同,首先,其不用每次调用都向内核拷贝事件描述信息,在第一次调用后,事件信息就会与对应的epoll描述符关联起来。其次,epoll不是通过轮询,而是通过在等待的描述符上注册回调函数,当事件发生时,回调函数负责把发生的事件存储在就绪事件链表中,最后写到用户空间。
    epoll返回后,该参数指向的缓冲区中即为发生的事件,对缓冲区中每个元素进行处理即可,而不需要像poll、select那样进行轮询检查。

解决方案2:多线程技术或多进程技术

多线程技术和多进程技术也可以处理高并发的数据连接,因为在服务器中可以产生大量的进程和线程和处理我们需要监视的连接。但是,这两种方式也是有很大的局限性的,比如多进程模型就不适合大量的短连接,因为进程的产生和关闭需要消耗较大的系统性能,同样,还要进程进程间的通信,在CPU性能不足的情况下不太适合。而多线程技术则不太适合处理长连接,因为当我们建立一个进程时,linux中会消耗8G的栈空间,如果我们的每个连接都杵着不断开,那么大量连接长连接后,导致的结果就是内存的大量消耗。

解决方案3:常用的上述二者复合使用
上述的两种方法各具有优缺点,因此,我们可以将上述的方法结合起来,这也是目前使用较多的处理高并发的方法。多进程+I/O复用或者多线程+I/O复用。而在具体的实现上,又可以分为很多的方式。比如多线程+I/O复用技术,我们使用使用一个主线程负责监听一个端口和接受的描述符是否有读写事件产生,如果有,则将事件分发给其他的工作进程去完成,这也是进程池的理念。

在说完上述的高并发的处理方法之后,我们可以来介绍一个libevent的主要特色了。

同样,lievent也是采用的上述系统提供的select,poll和epoll方法来进行I/O复用,但是针对于多个系统平台上的不同的I/O复用实现方式,libevent进行了重新的封装,并提供了统一的API接口。libevent在实现上使用了事件驱动这种机制,其本质上是一种Reactor模式。

Reactor模式,是一种事件驱动机制。应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

在Libevent中也是一样,向Libevent框架注册相应的事件和回调函数;当这些事件发生时,Libevent会调用这些回调函数处理相应的事件。

lbevent的事件支持三种,分别是网络IO、定时器和信号。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。

安装

libevent的安装很简单,我是直接从github上clone下一个源码,然后进行编译安装的。

具体的命令是(假设你已经安装了git):

  # git clone https://github.com/nmathewson/Libevent.git
  # cd Libevent
  # sh autogen.sh
  # ./configure && make
  # make install
  # make verify  //验证安装

代码讲解

开启服务端,并关联接收回调函数

        if(listen(m_listener,LISTEN_BACKLOG)<0)
        {
            return false;
        }
        evutil_make_socket_nonblocking(m_listener);//设置为非阻塞模式
        m_ebase=event_base_new();		//初始化event_base结构体,包含events集合并选择事件类型
        event_init();//初始化
        if(m_ebase==NULL)
        {
            return false;
        }            m_listen_event=event_new(m_ebase,m_listener, EV_READ|EV_WRITE|EV_PERSIST, do_accept,(void*)m_ebase);//参数:event_base,监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数
        event_add(m_listen_event ,NULL);//参数:event,超时时间(struct timeval *类型,NULL表示无超时设置)
        event_base_dispatch(m_ebase);	//启动循环事件

 

    //接收回调
    void do_accept(evutil_socket_t listener,short event,void *arg);
    //读回调
    void read_cb(struct bufferevent* bev,void * arg);
    //错误触发回调
    void error_cb(struct bufferevent* bev,short event, void * arg);
    //写回调
    void write_cb(struct bufferevent* bev,void * arg);
    //超时回调
    void timer_cb(int fd, short event, void *arg);

平常我们所用到的只需要接收回调、读回调、写回调和错误回调,这四种在服务器上是很常见的,接收回调可以在接收到客户端连接的情况下处理连接,读回调则依据已连接的客户端进行读取数据,写回调则在客户端与服务器连接完成的并绑定回调的情况下触发一次写回调。

讲到写回调,作者要简单讲两句,很皮。只是个人观点

写回调在客户端连接成功后,在accept回调函数触发完成之后会且已开启EV_WRITE则写回调触发,执行一次,若写回调函数内存在bufferevent_write,则下一次情况写回调依然会触发,不过若无bufferevent_write函数,则写回调不再触发,那之后再调用时怎么触发呢?

之后若写回调不触发,我们在写回调外部调用bufferevent_write输入数据保存到libevent中的缓存区内部,不过写回调依然未触发,而在读回调读完从客户端中接收的消息后,惊奇发现,写回调出了!!试了几次后发现依然这样,只有当读回调接收到消息,且写回调的缓存区内存有消息时,写回调会触发。

写回调完了,接下来就是我添加的超时回调

作者在完成读回调,写回调,错误回调之后,又绑定了超时回调。而超时回调则是作者用来解决发送消息的发送端实时问题,在超时回调内作者调用bufferevent_write函数可直接发送消息到客户端,解决了在函数在用套接字send的问题。防止两端接收发送出现错误问题,实现利用libevent来发送和接收消息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值