嵌入式全栈开发学习笔记---Linux常用库(libevent)

2024-09-02 15:04

本文主要是介绍嵌入式全栈开发学习笔记---Linux常用库(libevent),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

libevent下载与安装

两个重要的结构体struct event、struct event base

libevent监听(有名)管道事件

第一步,创建一个管道并打开管道

第二步,创建一个事件,并初始化一个事件集合

第三步,初始化事件

第四步,将事件放到集合中

第五步,开始监听

libevent监听信号事件

第一步,创建事件集合

第二步,创建事件并初始化事件

第三步,把事件添加到集合并开始监听

libevent-高并发服务器

第一步,监听对象

第二步,监听

第三步,接收连接并处理


上节学习了json,这节开始学习libevent!

本节来学习一下C语言中比较重要的库:Libevent,一般它是用来监听事件的。

libevent下载与安装

Libevent是一个开源库

之前我们学习服务器端和客户端的网络编程时,我们需要自己写创建socket,绑定信息,监听等等代码,而这个Libevent里面已经有封装好的这些代码,我们直接使用,提高我们编程的效率。

官网:libevent

我们可以到它的官网下载源码。

我们先在自己的虚拟机上安装libevent

安装步骤:

在官网把这个源码下载后解压,拖进虚拟机中任意一下文件夹下,然后依次执行以下三个命令

安装方法:

./configure

make

make install

注意:Linux操作系统它有很多发行版本,比如我用的虚拟机叫红帽,然后还有ubuntu,云服务器,还有其他比较常用的发行版本,不同的发行版本略微有一些区别,所以下载的方式也有所不同。要在虚拟机中安装只能通过这种源码的方式进行下载这些库。

两个重要的结构体struct event、struct event base

Libevent里面有两个非常重要的结构体:struct event;  struct event base;

之前我们学习epoll的时候知道被监听的事件是放在一个集合中的,而这个集合其实就是一个存放了很多事件的结构体,而每一个事件也是用结构体封装的。

struct eventstruct event base的关系如图:

libevent常用接口其实就是一些封装好的函数,接下来会在演示代码中挨个讲解。

接下来测试一下用libevent监听管道事件和监听信号事件

源码查看软件推荐:SourceInsight

libevent监听(有名)管道事件

复习一下有名管道的相关知识:有名管道需要有两个进程,一个进程负责写管道,另一个进程负责读管道,它有两个文件描述符,fd0/fd1。

在这里,我们把有名管道的一个文件描述符添加在libevent的集合里面,libevent就可以监听这个集合,一旦发现这个fd可读就调用函数把数据读出来。

第一步,创建一个管道并打开管道

第二步,创建一个事件,并初始化一个事件集合

接下来创建一个事件,并初始化一个事件集合,初始化集合这里要用到这个函数

第三步,初始化事件

然后是初始化事件(把fd和事件ev绑定)

绑定的时候用到这个函数

可以找到这个函数的参数描述:

第一个参数是我们刚刚创建的那个事件的地址;

第二个参数是要和事件绑定的那个文件描述符,这个类型evutil_socket_t其实是一个int类型;

第三个参数是事件类型,这个事件类型可以选择以下几种

第一个是 EV_TIMEOUT定时器(超时)事件;EV_READ和EV_WRITE是IO事件;EV_SIGNAL是信号事件;EV_PERSIST这个事件,之前我们学习select的时候就知道select一旦监听到sockfd可读,就把sockfd留在集合里面,把另外的删除,而如果我们使用了EV_PERSIST这个参数的话,即使监听到fd可读,其他的fd还可以保留在里面。

等会儿我们用的是EV_READ。

第四个参数是一个函数的地址,因为当监听到有fd可读了,就得调用一个函数把它的数据读出来。

第五参数是要给第四个参数的那个函数传的参数,如果不需要传参就直接写NULL

接下来实现fifo_read这个函数,在这个函数里面我们要读取数据

第四步,将事件放到集合中

然后我们要将事件放到集合中

要用到这个函数

第一个参数是事件ev,第二个参数类似于select()的最后一个参数,即超时的时间,可以直接写NULL

第五步,开始监听

接下来开始监听

监听要用到这个函数:

注意:使用libevent库要包含头文件#include <event.h>,并且编译的是要加上-levent,因为它是外来库

如果编译没有问题,运行的时候出现这个错误

就执行以下操作:

