高并发网络编程libevent
1. libevent库介绍
1.1 什么是libevent
libevent也称为事件通知库,即所见皆事件,是一个用C语言实现的、基于事件驱动(event-driven)的轻量级高性能开源网络库,适用于Windows、Linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。libevent不一定是用到网络当中,本地的文件描述符都可以用。
1.2 libevent特点
事件驱动(event-driven),高性能
轻量级,专注于网络,不如 NGINX 那么臃肿庞大
源代码相当精炼、易读
跨平台,支持 Windows、Linux、*BSD和Mac OS,但Windows支持不怎么好
支持多种I/O多路复用技术,select、epoll、poll、dev/poll、select、kqueue、evports等
支持I/O,定时器和信号等事件
采用Reactor设计模式
支持HTTP(S),DNS解析
libevent是用于编写高速可移植非阻塞IO应用的库,其设计目标是:可移植性、高性能、便携和可扩展性
2. libevent框架
2.1 步骤流程
1.创建event_base
struct event_base *event_base_new(void);struct event_base *base = event_base_new();
2.创建事件event
常规事件event:
event_new();bufferevent事件:
bufferevent_socket_new();
3.将事件添加到base上
int event_add(struct event *ev, const struct timeval *tv);
4.循环监听事件满足
int event_base_dispatch(struct event_base *base);
5.释放event_base
event_base_free(base);
2.2 event事件
1.struct event *event_new(struct event_base *base, evutil_socket_t fd, short what, event_callback_fn cb, void *arg);
参1:event_base_new()返回值
参2:绑定到event上的文件描述符,也是回调函数参数
参3:对应的事件(r,w,e),也是回调函数参数
- EV_READ:一次读事件
- EV_WRITE:一次写事件
- EV_PERSIST: 持续触发,结合event_base_dispatch函数使用
参4:一旦事件满足监听条件,就回调的函数
typedef void(*event_callback_fn)(evutil_socket_t fd, short, void*);
参5:回调函数的参数;
- 返回值:成功创建的event;
2.添加事件到event_base
int event_add(struct event *ev, const struct timeval *tv);
参1:event_new()的返回值
参2:NULL
3.从event_base上摘下事件[了解即可]
int event_del(struct event *ev);
- 参数:event_new()的返回值
4.销毁事件
int event_free(struct event *ev);
- 参数:event_new()的返回值
注意:使用到libevent库的事件,编译时后面要加-levent
补:常规事件event的过程状态
未决:有资格被处理,但还没有被处理(事件没有到来)
非未决:没有资格被处理
案例:通过event事件对管道写内容
1 | void write_cb(evutil_socket_t fd, short what, void *arg){ |
案例:通过event事件对管道读内容
1 | void read_cb(evutil_socket_t fd, short what, void *arg){ |
2.3 bufferevent缓冲事件
原理:bufferevent有两个缓冲区,也是队列实现,读走就没了,先进先出
读过程:有数据—–>读回调函数被调用—–>使用bufferevent_read()—–>有数据。
写过程:使用bufferevent_write()—–>向写缓冲中写数据—–>该缓冲区有数据就自动写出—–>写完,回调函数被调用(鸡肋)
1.创建bufferevent事件
struct bufferevenet *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);
参1:event_base
参2:封装到bufferevent内的fd
参3:BEV_OPT_CLOSE_ON_FREE—>释放bufferevent时关闭底层传输端口,这将关闭底层套接字(网络套接字),释放底层bufferevent等
返回值:成功创建的bufferevent事件对象
2.销毁bufferevent
void bufferevent_socket_free(struct bufferevent *ev);
3.给bufferevent设置回调
- 区别:event事件设置回调 event_new(fd,callback),一个函数就可以,而bufferevent事件设置回调则麻烦一点
bufferevent_socket_new(fd); bufferevent_setcb(callback);
4.void bufferevent_setcb(struct bufferevent* bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);
参1:bufferevent_socket_new()返回值
参2:设置bufferevent读缓冲,对应回调read_cb{bufferevent_read()读数据}
参3:设置bufferevent写缓冲,对应回调write_cb{ }–>给调用者,发送写成功通知,可以写NULL
参4:设置事件回调,也可以传NULL
参5:上述回调函数使用的参数
5.启动、关闭bufferevent的缓冲区
void bufferevent_enbale(struct bufferevent *bufev, short events);
- events:EV_READ、EV_WRITE、EV_READ|EV_WRITE
注意:默认write缓冲是enable、read缓冲是disable,所以需要开启都缓冲
6.客户端实现
socket(); connect();
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
参1:bufferevent事件对象(封装了fd)
参2和参3:等同于connect()的参2和参3
7.服务端实现
socket(); bind(); listen(); accept();
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:event_base
参2:回调函数,一旦被回调,说明在其内部应该与客户端完成,数据读写操作,进行通信。
参3:回调函数的参数
参4:LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE
参5:listen的参2,-1表示最大值
参6:服务器自身的地址结构体
参7服务器自己的地址结构体大小
返回值:成功创建的监听器
8.bufferevent的读事件回调触发时机:
当数据由内核的读缓冲区到bufferevent的读缓冲区的时候,会触发bufferevent()的读事件回调。需要注意的是,数据由内核到bufferevent的过程不是由用户程序执行的,是由bufferevent内部操作的
被触发的回调函数是自己写的,可以在里面读bufferevent的数据了,也可以向bufferevent写缓冲区写数据
9.bufferevent的写事件回调触发时机:
当用户程序将数据写到bufferevent的写缓冲区之后,bufferevent会自动将数据写到内核的写缓冲区,最终由内核程序将数据发送出去。此时回调函数起到的是通知作用,向我们传达数据已经发送出去的信息。
案例:用bufferevent来实现网络通信(服务端)
1 | //读缓冲区回调--->当数据从内核缓冲区到bufferevent的读缓冲区时,触发该事件 |
案例:用bufferevent来实现网络通信(客户端)
1 | //读缓冲区回调--->当数据从内核缓冲区到bufferevent的读缓冲区时,触发该事件 |
在上面代码中,需要注意的是,客户端在创建了bufferevent事件后,只是将其的读写缓冲区与内核的作了联系,若后面不创建event事件来主动向服务端发信息,将运行不起来程序,因为服务器和客户端的读缓冲区被激活的前提是有信息到自己的bufferevent读缓冲区,但没有event事件让客户端从终端的输入信息发送到对端,这个程序就不会执行下去。














