第2课-基于Epoll的采集端程序框架设计
epoll的使用流程
(1)创建
(2)加入事件
(3)等待和处理
步骤:
- 创建源文件
我们的软件体系到底能不能完成,关键的事情就是加入事件(摄像头)。
这里我们区分,主程序是第一个模块,摄像头采集系统是第二个模块,传输系统是第三个模块,配置子系统是第四个模块。每个模块对应一个原文件,我们要创建四个原文件。分别为:main.c cam.c net.c cfg.c
- 模型代码化
我们创建一个include文件,用来存放我们要用到的头文件。创建头文件main.h。在这里建立结构体struct server,在cam.c中建立结构体struct cam,在net.c中建立struct tcp,在cfg.c中建立struct cfg,具体内容先不去填写。今天我们的任务就是将main.h中的内容进行完善即可。
主程序对应着与配置子系统、采集子系统、传输子系统和epoll框架这四个内容的关系。我们就建立四个成员对应这四个关系。我们可以进行函数下放,就要下放接口。子程序同时也提供函数接口,这样就完成了函数的下放。我们看看有哪些接口:
(1)添加事件到EPOLL
(2)从EPOLL中删除事件
现在我们在想为什么不直接用系统带的函数epoll_ctl,还要自己写呢?因为我们用的epoll_ctl是依赖于struct epoll_event结构来的,但是这个结果里面的信息有限:
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_4 u64;
}epoll_data_t;
stryct epoll_event
{
unint32_t events; /*Epoll events*/
epoll_data_t data; /*User data variable*/
};
也就只有一个文件类型和一个联合(包含4项),我们要是想多保存点信息就不行了。我们可以使这个指针再加一些附加结构,这里面保存我们想要有的其他信息。我们在main函数中定义就可以。
struct server
{
int epfd; //指向epoll
struct cam *cam; //指向采集子系统
struct tcp_srv *srv; //指向传输子系统
struct cfg* cfg; //指向配置子系统
};
- 程序
main.c
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<main.h>
#include<stdbool.h>
struct event_ext //我们的附加的结构体,用来补充epoll_ctl函数
{
int fd;
bool epolled; //表示我们的时间是否已经加入到了我们监控池之中
uint32_t events; //事件的类型
void (*handler)(int fd,void *arg); //函数指针,其中有两个成员
void *arg; //提供给handler使用的指针
};
/*由于我们附加的函数的作用,我们还得需要自己手写我们对人epoll事件的添加和删除函数*/
/*基于新的结构体而定创造函数,结构体中有的内容,这里面都该有,第一个结构*/
struct event_ext *epoll_event_creat(int fd,uint32_t type,void (*handler)(void *arg),void *arg)
{
struct event_ext *e = calloc(1,sizeof(struct event_ext)); //分配空间,进行初始化
e->fd = fd;
e->events = type;
e->handler = handler;
e->arg = arg;
return e;
};
//添加epoll,第二个结构
int epoll_add_event(int epfd, struct event_ext *ev)
/*创建一个事件附加结构,并将参数传进来*/
{
struct epoll_event epv; //先创建一个结构体
int op; //定义原本的epoll_ctl中的第二个参数
//2. 初始化epoll_event(将附加结构挂载到epoll_event中)
epv.data.ptr = ev; //将附加的结构挂载原来系统带的结构体上
epv.events = ev->events; //初始化数据类型,原本的epoll_event中就这两个成员
if(ev->epolled) //文件已经存在的时候,就是进行修改,否则进行加入
{
op = EPOLL_CTL_MOD; //修改的操作
}
else
{
op = EPOLL_CTL_ADD;//添加的操作
ev->epolled = true; //第一次添加进去之后,标识就变成了真
}
//3. 将epoll_even加入到epoll中
epoll_ctl(epfd,op,ev->fd,&epv);
return 0;
};
//从epoll池中删除
int epoll_del_event(int epfd, struct event_ext *ev)
{
epoll_ctl(epfd,EPOLL_CTL_DEL,ev->fd,NULL);
ev->epolled = false; //删除后,标识就变成了假
return 0;
};
/*定义结束这个附加的结构指针,我们才能对下面的标有“处理”的地方进行书写*/
int main()
{
struct epoll_event events[512];
int fds;
int i;
uint32_t event; //用于保存事件的类型
struct event_ext *e; //附加结构带来的新的指针
srv_main = calloc(1,sizeof(struct server)); //为结构体分配空间
//1. 创建epoll
srv_main->epfd = epoll_create(512);
//2. 加入等事件,将这个程序进行下放-交给子系统,这之后就要设置子系统的函数接口
srv_main->cam = cam_sys_init();
srv_main->srv = net_sys_init();
//3. 等待事件发生且处理
while(1) //不断地进行处理相应的事件
{
fds = epoll_wait(srv_main->epfd,events,512,1000);
for(i=0;i<fds;i++)
{
event = events[i].events; //保存事件的类型
e = events[i].data.ptr; //保存事件的处理方法
if((event&EPOLLIN)&&(e->events&EPOLLIN)) /*若是读的事件,附加条件要e里面的内容类型与event里面的一致*/
{
//处理
e->handler(e->fd,e->arg);
}
if((event&EPOLLOUT)&&(e->events&EPOLLOUT)) /*若是读的事件,附加条件要e里面的内容类型与event里面的一致*/
{
//处理
e->handler(e->fd,e->arg);
}
if((event&EPOLLERR)&&(e->events&EPOLLERR)) /*若是读的事件,附加条件要e里面的内容类型与event里面的一致*/
{
//处理
e->handler(e->fd,e->arg);
}
}
}
return -1; //按道理来讲我们而定循环是不应该结束的,要是结束了就说明程序出错了
}
cam.c
#include<main.h>
struct cam
{
};
struct cam *cam_sys_init()
{
//1. 初始化采集子系统
//2. 将采集子系统中的事件加入epoll池
return NULL;
};
net.c
#include<main.h>
struct tcp_srv
{
};
struct tcp_srv *net_sys_init()
{
//1. 初始化传输子系统
//2. 将传输子系统中的事件加入epoll池
return NULL;
};
cfg.c
struct cfg
{
};
- 编译
创建一个Makefile文件,touch Makefile。通过samb服务器打开这个文件我们写下如下的文件:
BIN = wcamsrv //输出的文件名字
INC = -Iinclude/ /*表示需要引用的是当前文件下的include文 件,-I表示的对应的操作*/
SRC = $(wildcard *.c) //表示寻找的该路径下的所有.c文件,都给SRC
OBJ = $(patsubst %.c, %.o, $(SRC) ) /*表示输出的格式是.o的形式的,对应的三个参数,第一个参数表示所有的.c文件,第二个参数表示输出都是.o文件,第三个参数表示来自SCR变量*/
CC = arm-linux-gcc //表示编译器的选择
CFLAGS = $(INC) -g //编译选项
$(BIN):$(OBJS) //编译依赖
$(CC) -o $@ $^ //编译输出
clean: //清除选项
rm $(OBJS) $(BIN)