10. C++异步IO处理库和使用libevent实现高性能服务器

C++比较有名的异步IO处理库

  • libevent
    这个主要使用的是epoll。
  • libevthp
  • libuv
  • libev
    我们主要介绍libevent。

libevent重要函数

event_base_new

这个可以对应于epoll_create也就是创建一个实例。还可以初始化libevent所有管理相关的代码。比如说所能用到的队列,栈,都是可以初始化的。
函数原型
原型:struct event_base *event_base_new(void);
函数实现

struct event_base *event_base_new(void)
{
  struct event_base *base = NULL;
  struct event_config *cfg = event_config_new();
  if (cfg) {
    base = event_base_new_with_config(cfg);
    event_config_free(cfg);
  }
  return base;
}

函数作用
创建event_base对象
注意:采用event_base_new( )创建出来的事件集合,最后要用 event_base_free(base)释放掉,因为event_base_new( )是在堆空间上进行创建的。

event_base_new( )与event_init( )的区别
这两个函数都会创建一个事件集合
event_init( )创建的是一个全局的事件集合
event_base_new( )创建的是一个局部的事件集合
event_init( )函数实现是由event_base_new( )封装而成的

event_base_free( )

函数:释放事件集合
用于释放event_base_new( )函数创建的集合

event_base_dispatch

对应于epoll中的epoll_wait。在epoll_wait中可以收集所有的触发的文件描述符。

event new、event add、event del、event free

这三个就是对应于epoll_ctl 用于将事件加入、删除到libevent中。

event new

创建一个事件对象

event add

函数原型:int event_add(struct event *ev, const struct timeval *tv);。将该事件加入到libevent集合中。
函数作用
1.将event注册到event_base的I/O多路复用要监听的事件中;
2.将event注册到event_base的已注册事件链表中;
3.如果传入了超时时间,则删除旧的超时时间,重新设置,并将event添加到event_base的小根堆中;
如果没有传入超时时间,则不会添加到小根堆中。
只有步骤1成功,才会执行步骤2和3;否则什么都没做,直接返回,保证不会改变event的状态。

event del

原型:event_del(struct event *ev);将该事件从libevent集合中删除

event free

释放该事件对象

event_init

函数原型:struct event_base *event_init(void)

event_set

初始化事件,函数原型
原型:void event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg);
参数:
1)事件
2)关联的文件描述符
3)事件类型
4)回调函数
5)回调函数的参数
函数作用:
初始化event事件(其实就是给结构体ev的成员赋值)
fd 表示事件对应的文件描述符,
events表示事件的类型,
callback是回调函数(即当fd满足条件时调用该函数),
arg表示给回调函数传递的参数

一般首先要用event newnew一个事件。然后用event add将该事件加入到libevent中。用event del可以将该事件从libevent中删除。event free则是将该事件释放掉。

evconnlistener_new_bind( )函数

作用:用于创建一个evconnlistener对象的函数之一,用于监听指定地址和端口上的连接请求。

函数返回值为一个evconnlistener类型的指针,表示成功创建的监听器对象。当监听器接收到新连接时,将会调用回调函数cb,并将参数ptr传递给回调函数。

函数原型

struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
                                               evconnlistener_cb cb,
                                               void *ptr,
                                               unsigned flags,
                                               int backlog,
                                               const struct sockaddr *sa,
                                               int socklen);

参数介绍
1)base
struct event_base类型的指针,表示事件集合
2)cb
evconnlistener_cb类型的回调函数指针,用于处理新连接的事件,连接之后就会调用这个回调函数。其中:evconnlistener_cb类型
在这里插入图片描述

typedef void (*evconnlistener_cb)(
	struct evconnlistener *listener,
	evutil_socket_t fd,
	struct sockaddr *addr,
	int socklen,
void *ctx
);
其中,参数含义如下:
· listener:指向监听器的指针。
. fd:表示新连接的文件描述符。
. addr:指向新连接的远程地址。
. socklen:表示地址的长度。
. ctx:指向回调函数上下文的指针。
回调函数的返回值类型为void,即不需要返回任何值。

3)ptr
传递给回调函数的参数指针
4)flags
标志位,可以是LEV_OPT_REUSEABLELEV_OPT_CLOSE_ON_FREE等选项
其中:
LEV_OPT_REUSEABLE是libevent库中用于创建监听器的选项之一,用于指示创建的监听器是否可以重用。具体来说,当使用LEV_OPT_REUSEABLE选项创建监听器时,表示创建的监听器可以在关闭之后再次被使用。这意味着,当监听器关闭时,它并没有被完全销毁,而是被放置在一个可重用的状态中,以便在需要时可以被重新使用。这种方式可以提高应用程序的性能,因为不需要每次创建新的监听器。
LEV_OPT_CLOSE_ON_FREElibevent库中用于创建监听器的选项之一,用于指示在释放监听器时是否关闭监听器的文件描述符。具体来说,当使用LEV_OPT_CLOSE_ON_FREE选项创建监听器时,表示在释放监听器时,同时也会关闭监听器的文件描述符。这意味着,应用程序不需要显式地调用close()函数来关闭文件描述符,而是在释放监听器时自动关闭。这种方式可以减少应用程序的代码量,并提高代码的可读性和可维护性。
5)backlog:监听队列的长度。
6)sa:指向struct sockaddr类型的指针,表示要监听的地址和端口。
7)socklen:sa指向的地址结构体的长度。