完整代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <event.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>//当事件满足条件的时候会触发回调函数,通过回调函数读取数据
void fifo_read(evutil_socket_t fd,short events,void *arg)//文件描述符fd 事件类型 参数
{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(2);}//创建事件struct event ev;//初始化事件集合event_init();//初始化事件(把fd和事件ev绑定)event_set(&ev,fd,EV_READ|EV_PERSIST,fifo_read,NULL);//事件 关联的文件描述符 事件类型 回调函数 回调函数的参数//把事件添加到集合中event_add(&ev,NULL);//开始监听event_dispatch();//死循环,如果集合中没有事件可以监听则返回return 0;
}

以上完成的读管道,我们还需要一个写管道,直接拷贝我们之前写过的代码进行测试即可。

写管道的代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.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;
}

测试结果:

总结流程

这个程序运行后它就会一直在监听那里死循环,一旦监听到fd可读的时候就调用fifo_read函数读取数据,读完之后就结束进程。

为什么读一次就是结束?

因为Libevent集合里面和select()一样,一旦监听到有fd可读它就会把集合中不可读的清理掉,而没有fd在里面后event_dispatch()就返回了。

可以让它不清理掉其他fd吗?

可以在初始化事件的时候或上EV_PERSIST这个事件类型

测试结果:

这样读取完后读管道的那个进程并没有结束,因为fd没有被清零掉,因此它还在一直监听,没有返回。

libevent监听信号事件

刚刚我们写的是IO事件的代码,现在来演示一下信号事件

第一步,创建事件集合

我们可以试一下这个函数也可以创建事件集合

只是用这个函数来创建集合的话,最后记得要把它给释放掉,因为它是在堆空间创建的。

第二步,创建事件并初始化事件

接下来创建事件并初始化事件,初始化事件(把事件与信号绑定)我们也可以尝试一下这个函数:

第一个参数是事件,

第二个参数是事件集合,

第三个参数是文件描述符(或者说信号,这里我们将使用SIGINT: 来自键盘的中断信号(Ctrl-C),这个信号的系统编号是2),

第四个参数是事件类型,这里我们用信号类型EV_SIGNAL,并且或上EV_PERSIST

第五个参数是回调函数,第六个参数是回调函数的参数。

接下来实现回调函数signal_handler

第三步,把事件添加到集合并开始监听

接下来把事件添加到集合并开始监听,

监听这里要注意,之前我们用的是event_dispatch这个函数来监听集合,那是我们用的event_init这个函数来初始化库里边已经定义好的一个全局变量集合,但是这里我们自己用event_base_new()这个函数来自己创建集合,所以我们这里就不能用event_dispatch这个函数来监听集合了,我们要用event_base_dispatch这个函数来监听我们自己创建的集合。

完整代码:

#include <stdio.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;//void*型的指针最好强转后再赋值printf("收到信号%d\n",fd);signal_count++;if(signal_count>=2)//SIGINT的编号是2,按两下CTRL+C后就让程序结束{//把事件从集合中删除,event_base_dispatch监听不到就返回,程序就会结束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);return 0;
}

运行结果

libevent-高并发服务器

接下来我们用libevent来实现一个可以同时处理多个客户端的服务器,也就是高并发服务器

我们将用到之前在官网下载好的源码里的这些头文件,里面对每个函数都有详细的描述

复习一下搭建服务器的大体步骤

第一步,监听对象

在Libevent里面有个函数把前四步都封装好了:

这个函数的具体定义可在listener.h这个头文件里面找到

具体解析:

这个函数的返回值是一个struct evconnlistener *类型的监听对象,失败返回NULL

那个flags LEV_OPT_这个标志位可以在源码中找到:

LEV_OPT_LEAVE_SOCKETS_BLOCKING设置为阻塞的,这个标志位我们目前不需要

LEV_OPT_CLOSE_ON_FREE释放监听对象的时候要关闭socket(这个我们需要)

LEV_OPT_REUSEABLE当在相同的端口再次监听时和socket关闭之间不允许有暂停,即可以重复使用(需要)

Sa套接字的属性其实就是我们要绑定的信息,比如端口和IP地址

以上就是调用evconnlistener_new_bind这个函数完成创建socket(返回一个sockfd会被放在我们创建的集合base里面),绑定信息和创建监听对象的过程

注意:接收连接也是在这个函数里面完成的。参数里传的那个回调函数是当接收连接accept之后再调用的,在回调函数里面开始读写数据。在accept的时候会返回一个fd,这个fd会被放在我们创建的集合base里面。

第二步,监听

监听对象创建好后接下来就死循环监听就可以了

如果死循环监听这步返回了,我们要释放监听对象和集合

第三步,接收连接并处理

最后完成那个回调函数

