点击上方【蓝字】关注博主
“ 本节将深入探讨另一个强大的工具——开源库 `libevent`,它可以简化服务器程序的开发过程,让我们更专注于业务逻辑处理。我们将从 `libevent` 的介绍开始,详细分析其内部结构和封装层级,并通过一个完整示例代码展示如何利用 `libevent` 来构建高并发的 TCP 服务器程序。 ”
简介
libevent简介
wget https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz
tar -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
3.2、事件对象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_ | 具体的信号。 |
3.3、struct bufferevent对象
变量 | 含义 |
---|---|
ev_base | 事件对象所属的reactor的对象。 |
be_ops | bufferevent的具体操作。控制某个事件的打开、关闭、移除等,其中input是用户态读缓冲区,output是用户态写缓冲区 |
readcb | 读事件的回调函数 |
writecb | 注意不是写事件回调,而是低水平触发的回调函数。这是涉及到写失败时的处理,内部会处理写事件发送出去。通常不需要设置写回调函数。 |
errorcb | 所有错误事件的回调函数。被动关闭连接或其他异常的回调函数。 |
wm_read | 读水平线,里面分有高水平和低水平。低水平是指buffer中有多少数据就要触发回调,默认为0,即每次读事件都会触发回调;高水平是指缓冲区中达到多大的数据就要关闭读事件,即buffer数据比较多的时候不再处理读事件。 |
wm_write | 写水平线,写只有低水平没有高水平。低水平默认值是0,即用户态缓冲区为空时回调写回调函数。 |
变量 | 含义 |
---|---|
input | 用户态读缓冲区。 |
output | 用户态写缓冲区。 |
3.4、evconnlistener对象
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;
};
3.5、事件循环
3.6、事件处理
void
bufferevent_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 <event.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <sys/socket.h>
#include <functional>
#include <cstring>
#include <stdlib.h>
#define SOCKET_LISTEN_PORT 9703
#define SOCKET_BACKLOG_NUM 128
class 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);//写事件回调一般为NULL
bufferevent_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;
}
gcc -o ev ev.c -levent
error while loading shared libraries: libevent-2.1.so.7: cannot open shared object file: No such file or directory
将该路径放在系统查找路径内。这种方法仅永久有效。
sudo echo "/usr/local/lib" >> /etc/ld.so.conf
sudo ldconfig添加环境变量的方法,添加 export LD_LIBRARY_PATH=XXX。这种方法仅当前有效。
export LD_LIBRARY_PATH=/usr/local/lib/
总结
有了libevent可以不使用IO函数。因为如果使用IO函数,既需要知道这些IO函数里面的系统调用返回值的含义。
有了libevent就可以不清楚数据拷贝原理。
有了libevent就可以不清楚网络原理以及网络编程流程。
有了libevent只需要知道事件处理,IO操作完全交由libevent处理。
至此,我们实现了使用libevent库开发高并发的服务器程序,但是,这个服务器程序有些局限性,我们还要继续改善、优化。在改进之前,需要开发一个后台日志模块,这是服务器程序必须的,所有,下一个章节将开发一个高效的后台日志模块。
公众号: Lion 莱恩呀
微信号: 关注获取
扫码关注 了解更多内容
点个 在看 你最好看