Libevent学习

news/2024/10/23 12:30:46/

一、Libevent概述

1、简介

  • 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等等。

2、安装

  • 官网:官网连接
    源码安装:libevent-2.1.11-stable.tar.gz

  • Ubuntu安装:apt-get install libevent-dev

二、两个重要的结构体

1、struct event

2、struct event_base

在这里插入图片描述

三、libevent常用接口

1、event_init( )函数:初始化事件集合

(1)函数原型

原型:struct event_base *event_init(void)

(2)函数实现

struct event_base *event_init(void)
{struct event_base *base = event_base_new_with_config(NULL);if (base == NULL) {event_errx(1, "%s: Unable to construct event_base", __func__);return NULL;}current_base = base;return (base);
}

(3)函数作用

初始化事件集合,其实就是调用了event_base_new_with_config( )函数,创建event_base对象,并且赋值给了全局变量struct evdns_base *current_base

2、event_set( )函数:初始化事件

(1) 函数原型

原型:void event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg);
参数:
1)事件
2)关联的文件描述符
3)事件类型
4)回调函数
5)回调函数的参数

(2)函数实现:

void event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
{int r;r = event_assign(ev, current_base, fd, events, callback, arg);EVUTIL_ASSERT(r == 0);
}

(3)函数作用:

初始化event事件(其实就是给结构体ev的成员赋值)

  • fd表示事件对应的文件描述符,
  • events表示事件的类型,
  • callback是回调函数(即当fd满足条件时调用该函数),
  • arg表示给回调函数传递的参数。

3、event_add( )函数:把事件添加到集合

(1)函数原型

原型:int event_add(struct event *ev, const struct timeval *tv);

(2)函数实现

int event_add(struct event *ev, const struct timeval *tv)
{int res;if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {event_warnx("%s: event has no event_base set.", __func__);return -1;}EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);res = event_add_nolock_(ev, tv, 0);EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);return (res);
}

(3)函数作用

  • 1.将event注册到event_base的I/O多路复用要监听的事件中;
  • 2.将event注册到event_base的已注册事件链表中;
  • 3.如果传入了超时时间,则删除旧的超时时间,重新设置,并将event添加到event_base的小根堆中;
  • 如果没有传入超时时间,则不会添加到小根堆中。
  • 只有步骤1成功,才会执行步骤2和3;否则什么都没做,直接返回,保证不会改变event的状态。

4、event_dispatch( )函数:监听事件

这个函数会让程序陷入死循环, 如果集合中没有事件可以监听,则返回

例子1:IO事件

  • 1-fifo.c
#include <stdio.h>                                                                    
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <event.h>
#include <fcntl.h>
/*当监听的事件满足条件的时候,会触发回调函数,通过回调函数读取数据*/
void fifo_read(evutil_socket_t fd, short events, void *arg) {char buf[32] = {0};int ret = read(fd, buf, sizeof(buf));if (-1 == ret) {perror("read");exit(1);}   printf("从管道读取: %s\n", buf);
}int main() {int ret = mkfifo("fifo.tmp", 00700);if (-1 == ret) {perror("mkfifo");exit(1);}int fd = open("fifo.tmp", O_RDONLY);if (-1 == fd) {perror("open");exit(1);}// 创建事件struct event ev;// 初始化事件集合event_init();// 初始化事件(把fd和事件ev绑定)// 参数:事件、关联的文件描述符、事件类型、回调函数、回调函数参数event_set(&ev, fd, EV_READ | EV_PERSIST, fifo_read, NULL);//把事件添加到集合event_add(&ev, NULL);//开始监吿event_dispatch();  //死循玿 如果集合中没有事件可以监听,则返囿exit(0);
} 
  • 1-write_fifo.c
#include <stdio.h>                                                                    
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd = open("fifo.tmp", O_WRONLY);if (-1 == fd) {   perror("open");exit(2);}   char buf[128] = {0};while (1){scanf("%s", buf);if (write(fd, buf, strlen(buf)) == -1){perror("write");break;}if (!strcmp(buf, "bye"))break;memset(buf, 0, 128);}close(fd);return 0;
}
  • 运行结果:
    在这里插入图片描述

