Libevent库使用指南
Libevent库使用指南
Libevent库概述
简介:
Libevent
:是一个用C语言编写的、轻量级的开源高性能事件通知库,是一个基于事件的异步通信模型- Libevent是基于reactor实现的
- Libevent广泛地应用作为底层的网络库,比如memcached、 Vomit、 Nylon、 Netchat等
- 下载Libevent库,看到stable表示稳定版本,现在稳定版有1.4、2.0以及2.1系列,使用的话使用最新的稳定版即可,但是初学源码从1.4学比较好(源码简单)
优点:
- 开源
- 精简,轻量级,专注于网络通信
- 跨平台,支持windows、Linux、Mac Os等
- 事件驱动,高性能
- 支持多种I/O多路复用技术,epoll、poll、dev/poll、select和kqueue等
- 支持I/O,定时器和信号等事件
安装:
从Libevent中下载最新的稳定版2.1.12-stable
解压刚刚下载的压缩包,得到一个目录
进入到目录,进行源码包安装(如果有README.md文件也可以进行查看)
1
2
3
4
5
6
7
8#检查安装环境,生成makefile
./configure
#生产.o和可执行文件
make
#将必要的资源拷贝到系统指定目录
sudo make install进入sample目录,运行demo验证库安装使用情况,比方说编译hello-world.c文件
1
2
3
4
5
6
7
8
9
10#需要加库选项 -levent
gcc hello-world.c -o hello-world -levent
#运行
./hello-world
#另开终端测试,因为这是一个服务器
nc 127.0.0.1 9995
#返回Hello,World!可以到/usr/lib或者/usr/local/lib下输入以下命令查看是否存在libevent库也就是libevent.so
1
2
3
4
5
6cd /usr/lib
#或者
cd /usr/local/lib
ll libevent*
cmake链接Libevent库:
1 |
|
Libevent基本框架:
创建event_base:event_base_new
创建事件
事件分为:
- 常规事件event:event_new
- 带缓冲区事件bufferevent:bufferevent_socket_new
将事件添加到base上
- 常规事件event:event_add
- 带缓冲区事件bufferevent:
循环监听事件满足:event_base_dispatch
释放event_base:event_base_free
event_base
创建event_base
struct event_base *event_base_new(void)
:用于创建event_base
1 |
|
释放event_base
void event_base_free(struct event_base *base)
:用于释放event_base
1 |
|
事件循环
启动循环
int event_base_dispatch(struct event_base *base)
:阻塞监听event_base
1 |
|
注意:
- 只有event_new中指定了EV_PERSIST才能持续触发,否则只触发一次,就跳出循环
- 通常设定为:EV_WRITE|EV_PERSIST、EV_READ|EV_PERSIST
退出循环
int event_base_loopbreak(struct event_base *base,const struct timeval *tv)
:立即停止循环1
2
3
4
5
6
7
8
9
10
11#include <event2/event.h>
int event_base_loopbreak(struct event_base *base);
//参数
base:要停止循环的event_base变量;
//返回值
成功:0;
失败:-1;int event_base_loopexit(struct event_base *base,const struct timeval *tv)
:在指定时间后停止循环1
2
3
4
5
6
7
8
9
10
11
12
13
14#include <event2/event.h>
int event_base_loopexit(struct event_base *base,const struct timeval *tv);
//参数
base:要退出循环的event_base变量;
tv:定时时间;
//返回值
成功:0;
失败:-1;
常规事件event
创建事件
strutc event *event_new(struct event_base *base,evutil_socket_t fd,short what,event_callback_fn cb,void *arg)
:用于创建一个常规事件,里面已经调用了初始化函数
1 |
|
初始化事件
int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, event_callback_fn callback, void *arg))
:初始化事件,再创建事件时会调用该函数
1 |
|
添加事件
int event_add(struct event *ev,const struct timeval *tv)
:将创建的事件添加到其创建时所填的event_base中
1 |
|
删除事件
int event_del(struct event *ev)
:从创建事件所绑定的event_base中删除指定事件
1 |
|
释放事件
void event_free(struct event *ev)
:释放(销毁)事件
1 |
|
其他函数
const char **event_get_supported_methods(void)
:查看支持哪些多路IO模型1
2
3
4
5
6#include <event2/event.h>
const char **event_get_supported_methods(void);
//返回值
返回:当前系统支持哪些多路IO模型,字符串数组;const char *event_base_get_method(const struct event_base *base)
:查看当前用的多路IO模型1
2
3
4
5
6#include <event2/event.h>
const char *event_base_get_method(const struct event_base *base);
//返回值
返回:当前用的多路IO模型;int event_reinit(struct event_base *base)
:重新初始化fork后子进程的event_base变量,使用该函数后,父进程创建的base才能在子进程中生效,相当于子进程中的event_base_new函数,不是所有事件后端都在调用 fork之后可以正确工作,所以需要重新初始化1
2
3
4
5
6
7
8
9
10
11#include <event2/event.h>
int event_reinit(struct event_base *base);
//参数
base:需要重新初始化的event_base对象;
//返回值
成功:0;
失败:-1;
未决和非未决
未决
:有资格被处理,但还没被处理,将事件添加到event_base中(event_add)则非未决态变成未决态
非未决
:没有资格处理,新创建的事件(event_new)都处于非未决态
激活态
:事件在阻塞监听时,当事件被触发后,则事件从未决态变为激活态
被处理态
:事件的回调函数调用后,激活态变为被处理态,然后自动进入非未决态
带缓冲区事件bufferevent
bufferevnt
头文件
:1
#include <event2/bufferevent.h>
原理
:bufferevent有两个缓冲区(读和写),也是队列实现的,读走即没,先进先出读
:有数据则调用读回调函数,使用bufferevent_read读数据写
:使用bufferevent_write向写缓冲区写数据,写缓冲区中一旦有数据就自动发送给对端,数据写完后,调用写回调函数(通知写数据完成)
创建带缓冲区事件
struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil_socket_t fd,enum bufferevent_options options)
:创建一个带缓冲区事件
1 |
|
释放带缓冲区事件
void bufferevent_free(struct bufferevent *bev)
:释放(销毁)带缓冲区事件
1 |
|
设置缓冲区回调函数
void bufferevent_setcb(struct bufferevent *bufev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void *cbarg)
:给带缓冲区事件对象bufev的缓冲区设置回调函数
1 |
|
注意:writecb回调函数是在写操作完成后调用的
readcb和writecb回调函数原型:
1 |
|
eventcb回调函数原型:
1 |
|
启用/禁用缓冲区
引言:在套接字中的两个缓冲区可以支持半关闭,因此在bufferevent的缓冲区中也可以通过启用/禁用支持全双工/半关闭
函数原型:
void bufferevent_enable(struct bufferevent *bufev,short events)
:启用缓冲区,通常用来启用的read缓冲区1
2
3
4
5
6
7
8
9
10
11
12#include <event2/bufferevent.h>
void bufferevent_enable(struct bufferevent *bufev,short events);
//参数
bufev:要启用缓冲区的bufferevent对象;
events:要启用的缓冲区类型(读、写和读写);
//取值列表
EV_READ:读缓冲区;
EV_WRITE:写缓冲区;
EV_READ|EV_WRITE:读写缓冲区;void bufferevent_disable(struct bufferevent *bufev,short events)
:禁用缓冲区1
2
3
4
5
6
7
8
9
10
11
12#include <event2/bufferevent.h>
void bufferevent_disable(struct bufferevent *bufev,short events);
//参数
bufev:要启用缓冲区的bufferevent对象;
events:要启用的缓冲区类型(读、写和读写);
//取值列表
EV_READ:读缓冲区;
EV_WRITE:写缓冲区;
EV_READ|EV_WRITE:读写缓冲区;short bufferevnt_get_enabled(struct bufferevent *bufev)
:获取缓冲区的禁用状态,需要借助&来得到1
2
3
4
5
6
7
8
9
10
11
12#include <event2/bufferevent.h>
short bufferevnt_get_enabled(struct bufferevent *bufev);
//参数
bufev:要查看缓冲区状态的bufferevent对象;
//返回值
返回:缓冲区状态;
//得到的返回值与你想查看是否禁用的缓冲区类型进行&操作
bufferevnt_get_enabled(bufev)&EV_READ:当结果为true则表示读缓冲区禁用,否则就是启用
注意:新建的bufferevent写缓冲是启用状态的,读缓冲是禁用状态的
设置/调整缓冲区水位
概念:
缓冲区"水位"
:也就是缓冲区的界限,对于低水位就是缓冲区中数据最小数,高水位就是缓冲区最大存储数据数对于每个bufferevent对象,都有4个水位分别为
读取低水位
:读缓冲区数据量高于此水位后,读取回调被调用。默认值0,导致一旦缓冲区有数据,立即调用回调读取高水位
:读缓冲区数据量高于此水位后,bufferevent不再将数据读取到缓冲区。默认值无限写入低水位
:写缓冲区数据量低于此水位后,写入回调被调用。默认值0,导致仅当缓冲区全部写入到底层传输接口后,才调用写入回调写入高水位
:没有直接使用。当一个bufferevent用作另外一个bufferevent的底层传输接口时有用
函数原型:
void bufferevent_setwatermark(struct bufferevent *bufev.short events,size_t lowmark,size_t highmark)
:设置bufferevent对象的缓冲区水位
1 |
|
建立连接函数
int bufferevent_socket_connect(struct bufferevent *bev,const struct sockaddr *sa,int socklen)
:客户端与服务端建立连接
1 |
|
缓冲区IO操作函数
读缓冲区数据
size_t bufferevent_read(struct bufferevent *bufev,void *data,size_t size)
:从读缓冲区中读指定字节数数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18#include <event2/bufferevent.h>
size_t bufferevent_read(struct bufferevent *bufev,void *data,size_t size);
//参数
bufev:要读取读缓冲区数据的带缓冲区事件对象;
data:传出参数,用于指向存储从读缓冲区中读出来的数据的缓冲区,该存储大小大于size值;
size:表示要读取的数据的最大字节数;
//返回值
>0:实际读取到数据的字节数;
=0:没有数据可读;
-1,errno:读取时发生错误,并设置errno;int bufferevent_read_buffer(struct bufferevent *bufev,struct evbuffer *buf)
:将读缓冲区的所有数据读出1
2
3
4
5
6
7
8
9
10
11
12
13#include <event2/bufferevent.h>
int bufferevent_read_buffer(struct bufferevent *bufev,struct evbuffer *buf);
//参数
bufev:要读取读缓冲区数据的带缓冲区事件对象;
buf:传出参数,用于指向存储从读缓冲区中读出来的数据的缓冲区;
//返回值
成功:0;
失败:-1;
注意:
- 使用 bufferevent_read() 函数只会从套接字缓冲区中读取数据,并不会阻塞等待数据到达。如果没有数据可读,函数将立即返回,因此需要在事件处理器的读回调函数中多次调用该函数,以确保读取所有可用的数据
- 以上两个函数,所读出数据则读缓冲区对应数据移除,也就是读走即没
写缓冲区数据
int bufferevent_write(struct bufferevent *bufev,const void *data,size_t size)
:向写缓冲区末尾写入data缓冲区开头到size字节大小的数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <event2/bufferevent.h>
int bufferevent_write(struct bufferevent *bufev,const void *data,size_t size);
//参数
bufev:要写入数据的写缓冲区的带缓冲区事件对象;
data:指向数据源缓冲区;
size:写入数据大小;
//返回值
成功:0;
失败:-1;int bufferevent_write_buffer(struct bufferevent *bufev,struct evbuffer *buf)
:将data中的所有数据写入到bufev中的写缓冲区末尾1
2
3
4
5
6
7
8
9
10
11
12
13
14
15#include <event2/bufferevent.h>
int bufferevent_write_buffer(struct bufferevent *bufev,struct evbuffer *buf);
//参数
bufev:要写入数据的写缓冲区的带缓冲区事件对象;
data:指向数据源缓冲区;
//返回值
成功:0;
失败:-1;
evconnlistener监听器
监听连接/创建
函数原型
struct evconnlistener_new(struct event_base *base,evconnlistener_cb cb,void *ptr,unsigned flags,int backlog,evutil_socket_t fd)
:创建一个事件监听者对象(监听器),用于监听客户端的建立连接请求,一般直接使用evconnlistener_new_bind函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27#include <event2/listener.h>
struct evconnlistener_new(struct event_base *base,evconnlistener_cb cb,void *ptr,unsigned flags,int backlog,evutil_socket_t fd);
//参数
base:用于监听连接的event_base对象;
cb:当新连接请求时,要调用的回调函数,一般用于与客户端进行通信;
ptr:回调函数参数;
flags:控制函数行为参数,多个参数用"|";
//取值列表
LEV_OPT_CLOSE_ON_FREE:释放bufferevent时,将底层套接字关闭并且释放底层bufferevent等;
LEV_OPT_REUSEABLE:端口复用;
LEV_OPT_LEAVE_SOCKETS_BLOCKING:默认情况下,监听器是非阻塞模式的用于Libevent库所需,该参数可以设置成阻塞;
LEV_OPT_THREADSAFE:为监听器分配锁,用于多线程安全;
LEV_OPT_CLOSE_ON_EXEC:在监听的socket上设置 FD_CLOEXEC 选项;
backlog:建立连接的上限值,传-1表示使用默认最大值128;
fd:监听文件描述符,socket创建出来的文件描述符;
//返回值
返回:事件监听器对象;注意
:这个函数实质是完成了listen()和accept()函数的工作,并没有完成socket()和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)
:创建一个事件监听者对象(监听器),用于监听客户端的建立连接请求,完成了socket、bind、listen、accept函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28#include <event2/listener.h>
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,evconnlistener_cb cb,void *ptr,unsigned flags,int backlog,const struct sockaddr *sa,int socklen);
//参数
base:用于监听连接的event_base对象;
cb:当新连接请求时,要调用的回调函数,一般用于与客户端进行通信;
ptr:回调函数参数;
flags:控制函数行为参数,多个参数用"|";
//取值列表
LEV_OPT_CLOSE_ON_FREE:释放bufferevent时,将底层套接字关闭并且释放底层bufferevent等;
LEV_OPT_REUSEABLE:端口复用;
LEV_OPT_LEAVE_SOCKETS_BLOCKING:默认情况下,监听器是非阻塞模式的用于Libevent库所需,该参数可以设置成阻塞;
LEV_OPT_THREADSAFE:为监听器分配锁,用于多线程安全;
LEV_OPT_CLOSE_ON_EXEC:在监听的socket上设置 FD_CLOEXEC 选项;
backlog:建立连接的上限值,传-1表示使用默认最大值128;
sa:传入参数,服务器IP地址结构;
socklen:服务器IP地址结构大小;
//返回值
返回:事件监听器对象;
监听器回调函数:
typedef void (*evconnlistener_cb)(struct evconnlistener *listener,evutil_socket_t sock,struct sockaddr *addr,int len,void *ptr)
:创建监听器时的回调函数参数原型,用于与客户端通信
1 |
|
设置/调整回调
void evconnlistener_set_cb(struct evconnlistener *lev,evconnlistener_cb cb,void *arg)
:可以重新调整修改监听器的回调函数以及回调函数参数
1 |
|
销毁/释放
void evconnlistener_free(struct evconnlistener *lev)
:释放监监听器对象
1 |
|
启用/禁用
int evconnlistener_enable(struct evconnlistener *lev)
:重新允许监听新连接1
2
3
4
5
6
7
8
9
10
11
12#include <event2/listener.h>
int evconnlistener_enable(struct evconnlistener *lev);
//参数
lev:要允许监听新连接的监听器对象;
//返回值:
成功:0;
失败:-1;int evconnlistener_disable(struct evconnlistener *lev)
:暂时禁止监听器监听新连接1
2
3
4
5
6
7
8
9
10
11
12#include <event2/listener.h>
int evconnlistener_disable(struct evconnlistener *lev);
//参数
lev:要禁止监听新连接的监听器对象;
//返回值:
成功:0;
失败:-1;
异常处理
void evconnlistener_set_error_cb(struct evconnlistener *lev,evconnlistener_errorcb errorcb)
:可以给监听器设置一个错误捕捉的回调函数,当监听器错误产生时就调用该回调函数
1 |
|
错误回调函数:
1 |
|
TCP服务器
server
具体步骤:
创建event_base,使用event_base_new
创建一个服务器连接监听器,并设置其回调函数,当有客户端成功连接时,回调函数被调用,使用evconnlistener_new_bind
封装连接监听器的回调函数,该回调函数完成以下操作
- 创建bufferevent事件对象,使用bufferevent_socket_new
- 使用bufferevent_setcb给read、write、event事件设置回调函数,并添加到event_base中
- 设置bufferevent的读写缓冲区启用/禁用
- 当监听的事件满足时,调用对应事件的回调函数
使用bufferevent_read/bufferevent_write进行接收/发送数据
启动循环监听,使用event_base_dispatch
释放连接
代码:
1 |
|
client
具体步骤:
- 创建event_base,使用event_base_new
- 使用bufferevent_socket_new创建一个跟服务器通信的bufferevent事件对象
- 使用bufferevent_socket_connect连接服务器
- 使用bufferevent_setcb设置read、write、event设置回调
- 启用/禁用bufferevent对象的读写缓冲区
- 使用bufferevent_read/bufferevent_write进行接收/发送数据
- 启动循环监听,使用event_base_dispatch
- 释放资源
代码:
1 |
|