libevent编译和安装

  1. http://libevent.org/
    首先进入这个网站,然后
  2. wget -c addr --no-check-certificate
  3. 进入该文件夹
    然后执行这个
    ./configure --prefix=/usr/local/libevent
  4. 执行这个
mkdir build && cd build

cmake .. -DEVENT__DISABLE_OPENSSL=ON -DEVENT__LIBRARY_TYPE=STATIC

make && make install

libevent实现高性能服务器

bufferevent的作用

  • 从外面看它就是一个缓冲区,可以与socket绑定
  • 内部由输入和输出缓冲区组成
  • 每一个socket对应一个bufferevent
  • 当socket有事件触发时,可以设置回调函数

bufferevent实际上也是一个event,只不过比普通的event高级一些,它的内部有两个缓冲区,以及一个文件描述符(网络套接字)。我们都知道一个网络套接字有读和写两个缓冲区,bufferevent同样也带有两个缓冲区,还有就是libevent事件驱动的核心回调函数,那么四个缓冲区以及触发回调的关系如下所示:
在这里插入图片描述
有三个回调函数:

  • 读回调:当bufferevent将底层读缓冲区的数据读到自身的读缓冲区时触发读事件回调
  • 写回调:当bufferevent将自身写缓冲区的数据写到底层写缓冲区的时候触发写事件回调
  • 事件回调:当bufferevent绑定的socket连接,断开或者异常的时候触发事件回调

bufferevent的API

1)创建基于套接字的bufferevent
可以使用 bufferevent_socket_new()创建基于套接字的 bufferevent

struct bufferevent *bufferevent_socket_new(
    struct event_base *base,
    evutil_socket_t fd,
    enum bufferevent_options options
);
base:对应根节点
fd:文件描述符
options:bufferevent的选项
   - BEV_OPT_CLOSE_ON_FREE:释放bufferevent自动关闭底层接口
   - BEV_OPT_THREADSTAFE:使bufferevent能够在多线程下是安全的

成功时函数返回一个 bufferevent, 失败则返回NULL

2)bufferevent_socket_connect函数

int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *serv, int socklen);

bufferevent_socket_connect封装了底层的socket与connect接口,通过调用此函数,可以将bufferevent事件与通信的socket进行绑定,参数如下:
	bev:需要提前初始化的bufferevent事件
	serv:对端的ip地址,端口,协议的结构指针
	socklen:描述serv的长度
  1. bufferevent_free函数
void bufferevent_free(struct bufferevent *bufev);
释放bufferevent

4)bufferevent_setcb函数

void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);

bufferevent_setcb用于设置bufferevent的回调函数,readcb,writecb,eventcb分别对应了读回调,写回调,事件回调,cbarg代表回调函数的参数。回调函数的原型:

typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx); //读写回调
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx); //事件回调
what对应的事件:
	BEV_EVENT_EOF:对方关闭连接
	BEV_EVENT_ERROR:出错
	BEV_EVENT_TIMEOUT:超时
	BEV_EVENT_CONNECTED:建立连接成功
  1. bufferevent_write函数
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
bufferevent_write是将data的数据写到bufferevent的写缓冲区。
  1. bufferevent_write_buffer函数
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
bufferevent_write_buffer 是将数据写到写缓冲区另外一个写法,实际上bufferevent的内部的两个缓冲区结构就是struct evbuffer。  
  1. bufferevent_read函数
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
bufferevent_read 是将bufferevent的读缓冲区数据读到data中,同时将读到的数据从bufferevent的读缓冲清除。
  1. bufferevent_read_buffer函数
int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);
bufferevent_read_buffer 将bufferevent读缓冲数据读到buf中,接口的另外一种。
  1. bufferevent_enable和bufferevent_disable函数
int bufferevent_enable(struct bufferevent *bufev, short event); //EV_READ、EV_WRITE
int bufferevent_disable(struct bufferevent *bufev, short event); //EV_READ、EV_WRITE
bufferevent_enable与bufferevent_disable是设置事件是否生效,如果设置为disable,事件回调将不会被触发。

链接监听器(evconnlistener)的API

链接监听器封装了底层的socket通信相关函数,比如socket,bind,listen,accept这几个函数。链接监听器创建后实际上相当于调用了socket,bind,listen,此时等待新的客户端连接到来,如果有新的客户端连接,那么内部先进行accept处理,然后调用用户指定的回调函数。

1)evconnlistener_new_bind函数