5、event_base_new( )函数:创建事件集合

(1)函数原型

原型:struct event_base *event_base_new(void);

(2)函数实现

struct event_base *event_base_new(void)
{struct event_base *base = NULL;struct event_config *cfg = event_config_new();if (cfg) {base = event_base_new_with_config(cfg);event_config_free(cfg);}return base;
}

(3)函数作用

创建event_base对象

注意:采用event_base_new( )创建出来的事件集合,最后要用 event_base_free(base)释放掉,因为event_base_new( )是在堆空间上进行创建的。

(4)event_base_new( )与event_init( )的区别

  • 这两个函数都会创建一个事件集合
  • event_init( )创建的是一个全局的事件集合
  • event_base_new( )创建的是一个局部的事件集合
  • event_init( )函数实现是由event_base_new( )封装而成的

6、event_base_free( )函数:释放事件集合

用于释放event_base_new( )函数创建的集合

7、event_assign( )函数:初始化事件

(1)函数原型:

原型:int event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)

(2)参数介绍

1)事件
2)事件集合
3)关联的文件描述符
4)事件类型
5)回调函数
6)回调函数的参数

(3)函数作用

将指定事件集合中的事件与某一文件描述符进行关联

(4)event_assign( )与event_set( )的区别

  • event_assign( )可以指定事件集合;
  • event_set( )不能指定事件集合,默认采用event_init( )创建的全局的事件集合;

8、event_base_dispatch( )函数:监听事件

可监听事件集合当中的事件,和event_dispatch( )的作用相同

(1)event_base_dispatch( )与event_dispatch( )的区别

  • event_base_dispatch( )可以指定监听哪个事件集合;
  • event_dispatch( )不能指定事件集合,默认监听event_init( )创建的全局的事件集合;

9、event_del( )函数:把事件从集合中删除

原型:event_del(struct event *ev);

例子2:信号事件

  • 2-signal.c
#include <stdio.h>                                                                    
#include <stdlib.h>
#include <signal.h>
#include <event.h>int signal_count = 0;void signal_handler(evutil_socket_t fd, short events, void *arg) {struct event *ev = (struct event *)arg;printf("收到信号 %d\n", fd);signal_count++;if (signal_count >= 5) {// 把事件从集合中删除event_del(ev);}   
}int main() {// 创建事件集合struct event_base *base = event_base_new();// 创建事件struct event ev;// 把事件和信号进行绑定event_assign(&ev, base, SIGINT, EV_SIGNAL | EV_PERSIST, signal_handler, &ev);// 把事件添加到集合中event_add(&ev, NULL);// 监听集合event_base_dispatch(base);// 释放集合event_base_free(base);exit(0);
}  

运行结果(收到一次Ctrl+C就打印信息,收到五次之后退出):
在这里插入图片描述

四、libevent高并发服务器

常见步骤:

  • 创建socket对象
  • 绑定 bind
  • 监听 listen
  • 接受连接 accept

1、evconnlistener_new_bind( )函数

(1)作用:用于创建一个evconnlistener对象的函数之一,用于监听指定地址和端口上的连接请求。

在这里插入图片描述

(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);

(3)参数介绍

  • 1)base
    struct event_base类型的指针,表示事件集合
  • 2)cb
    evconnlistener_cb类型的回调函数指针,用于处理新连接的事件。其中:evconnlistener_cb类型
    在这里插入图片描述
  • 3)ptr
    传递给回调函数的参数指针
  • 4)flags
    标志位,可以是LEV_OPT_REUSEABLELEV_OPT_CLOSE_ON_FREE等选项
    其中:
    在这里插入图片描述
    在这里插入图片描述
  • 5)backlog:监听队列的长度。
  • 6)sa:指向struct sockaddr类型的指针,表示要监听的地址和端口。
  • 7)socklen:sa指向的地址结构体的长度。

(4)例子

在这里插入图片描述

2、evconnlistener_free( )函数

