1.事件和集合
libvent的核心是个集合,把所有的需要监听的事件放在集合中,如果有满足条件的事件,就会触发回调函数去处理!
画个图,先搞清楚TCP的C/S架构
图中有三个文件描述符,sockfd和fd1、fd2。
三个文件描述符可以分为两类:
socfd用于监听是否有客户端发起连接请求,fd1和fd2用于监听客户端是否向服务器发送数据。
每一个文件描述符可以理解成一个事件。事实上,libevent就是把文件描述符封装成了结构体bufferevent。把三个bufferevent同时放入集合中监听,就成了高并发服务器的雏形!
2.使用过程
(1)创建集合
struct event_base *event_base_new();
这行代码非常简单,创建一个空的集合。没有集合,libevent啥也不是。
用完之后,需要手动释放。
void event_base_free(struct event_base *);
(2)创建监听对象
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
const struct sockaddr *sa, int socklen);
/*智能音箱原始代码*/
PlayerServer::PlayerServer(const char *ip, int port)
{
base = event_base_new(); //创建事件集合
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ip);
listener = evconnlistener_new_bind(base, listener_cb, base,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 10, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (NULL == listener)
{
std::cout << "evconnlistener_new_bind error" << std::endl;
}
event_base_dispatch(base); //监听集合
}
可以理解成,这一函数完成了socket、bind、listen、accept四个函数功能,并且把得到的sockfd(监听对象)添加到集合中。
参数的意义:
base:刚才创建的集合。
cb:回调函数。一旦有客户端连接服务器,就会触发该回调函数。
ptr:给回调函数传的参数。
flags:LEV_OPT_开头的宏定义,指定了监听对象的属性,比如释放监听对象的时候关闭socket连接、地址可以复用、多线程安全等等。
backlog:监听队列的大小。
sa:存放服务器的信息,用于绑定使用。
socklen:结构体的长度。
其中回调函数原型如下:
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
/*智能音箱回调函数原始代码*/
void PlayerServer::listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
struct event_base *base = (struct event_base *)arg;
std::cout << "有客户端连接 " << fd << std::endl;
//创建bufferevent事件
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (NULL == bev)
{
std::cout << "bufferevent_socket_new error" << std::endl;
}
bufferevent_setcb(bev, read_cb, NULL, event_cb, base);
bufferevent_enable(bev, EV_READ);
}
回调函数里面主要用于创建bufferevent对象,可以理解为一个bufferevent对应一个客户端。代码如下:
void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
struct event_base *base = arg;
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (NULL == bev)
{
printf("bufferevent_socket_new error!\n");
exit(1);
}
bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL);
bufferevent_enable(bev, EV_READ);
}
(3)开始监听
int event_base_dispatch(struct event_base *);
dispatch函数可以理解成一个循环,负责监听集合中的事件。如果没有这个函数,主函数会立即返回。
(4)设置回调函数
libevent里面用到的回调函数的地方特别多。
有客户连接请求,会触发回调函数;客户端发送数据会触发回调函数;客户端有异常时会触发回调函数。
对bufferevent事件设置回调函数需要用到两个函数:
//设置回调函数
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg);
//使能回调函数
int bufferevent_enable(struct bufferevent *bufev, short event);
其中,readcb最常使用,一旦有客户端发送数据,就会触发readcb回调函数。在智能音箱项目中,我们使用了两个回调函数,分别是服务器收到客户端消息时的回调函数readcb、客户端异常时回调函数event_cb;
(5)读取数据
libevent封装了两个函数分别用于读取和发送。
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);