struct evconnlistener *evconnlistener_new_bind(struct event_base *base, 
evconnlistener_cb cb, void *ptr, unsigned flags, 
int backlog,const struct sockaddr *sa, int socklen);

evconnlistener_new_bind是在当前没有套接字的情况下对链接监听器进行初始化,看最后2个参数实际上就是bind使用的关键参数,backlog是listen函数的关键参数(略有不同的是,如果backlog是-1,那么监听器会自动选择一个合适的值,如果填0,那么监听器会认为listen函数已经被调用过了),ptr是回调函数的参数,cb是有新连接之后的回调函数,但是注意这个回调函数触发的时候,链接器已经处理好新连接了,并将与新连接通信的描述符交给回调函数。
参数如下:
base:base根节点
cb:提取cfd后调用的回调
ptr:传给回调的参数
 backlog:-1
sa:绑定的地址信息
socklen:sa的大小 
Flags 需要参考几个值:  
LEV_OPT_LEAVE_SOCKETS_BLOCKING :文件描述符为阻塞的
LEV_OPT_CLOSE_ON_FREE: 释放时自动关闭
LEV_OPT_REUSEABLE: 端口复用
LEV_OPT_THREADSAFE: 分配锁,线程安全
返回值:链接监听器的地址

2)evconnlistener_new函数

struct evconnlistener *evconnlistener_new(struct event_base *base,
evconnlistener_cb cb, 
void *ptr, unsigned flags, int backlog,evutil_socket_t fd);
evconnlistener_new函数与前一个函数不同的地方在与后2个参数,使用本函数时,
认为socket已经初始化好,并且bind完成,甚至也可以做完listen,所以大多数时候,
我们都可以使用第一个函数。  

3)回调函数evconnlistener_cb

typedef void (*evconnlistener_cb)(struct evconnlistener *evl, evutil_socket_t fd, struct sockaddr *cliaddr, int socklen, void *ptr);
主要回调函数fd参数会与客户端通信的描述符,并非是等待连接的监听的那个描述符,所以cliaddr对应的也是新连接的对端地址信息,已经是accept处理好的。
参数如下:
链接监听器的地址
fd:cfd
cliaddr:客户端的地址信息
ptr:evconnlistener_new_bind传过来的参数

4)evconnlistener_free函数

void evconnlistener_free(struct evconnlistener *lev);
释放链接监听器

5)evconnlistener_enable函数

int evconnlistener_enable(struct evconnlistener *lev);
使链接监听器生效

6)evconnlistener_disable函数

函数原型:
int evconnlistener_disable(struct evconnlistener *lev);
使链接监听器失效

代码实现

#include <iostream>

#include<event2/listener.h>

#include<event2/bufferevent.h>

#include<event2/buffer.h>

#include<arpa/inet.h>

//端口
#define PORT 8888
#define MESSAGE_LEN 1024

void on_read_cb(struct bufferevent *bev,void *ctx){//读
    struct evbuffer *input=NULL,*output=NULL;
    //分别从bufferevent里拿到 input buffer 和 output buffer.
    input=bufferevent_get_input(bev);
    output=bufferevent_get_output(bev);
    //将input buffer里面收到的数据全部搬到output buffer , libevent会为我们自动发送给客户端.
    evbuffer_add_buffer(output,input);
}

//回调函数,连接之后就会调用这个回调函数
void on_accept_cb(struct evconnlistener *listener,
                  evutil_socket_t fd,
                  struct sockaddr* addr,
                  int socket,
                  void* ctx){//就是连接之后干啥
    struct bufferevent *bev=NULL;
    struct event_base *base=NULL;

    base=evconnlistener_get_base(listener);
    //创建一个bufferevent
    bev=bufferevent_socket_new(base,fd,0);
    //设置读事件EV_READ和写事件EV_WRITE
    bufferevent_enable(bev,EV_READ|EV_WRITE);
    //设置回调函数on_read_cb读函数
    bufferevent_setcb(bev,on_read_cb,NULL,NULL,NULL);
}

int main(int argc,char* argv[]){

    struct sockaddr_in serveraddr;

    struct evconnlistener *listener = NULL;
    //创建一个libevent
    struct event_base *base = NULL;
    base = event_base_new();

    serveraddr.sin_family=AF_INET;
    serveraddr.sin_port=htons(PORT);
    serveraddr.sin_addr.s_addr=INADDR_ANY;//任何网卡的8111端口都监听

    listener = evconnlistener_new_bind(base,
                            on_accept_cb,//回调
                            NULL,//回调函数的输入参数
                            LEV_OPT_REUSEABLE,//可以快速启动
                            10,//同一时刻处理多少个
                            (struct sockaddr*)&serveraddr,
                            sizeof(serveraddr));

    event_base_dispatch(base);

    evconnlistener_free(listener);

    event_base_free(base);


    return 0;
}

在这里插入图片描述
我们实验一个完整的:


  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值