(1)作用:释放 TCP 监听器

  • 具体来说,evconnlistener_free() 函数用于释放由 evconnlistener_new() 或 evconnlistener_new_bind() 函数创建的 TCP 监听器。在释放监听器之前,应该确保已经停止了监听器上的事件循环,并且不再有任何活动的连接。

(2)函数原型

void evconnlistener_free(struct evconnlistener *listener);

参数 listener 是指向要释放的 TCP 监听器的指针

(3)注意事项

  • 使用 evconnlistener_free() 函数释放监听器时,libevent 库将自动关闭监听器的文件描述符,并释放监听器所占用的内存。在释放监听器之后,应该将指向监听器的指针设置为 NULL,以避免出现悬空指针的问题。

  • 需要注意的是,如果在释放监听器之前仍然有活动的连接,则这些连接将被关闭,并且可能会因为连接未正常关闭而导致数据丢失。因此,在释放监听器之前,应该确保已经停止了所有连接的事件循环,并且数据已经被正确地处理和发送。

3、bufferevent_socket_new( )函数

(1)作用:对新出现的socket创建一个bufferevent

  • bufferevent_socket_new() 是 libevent 库中用于创建基于套接字的缓冲区事件处理器的函数。
  • 套接字缓冲区事件处理器是一种基于 libevent 库的高级 I/O 抽象,它可以将套接字的读写操作转换为事件驱动的方式。使用套接字缓冲区事件处理器,应用程序可以方便地进行非阻塞 I/O 操作,避免了使用底层的 socket API 进行复杂的事件循环编程。

(2)函数原型

struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil_socket_t fd,enum bufferevent_options options
);

(3)参数介绍

  • 1)base
    事件集合
  • 2)fd
    表示要创建缓冲区事件处理器的套接字文件描述符
  • 3)options
    表示创建缓冲区事件处理器的选项,可以是多个选项的组合,例如 BEV_OPT_CLOSE_ON_FREEBEV_OPT_THREADSAFE
    在这里插入图片描述
    在这里插入图片描述

(4)注意事项

  • bufferevent_socket_new() 函数将返回一个指向新创建的套接字缓冲区事件处理器的指针。应用程序可以使用返回的指针来操作缓冲区事件处理器,例如注册事件回调函数、发送和接收数据等。
  • 套接字缓冲区事件处理器提供了一些高级功能,例如数据读写缓冲区、自动调整缓冲区大小、高效的数据传输等。开发人员可以通过设置选项来控制这些功能的行为,从而满足不同的应用场景需求

(5)例子

在这里插入图片描述

4、bufferevent_setcb( )函数

(1)改变bufferevent的回调函数

bufferevent_setcb() 函数是 libevent 库中用于设置套接字缓冲区事件处理器回调函数的函数。

(2)函数原型

void bufferevent_setcb(struct bufferevent *bev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void *cbarg
);

(3)参数介绍

  • 1)bev:指向要设置回调函数的套接字缓冲区事件处理器的指针。
  • 2)readcb:表示在缓冲区有数据可读时调用的回调函数。
  • 3)writecb:表示在缓冲区可写时调用的回调函数。
  • 4)eventcb:表示在事件处理器发生错误或状态变化时调用的回调函数。
  • 5)cbarg:表示传递给回调函数的参数。

(4)注意

  • 使用 bufferevent_setcb() 函数,应用程序可以指定回调函数来处理套接字缓冲区事件处理器上的事件,例如接收数据、发送数据、处理错误等。回调函数应该是线程安全的,并且需要尽快返回,以避免阻塞事件循环。
  • 需要注意的是,bufferevent_setcb() 函数必须在事件处理器开始运行之前调用,否则设置的回调函数可能无法生效。而且,在设置回调函数之后,应用程序应该尽快启动事件处理器的事件循环,以便及时处理事件。

(5)例子

在这里插入图片描述
在这里插入图片描述

5、bufferevent_read( )函数

(1)作用:读取套接字缓冲区事件处理器中的数据

  • bufferevent_read() 是 libevent 库中的一个函数,用于读取套接字缓冲区事件处理器中的数据。

