目录
- 参考
- 什么是libevent
- 应用
- 核心实现
- libevent的地基event_base
- 等待事件产生,循环监听event_loop
- 退出循环监听event_base_loopexit
- 创建事件
- 工作流程
- 安装一(源码安装,推荐)
- 现在源码
- 配置
- 编译
- 安装
- 验证安装
- 安装二(可能因为openssl报错)
- 参考
- 下载安装包
- 配置
- 编译
- 安装
- 测试libevent是否安装成功:
- samples
- 测试method.c
- 代码
- 编译执行
- 服务端程序
参考
视频教程
libevent的基本使用
什么是libevent
Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。
应用
Chromium、Memcached、NTP、HTTPSQS等著名的开源程序都使用libevent库,足见libevent的稳定。更多使用libevent的程序可以到libevent的官网查看。
核心实现
Reactor(反应堆)模式是libevent的核心框架,libevent以事件驱动,自动触发回调功能。之前介绍的epoll反应堆源码,就是从libevent中抽取出来的。
libevent的地基event_base
在使用libevent这个库的时候,就像我们盖房子一样,需要一个地基,我们这个接口就是来完成这个工作的,在他的基础上会有的一个事件集合去检查哪一个事件是激活的
// 通过以下函数获得event_base结构
struct event_base * event_base_new(void);
// 申请到的指针可以通过event_base_free释放
event_base_free(struct event_base *)
// 如果fork出子进程,想在子进程继续使用event_base,那么子进程需要对event_base重新初始化,函数如下:
int event_reinit(struct event_base *base);
等待事件产生,循环监听event_loop
他类似于while(1)的功能,去循环监视事件的发生
int event_base_dispatch(struct event_base *base);
调用该函数(相当于while(1) {epoll_wait}),相当于没有设置标志位的event_base_loop。程序将会一直运行,直到没有需要检测的事件了,或者被结束循环的api终止。
int event_base_dispatch(struct event_base *base);
调用该函数,相当于没有设置标志位的event_base_loop。程序将会一直运行,直到
没有需要检测的事件了,或者被结束循环的api终止。
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
struct timeval {
long tv_sec;
long tv_usec;
两个函数的区别是如果正在执行激活事件的回调函数,那么event_base_loopexit
将在事件回调执行结束后终止循环(如果tv时间非NULL,那么将等待tv设置的时间
后立即结束循环),而event_base_loopbreak会立即终止循环。
退出循环监听event_base_loopexit
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
struct timeval {
long tv_sec;
long tv_usec;
两个函数的区别是如果正在执行激活事件的回调函数,那么event_base_loopexit
将在事件回调执行结束后终止循环(如果tv时间非NULL,那么将等待tv设置的时间
后立即结束循环),而event_base_loopbreak会立即终止循环。
创建事件
struct event *event_new(struct event_base *base, evutil_socket_t fd,
short events, event_callback_fn cb, void *arg);
event_new负责新创建event结构指针,同时指定对应的地基base,还有对应的文件描述符,
事件,以及回调函数和回调函数的参数。参数说明:
base 对应的根节点
fd 要监听的文件描述符
events 要监听的事件
#define EV_TIMEOUT 0x01 //超时事件
#define EV_READ 0x02 //读事件
#define EV_WRITE 0x04 //写事件
#define EV_SIGNAL 0x08 //信号事件
#define EV_PERSIST 0x10 //周期性触发
#define EV_ET 0x20 //边缘触发,如果底层模型支持
cb 回调函数,原型如下:
typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg);
arg 回调函数的参数
7. int event_add(struct event *ev, const struct timeval *timeout);
将非未决态事件转为未决态,相当于调用epoll_ctl函数,开始监听事件是否产生。
参数说明:
Ev 就是前面event_new创建的事件
Timeout 限时等待事件的产生,也可以设置为NULL,没有限时。
8. int event_del(struct event *ev);
将事件从未决态变为非未决态,相当于epoll的下树(epoll_ctl调用EPOLL_CTL_DEL操作)操作。
9. void event_free(struct event *ev);
释放event_new申请的event节点。
工作流程
安装一(源码安装,推荐)
现在源码
cd /usr/local/clib/libevent/
git clone https://github.com/nmathewson/Libevent.git
配置
sh autogen.sh
./configure --prefix=/usr/local/clib/libevent/libevent-2.0.12
编译
make -j4
安装
make install
验证安装
make verify //验证安装
安装二(可能因为openssl报错)
参考
libevent下载与安装学习整理
下载安装包
官网:http://www.monkey.org/~provos…
下载:libevent-2.1.12-stable.tar.gz
解压
cd /usr/local/clib/libevent
# 下载
wget https://www.monkey.org/~provos/libevent-2.0.12-stable.tar.gz
tar zxvf libevent-2.0.12-stable.tar.gz
#
配置
./configure --prefix=/usr/local/clib/libevent/libevent-2.0.12 --with-openssl=/usr/local/clib/openssl/1.1.1o
编译
make -j4
安装
make install
测试libevent是否安装成功:
# ls -al /usr/lib | grep libevent
samples
cd /usr/local/clib/libevent/Libevent/sample
测试method.c
打印支持的方法
代码
#include<event.h>
#include<stdio.h>
int main()
{
// 获取libevent后端支持的方法
const char **methods = event_get_supported_methods();
for(int i = 0;methods[i] != NULL;i++){
printf("method:%s\n", methods[i]);
}
struct event_base* base = event_base_new();
printf("%s\n", event_base_get_method(base));
return 0;
}
编译执行
# 编译
gcc -I/usr/local/clib/libevent/libevent-2.0.12/include -L/usr/local/clib/libevent/libevent-2.0.12/lib hello-world.c -levent
# 运行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/clib/libevent/libevent-2.0.12/lib
./a.out
服务端程序
#include<event.h>
#include <sys/socket.h> // socket依赖
#include <arpa/inet.h> // socket依赖
#include<stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h> // close依赖
#include <errno.h>
#include <string.h>
#define MAX_LISTEN_SOCKET 1 /*监听上限*/
#define MAX_LISTEN_EVENTS 100 /*监听上限*/
#define SOCKET_PORT 8000 /*端口号*/
#define BUF_SIZE 1024 /*缓存区大小*/
#define READ_BUF_SIZE 4 /*缓存区大小*/
typedef struct T_EVENT
{
int fd; // 要监听的文件描述符
struct event_base * base; // 对应的监听事件,EPOLLIN和EPLLOUT
struct event *ev;
long last_active; // 记录每次加入红黑树 g_efd 的时间值
}T_EVENT, *PT_EVENT;
/**
* @brief
* 客户端事件
* @param fd
* @param event
* @param arg
*/
void clientFun(int fd, short events, void* arg){
// 参数转换
PT_EVENT e = (PT_EVENT)arg;
struct event_base * base = e->base;
int len = -1;
// 开始循环度
int buf_len = 0;
char buf[BUF_SIZE] = {0};
struct event *ev = e->ev;
// 先下树
event_del(ev);
int is_close = 0;
while (1)
{
len = recv(fd, buf+buf_len, READ_BUF_SIZE, 0); // 读取客户端发过来的数据
if (len > 0)
{
buf_len += len;
printf("C[%d] say: len:%d, content:%s\n", fd, len,buf);
}
else if (len == 0)
{
is_close = 1;
printf("[fd=%d] closed\n", fd);
break;
}
else
{
// 如果是读缓冲区读干净了,这个时候应该跳出while循环
if (errno == EAGAIN)
{
break;
}
is_close = 1;
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
break;
}
}
printf("客户端已经循环读完,is_close:%d\n",is_close);
if(is_close){
close(fd);
// 释放
free(e);
}else{
len = send(fd, buf, buf_len, 0); // 直接将数据回射给客户端
// 再次上树
event_add(ev, NULL);
}
}
void serverFun(int fd, short event, void* arg){
// 参数转换
struct event_base * base = (struct event_base *)arg;
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
// 提取新的cfd
int cfd = -1;
if ((cfd = accept(fd, (struct sockaddr *)&cin, &len)) == -1)
{
if (errno != EAGAIN && errno != EINTR)
{
sleep(1);
}
printf("%s:accept,%s\n", __func__, strerror(errno));
return;
}
printf("new connect[%s:%d]\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
int flag = 0;
// 设置客户端为非阻塞
int flags = fcntl(cfd, F_GETFL); // 获取的cfd的标志位
flags |= O_NONBLOCK;
if ((flag = fcntl(cfd, F_SETFL, flags)) < 0) // 将cfd也设置为非阻塞
{
printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
}
// 初始化上树节点和事件
PT_EVENT e = malloc(sizeof(T_EVENT));
e->fd = cfd;
e->base = base;
struct event *ev = event_new(base, cfd, EV_READ | EV_ET | EV_PERSIST, clientFun, e);
e->ev = ev;
// 上树
event_add(ev, NULL);
}
int main()
{
int sockfd = 0;
int ret = -1;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
printf("监听套接字文件描述符:%d\n", sockfd);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(SOCKET_PORT);
// addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_addr.s_addr = htons(INADDR_ANY);
// 一般在一个端口释放后需要等一段时间才能重新启用,因此需要借助SO_REUSEADDR来使端口重新启用。解决服务端异常退出之后,再次启动服务端,客户端无法使用同一个端口连接socket的问题
// 设置端口复用
int opt = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (ret == -1)
{
perror("设置端口复用失败!\n");
return -1;
}
ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
if (ret == -1)
{
perror("绑定失败!\n");
return -1;
}
ret = listen(sockfd, 5);
if (ret == -1)
{
perror("监听失败!\n");
return -1;
}
// 注册event_base根节点,相当于epoll_craete
struct event_base * base = event_base_new();
// 初始化sockfd上树节点
struct event *ev = event_new(base, sockfd, EV_READ | EV_ET | EV_PERSIST, serverFun, base);
// 上树
event_add(ev, NULL);
// 循环监听, 阻塞
event_base_dispatch(base);
// 收尾
close(sockfd);
event_base_free(base);
}