我们从fd里面读取数据,如果用之前的read函数也是可以的,但是由于我们设置的是不阻塞,还是建议直接用libevent封装好的库函数,这里要先创建bufferevent对象,它相当于创建缓冲区,当它从socket里面读数据的时候,libevent是需要一个缓冲区来保存数据的,在里面也可以检测到一些事件的发生。

创建bufferevent对象要用到这个函数:

它的作用主要是将buffferevent和socket绑定起来

这个函数可以在bufferevent.h这个头文件中找到

注意,bufferevent对象也要放在我们创建的base集合里面,所以这里回调函数的参数要传base

于是回调函数这里的void*arg接收的就是base

然后这个option的值可以到源码中找

可以选择以下这些标志

BEV_OPT_CLOSE_ON_FREE表示如果bufferevent被释放掉,就关闭文件描述符,其他的暂时用不上。

返回值是一个bufferevent对象,这个对象也可以当成一个事件来监听,我们要监听它是否可读,一旦可读就触发一个函数,因此我们得给这个bufferevent设置一个回调函数。

给这个bufferevent设置一个回调函数要用到这个函数:

这个函数也是可以在bufferevent.h这个头文件中找到

第一个参数是我们要为哪个bufferevent设置回调函数,

第二个参数是读数据的回调函数,

第三个参数是写数据的,

第四个参数是当我们监听的那个bufferevent对象有异常发生的时候就会调用的函数(比如说客户端被CTRL+C异常退出了就会调用这个函数),

第五个参数是可以给前面的三个函数传参

没有用到的函数,直接写成NULL

最后我们要使能bufferevent对象,让它工作,我们要调用buffferevent_enable这个函数来使能它

第二个参数是事件类型,也就是我们是想让这个bufferevent对象是被使能来读数据的,还是被使能来写数据的,这里我们只要读就可以了,所以选择EV_READ

接下来就来实现read_cb和event_cb这两个函数

read_cb是这样类型的函数:

在read_cb这个函数里面要读取数据得调用这个函数:

返回值是读到的字节数

event_cb是这样类型的函数:

当socket连接异常发生时就会触发这个函数,

这个函数的第二个参数是异常的原因,可以选择这些标志位

CTRL+C这个事件对应的是BEV_EVENT_EOF

运行结果:

这样打印出来的what是十六进制0x11,它就是BEV_EVENT_READING或上BEV_EVENT_EOF的结果

因此我们代码中用这种方式判断BEV_EVENT_EOF导致的异常退出(0x11 & 0x10=0x10即BEV_EVENT_EOF)

但是提示客户端下线那里提示的fd错误,按理来说它应该是7

可能是由于fd是局部变量,在栈空间中,程序运行过程中它被释放了,我们也许可以把它从bev中解析出来,这里可以自己尝试一下,这里就不演示了,直接删掉不打印这个fd了

完整代码:

#include <stdio.h>
#include <event.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <event2/listener.h>//包含event2目录下的listen.h头文件//读取数据
void read_cb(struct bufferevent *bev, void *ctx)//ctx接收的是bufferevent_setcb传过来的fd
{char buf[128]={0};size_t ret=bufferevent_read(bev,buf,sizeof(buf));//从哪读,读到哪,读多少if(ret<0){printf("bufferevent_read error\n");}else{printf("read from %s\n",buf);}
}void event_cb(struct bufferevent *bev, short what, void *ctx)
{printf("异常发生 %x!\n",what);//%x打印十六进制,把原因打印出来if(what & BEV_EVENT_EOF)//如果原因是BEV_EVENT_EOF,what是一个整数{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对象,这里的socket是TCP连接的那个socket,不是监听的那个//事件集合(从主函数传递过来),fd(代表TCP连接),BEV_OPT_CLOSE_ON_FREE(如果释放bufferevent对象则关闭连接)struct bufferevent *bev=bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);//返回值是一个bufferevent对象的指针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);}//搭架服务器的步骤:socket,bind,listen,accept(返回fd),读写
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=PF_INET;server_addr.sin_port=htons(8000);server_addr.sin_addr.s_addr=inet_addr("192.168.0.163");//本机//创建监听对象,在指定的地址上监听接下来的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(2);}//开始监听集合中的事件event_base_dispatch(base);//释放两个对象evconnlistener_free(listener);event_base_free(base);return 0;
}

运行结果

客户端下线之后还有个sockfd在里面,所以event_base_dispatch会一直在监听。

下节开始学习shell脚本!

如有问题可评论区或者私信留言,如果想要进交流群请私信!

这篇关于嵌入式全栈开发学习笔记---Linux常用库(libevent)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130289

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设