(2)函数原型

int bufferevent_read(struct bufferevent *bev, void *data, size_t size);

(3)参数介绍

  • 1)bev:指向要读取数据的套接字缓冲区事件处理器的指针;
  • 2)data:指向存储读取数据的缓冲区的指针;
  • 3)size:表示要读取的数据的最大字节数。

(4)注意事项

  • 使用 bufferevent_read() 函数,应用程序可以从套接字缓冲区事件处理器中读取数据到指定的缓冲区中,并返回实际读取的字节数。如果没有数据可读,函数将立即返回,并返回 0。如果读取时发生错误,函数将返回 -1,并设置 errno 变量来指示错误的原因。
  • 需要注意的是,使用 bufferevent_read() 函数只会从套接字缓冲区中读取数据,并不会阻塞等待数据到达。如果没有数据可读,函数将立即返回,因此需要在事件处理器的读回调函数中多次调用该函数,以确保读取所有可用的数据。

(5)例子

在这里插入图片描述

6、bufferevent_free( )函数

(1)作用:释放套接字缓冲区事件处理器的内存资源

(2)函数原型:

void bufferevent_free(struct bufferevent *bev);

(3)注意事项

  • 使用 bufferevent_free() 函数,应用程序可以释放套接字缓冲区事件处理器占用的内存资源,包括套接字缓冲区、读写缓冲区、回调函数等。在释放套接字缓冲区事件处理器之前,应用程序应该确保已经停止所有的 I/O 操作,并且没有剩余的事件需要处理。
  • 需要注意的是,如果套接字缓冲区事件处理器是使用 bufferevent_socket_new() 函数创建的,则在释放套接字缓冲区事件处理器之前,应用程序通常需要先关闭套接字。可以使用 bufferevent_free() 函数的前一个参数 bev 来访问套接字缓冲区事件处理器的套接字描述符,然后使用 close() 函数来关闭套接字。

完整的例子

服务端

#include <stdio.h>                                                                                                                  
#include <stdlib.h>
#include <event.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <event2/listener.h>//读取数据
void read_cb(struct bufferevent *bev, void *ctx) {char buf[128] = {0};size_t ret = bufferevent_read(bev, buf, sizeof(buf));if (ret < 0) {printf("bufferevent_read error!\n");} else {printf("read %s\n", buf);}   
}
void event_cb(struct bufferevent *bev, short what, void *ctx) {if (what & BEV_EVENT_EOF) {printf("客户端下线\n");bufferevent_free(bev);     //释放bufferevent对象} else {printf("未知错误\n");}
}void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg) {printf("接受%d的连接\n", fd);struct event_base *base = arg;//针对已经存在的socket创建bufferevent对象//事件集合(从主函数传递来)、fd(代表TCP连接)、BEV_OPT_CLOSE_ON_FREE(如果释放bufferevent对象则关闭连接)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设置回调函数//bufferevent对象、读事件回调函数、写事件回调函数、其他事件回调函数、参数bufferevent_setcb(bev, read_cb, NULL, event_cb, NULL);//使能bufferevent对象bufferevent_enable(bev, EV_READ);
}// 常见步骤:
/*socketbindlistenaccept
*/int main() {//创建一个事件集合struct event_base *base = event_base_new();if (NULL == base) {printf("event_base_new error\n");exit(1);}struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8000);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//创建socket、绑定、监听、接受连接//创建监听对象,在指定的地址上监听接下来的TCP连接//事件集合、当有连接时调用的函数、回调函数参数、释放监听对象关闭socket|端口重复使用、监听队列长度、绑定信息//返回值:监听对象struct evconnlistener *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) {printf("evconnlistener_new_bind error\n");exit(1);}    //监听集合中的事件event_base_dispatch(base);//释放两个对象evconnlistener_free(listener);event_base_free(base);exit(0);
}      

客户端:

