C++使用libevent库开发服务器程序
- 一、引言
- 二、libevent简介
- 三、Libevent库的封装层级
- 3.1、reactor对象封装struct event_base
- 3.2、事件对象struct event
- 3.3、struct bufferevent对象
- 3.4、evconnlistener对象
- 3.5、事件循环
- 3.6、事件处理
- 四、完整示例代码
- 小结
一、引言
手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。
为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。
在上一章节介绍了如何使用epoll构建reactor网络模型开发高效的服务器,有了上一节的基础,本节将介绍使用开源库libevent进行开发服务器程序。
二、libevent简介
libevent是一个事件通知库,封装了reactor。
libevent API 提供了一种机制,用于在文件描述符上发生特定事件或达到超时后执行回调函数。此外,libevent还支持由于信号或常规超时而导致的回调。
libevent 旨在替换在事件驱动的网络服务器中找到的事件循环。应用程序只需要调用event_dispatch(),然后动态添加或删除事件,而无需更改事件循环。
目前,该控件支持/dev/poll, kqueue(), event ports, POSIX select(), Windows select(), poll(), and epoll()。内部事件机制完全独立于公开的事件 API,并且 libevent 的简单更新可以提供新功能,而无需重新设计应用程序。因此,Libevent 允许可移植应用程序开发,并提供操作系统上可用的最具可扩展性的事件通知机制。libevent 还可用于多线程应用程序,方法是隔离每个event_base,以便只有单个线程访问它,或者锁定对单个共享event_base的访问。自由的在 Linux、*BSD、Mac OS X、Solaris、Windows 等设备上编译。
libevent 还为缓冲网络 IO 提供了一个复杂的框架,支持套接字、筛选器、速率限制、SSL、零副本文件传输和 IOCP。自由度包括对几种有用协议的支持,包括 DNS、HTTP 和最小的 RPC 框架。
libevent编译安装:
官网下载安装包并解压。进入解压目录执行:
wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gztar -zxvf libevent-2.1.12-stable.tar.gz cd libevent-2.1.12-stable./configure make sudo make install
三、Libevent库的封装层级
3.1、reactor对象封装struct event_base
reactor对象封装为struct event_base;通过:
(1)event_base_new()构造对象。
(2)event_base_free()销毁对象。
3.2、事件对象struct event
事件对象通过struct event的结构体封装使用。
struct event {struct event_callback ev_evcallback;/* for managing timeouts */union {TAILQ_ENTRY(event) ev_next_with_common_timeout;int min_heap_idx;} ev_timeout_pos;evutil_socket_t ev_fd;struct event_base *ev_base;union {/* used for io events */struct {LIST_ENTRY (event) ev_io_next;struct timeval ev_timeout;} ev_io;/* used by signal events */struct {LIST_ENTRY (event) ev_signal_next;short ev_ncalls;/* Allows deletes in callback */short *ev_pncalls;} ev_signal;} ev_;short ev_events;short ev_res;/* result passed to event callback */struct timeval ev_timeout;};
变量 | 含义 |
---|---|
ev_evcallback | 回调函数。事件是异步处理的,需要回调函数。 |
min_heap_idx | 时间事件的最小堆的索引。 |
ev_fd | 定时事件的fd。 |
ev_base | 事件对象所属的reactor的对象。 |
ev_io | 网络事件关注的事情。 |
ev_signal | 信号事件关注的事情。 |
ev_timeout | 超时。 |
ev_timeout_pos和ev_fd | 定时任务处理的事情。 |
ev_events | 具体注册的事件。 |
ev_ | 具体的信号。 |
通常,event对象可以自己处理IO。
(1)event_new():构建事件对象、绑定、事件回调。
(2)event_free():销毁事件对象。
bufferevent和evconnlistener对象只需要关注业务逻辑的处理,由libevent内部处理IO操作。
bufferevent是在event对象上面封装的缓冲区。
3.3、struct bufferevent对象
struct bufferevent中的重要成员变量:
变量 | 含义 |
---|---|
ev_base | 事件对象所属的reactor的对象。 |
be_ops | bufferevent的具体操作。控制某个事件的打开、关闭、移除等,其中input是用户态读缓冲区,output是用户态写缓冲区 |
readcb | 读事件的回调函数 |
writecb | 注意不是写事件回调,而是低水平触发的回调函数。这是涉及到写失败时的处理,内部会处理写事件发送出去。通常不需要设置写回调函数。 |
errorcb | 所有错误事件的回调函数。被动关闭连接或其他异常的回调函数。 |
wm_read | 读水平线,里面分有高水平和低水平。低水平是指buffer中有多少数据就要触发回调,默认为0,即每次读事件都会触发回调;高水平是指缓冲区中达到多大的数据就要关闭读事件,即buffer数据比较多的时候不再处理读事件。 |
wm_write | 写水平线,写只有低水平没有高水平。低水平默认值是0,即用户态缓冲区为空时回调写回调函数。 |
struct bufferevent_ops中的重要成员变量:
变量 | 含义 |
---|---|
input | 用户态读缓冲区。 |
output | 用户态写缓冲区。 |
(1)bufferevent_socket_new():构建bufferevent对象。
(2)bufferevent_free():销毁bufferevent对象。
3.4、evconnlistener对象
evconnlistener是专门处理listenfd的对象,使我们不需要关注bind、listen、accept的具体操作。
struct evconnlistener_ops {int (*enable)(struct evconnlistener *);int (*disable)(struct evconnlistener *);void (*destroy)(struct evconnlistener *);void (*shutdown)(struct evconnlistener *);evutil_socket_t (*getfd)(struct evconnlistener *);struct event_base *(*getbase)(struct evconnlistener *);};struct evconnlistener {const struct evconnlistener_ops *ops;void *lock;evconnlistener_cb cb;evconnlistener_errorcb errorcb;void *user_data;unsigned flags;short refcnt;int accept4_flags;unsigned enabled : 1;};
(1)evconnlistener_new():构建evconnlistener对象、绑定、事件回调。
(2)evconnlistener_free():销毁evconnlistener对象。
(3)evconnlistener_bind_new():创建listenfd、bind、listen、注册读事件。
3.5、事件循环
(1)事件循环:event_base_dispatch(),event_base_loop()。
(2)事件循环退出:event_base_loopexit(),event_base_break()。
3.6、事件处理
设置事件相对应的回调。
(1)如果是使用event对象,在event_new()会设置相对应的回调。
(2)如果IO由libevent处理,那么使用bufferevent_setcb()来设置回调。
voidbufferevent_setcb(struct bufferevent *bufev,bufferevent_data_cb readcb, bufferevent_data_cb writecb,bufferevent_event_cb eventcb, void *cbarg){BEV_LOCK(bufev);bufev->readcb = readcb;bufev->writecb = writecb;bufev->errorcb = eventcb;bufev->cbarg = cbarg;BEV_UNLOCK(bufev);}
四、完整示例代码
#include #include #include #include #include #include #include #define SOCKET_LISTEN_PORT9703#define SOCKET_BACKLOG_NUM128class asyn_event{private:/* data */struct event_base *base;struct evconnlistener *listener;public:asyn_event(/* args */);~asyn_event();static void accept_cb(struct evconnlistener *listen,evutil_socket_t fd,struct sockaddr *sock,int socklen,void *arg);static void socket_event_callback(struct bufferevent *bev, short events, void *arg);static void socket_read_callback(struct bufferevent *bev, void *arg);void loop_run();};asyn_event::asyn_event(/* args */){base=event_base_new();struct sockaddr_in server={0};server.sin_family=AF_INET;server.sin_addr.s_addr=htonl(INADDR_ANY);server.sin_port=htons(SOCKET_LISTEN_PORT);listener=evconnlistener_new_bind(base,&asyn_event::accept_cb,base,LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE,SOCKET_BACKLOG_NUM,(struct sockaddr*)&server,sizeof(server));}asyn_event::~asyn_event(){// 销毁evconnlistener对象evconnlistener_free(listener);// 销毁事件对象event_base_free(base);}void asyn_event::accept_cb(struct evconnlistener *listen,evutil_socket_t fd,struct sockaddr *sock,int socklen,void *arg){struct event_base *base = (struct event_base *)arg;// 连接的建立---接收连接char ip[32] = { 0 };evutil_inet_ntop(AF_INET, sock, ip, sizeof(ip) - 1);printf("accept a client fd:%d, ip:%s\n", fd, ip);struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);// 注册读事件bufferevent_setcb(bev, socket_read_callback, NULL, socket_event_callback, NULL);//写事件回调一般为NULLbufferevent_enable(bev, EV_READ | EV_PERSIST);}void asyn_event::loop_run(){// 事件循环event_base_dispatch(base);}// 处理连接断开void asyn_event::socket_event_callback(struct bufferevent *bev, short events, void *arg){if (events &BEV_EVENT_EOF)//read=0{printf("connection closed\n");}else if (events & BEV_EVENT_ERROR)//strerro(errno){printf("some other error\n");}else if (events &BEV_EVENT_TIMEOUT)printf("time out\n");bufferevent_free(bev);// close(fd)}// 读回调void asyn_event::socket_read_callback(struct bufferevent *bev, void *arg){struct evbuffer *input = bufferevent_get_input(bev);struct evbuffer *output = bufferevent_get_output(bev);// 从输入缓冲区读取数据char buf[1024];size_t len;while ((len = evbuffer_remove(input, buf, sizeof(buf))) > 0) {//printf("Received: %.*s", (int)len, buf);evbuffer_add(output, buf, len);}//bufferevent_write(bev, reply, strlen(reply));}int main(){asyn_event ev;ev.loop_run();return 0;}
编译时要指定事件库,添加 -levent 参数。
gcc -o ev ev.c-levent
运行时出现libevent-2.1.so.7。
error while loading shared libraries: libevent-2.1.so.7: cannot open shared object file: No such file or directory
产生原因:libevent动态库在默认安装时,存放的路径在/usr/local/lib下,不在系统的默认查找路径内。
解决办法有两个:
(1)将该路径放在系统查找路径内。这种方法仅永久有效。
sudo echo "/usr/local/lib" >> /etc/ld.so.confsudo ldconfig
(2)添加环境变量的方法,添加 export LD_LIBRARY_PATH=XXX。这种方法仅当前有效。
export LD_LIBRARY_PATH=/usr/local/lib/
小结
(1)有了libevent可以不使用IO函数。因为如果使用IO函数,既需要知道这些IO函数里面的系统调用返回值的含义。
(2)有了libevent就可以不清楚数据拷贝原理。
(3)有了libevent就可以不清楚网络原理以及网络编程流程。
(4)有了libevent只需要知道事件处理,IO操作完全交由libevent处理。
至此,我们实现了使用libevent库开发高并发的服务器程序,但是,这个服务器程序有些局限性,我们还要继续改善、优化。在改进之前,需要开发一个后台日志模块,这是服务器程序必须的,所有,下一个章节将开发一个高效的后台日志模块。