#include <stdio.h>                                                                                                                  
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>int main()
{//创建socketint sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd){   perror("socket");exit(1);}  //发起连接请求struct sockaddr_in server_info;    //保存服务器的信息bzero(&server_info, sizeof(server_info));server_info.sin_family = AF_INET;server_info.sin_port = htons(8000);server_info.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(sockfd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1){perror("connect");exit(2);}char buf[1024] = {0};while (1){scanf("%s", buf);if (send(sockfd, buf, strlen(buf), 0) == -1){perror("send");break;}if (!strcmp(buf, "bye"))break;bzero(buf, 1024);}close(sockfd);return 0;
}

运行结果:

在这里插入图片描述


http://www.ppmy.cn/news/212920.html

相关文章

BLIP使用教程

文章目录 准备测试示例一示例二&#xff1a; 结论源代码 原理篇&#xff1a; BLIP2-图像文本预训练论文解读 准备 如果无网络需提前下载相关模型 安装torch、transformers pip install torch trtransformers测试 测试blip基于图片生成文本描述能力&#xff08;Caption&…

1132 Cut Integer(17行代码)

分数 20 全屏浏览题目 切换布局 作者 CHEN, Yue 单位 浙江大学 Cutting an integer means to cut a K digits lone integer Z into two integers of (K/2) digits long integers A and B. For example, after cutting Z 167334, we have A 167 and B 334. It is intere…

金立S8 线刷

由于金立s8广告太多&#xff0c;想通过线刷刷回以前版本&#xff0c;记录关键内容&#xff1a; 1.进REC方式&#xff1a;按电源键音量上键&#xff0c;选择REC&#xff0c;进入机器人倒地界面&#xff0c;按电源键音量下键五秒后按电源上键&#xff0c;进入REC 2. 线刷方式&a…

金立android在哪里设置密码,金立s5.5手机怎么设置锁屏密码 金立s5.5屏幕密码设置教程...

金立s5.5怎么设置屏幕密码&#xff1f;手机里有隐私&#xff0c;不想让别人看到如何设置屏幕密码呢&#xff1f;下面脚本之家小编为大家介绍下s5.5设置屏幕密码教程&#xff01;不知道的赶快来看下吧&#xff01; 1.在手机界面点击“设置”&#xff0c;之后会出现“滑动”、“图…

联发科6758_搭载MTK MT6758!金立M7现身跑分网站

随着流行趋势的变化&#xff0c;当前众手机厂商的目光都盯紧了全面屏领域&#xff0c;已上市的全面屏产品之外&#xff0c;另有多家厂商明确表态下半年会有全面屏新品面世&#xff0c;其中就包括金立手机&#xff0c;金立官方早早便宣布&#xff0c;下半年重点发力全面屏领域&a…

金立(Gionee)金立M7 Power root 大金刚 GN5007 刷机TWRP 面具 XP框架 线刷包

刷机 免解锁BL 刷精简官方系统&#xff0c;如果需要售后刷机软件 请联系获取免费获取 成功安装框架、 成功 root 金立(Gionee)金立M7 Power root 大金刚 GN5007 刷机TWRP 面具 XP框架,手机root刷机包 柒叁伍叁玖肆零零陆 &#xff08; 转换成小写数字&#xff09; 远程ro…

金立android手机怎么截图,金立M6手机怎么截图 金立M6截屏/截图方法(两种)

昨天下午&#xff0c;国产老牌手机厂商金立发布了M6安全长续航手机&#xff0c;这款手机主要有两大亮点&#xff0c;一是超级续航&#xff0c;而是内置安全加密芯片。注重续航和安全的用户可能会更加看中这点准备入手。如今就有网友开始咨询金立M6怎么截图&#xff0c;为此脚本…

金立卒于 16 岁

金立的没落&#xff0c;是技术更迭浪潮里守旧者的残酷结局&#xff0c;是企业难以把控资金流向的失控之殇&#xff0c;也可能是董事长个人行为酿下的苦果。 作者 | Yesse& Yorke 责编 | 胡巍巍 一家2017年上半年还有7.6亿元盈利的公司&#xff0c;如何会在一年内迅速走向…