本文主要是介绍7、epoll边沿触发与反应堆,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
epoll边沿触发
1、epoll事件模型:
epoll监听的是文件描述符,也可以监控进程间通信的事件。
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>#define MAXLINE 10int main(int argc, char *argv[])
{int efd, i;int pfd[2];pid_t pid;char buf[MAXLINE], ch = 'a';pipe(pfd);//创建一个管道pid = fork();//创建一个主进程if (pid == 0) //pid = 0说明是子进程,子进程完成写操作{ close(pfd[0]);//关闭子进程读端while (1){//aaaa\nfor (i = 0; i < MAXLINE/2; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//bbbb\nfor (; i < MAXLINE; i++)buf[i] = ch;buf[i-1] = '\n';ch++;//aaaa\nbbbb\nwrite(pfd[1], buf, sizeof(buf));//向管道里写bufsleep(5);}close(pfd[1]);} else if (pid > 0){ struct epoll_event event;struct epoll_event resevent[10]; //epoll_wait就绪返回eventint res, len;close(pfd[1]);efd = epoll_create(10);//创建一个文件描述字来进行监听cfdevent.events = EPOLLIN | EPOLLET; // ET 边沿触发// event.events = EPOLLIN; // LT 水平触发 (默认)event.data.fd = pfd[0];epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);/*event.events = EPOLLIN | EPOLLET;:这行代码设置了event结构体的events字段,指示关 联的文件描述符(pfd[0])应该以边沿触发(Edge Triggered,ET)模式监视可读事件 (EPOLLIN)。在ET模式下,只有当文件描述符上的状态从不可读变为可读时,才会触发事件,因此需要 确保在处理可读事件后,读取所有可用数据,直到再次返回EAGAIN或EWOULDBLOCK。event.data.fd = pfd[0];:这行代码设置了event结构体的data.fd字段,将文件描述符 pfd[0]与这个事件关联起来。epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);:这行代码将event结构体中定义的事 件添加到epoll实例(由efd表示)。这将告诉epoll开始监视pfd[0]上的可读事件,并在该事件发生 时通知应用程序。*/ //LT为水平触发,只要缓冲区里还有就会触发//ET为边沿触发,只有缓冲区中内容在增加时才会触发while (1) {res = epoll_wait(efd, resevent, 10, -1);//epoll_wait函数来等待事件的发生。efd是epoll实例的文件描述符,resevent是用于存 //储就绪事件的数组,10表示resevent数组的大小,-1表示等待时间无限长printf("res %d\n", res);if (resevent[0].data.fd == pfd[0]) //检查就绪事件数组中的第一个事件是否与文件描述符pfd[0]相关。data.fd字段存储了与事件 //相关的文件描述符。{len = read(pfd[0], buf, MAXLINE/2);write(STDOUT_FILENO, buf, len);}}close(pfd[0]);close(efd);} else {perror("fork");exit(-1);}return 0;
}
ET模式:边沿触发
缓冲区剩余未读尽的数据不会导致epoll_wait返回。新的事件满足才会触发。
LT模式:水平触发(默认)
缓冲区未读尽的数据会导致epoll_wait返回
2、epoll中的ET非阻塞模式
readn:读够一定数量的字节才会返回
**只需要三行代码:**文件描述字设置非阻塞
flag = fcntl(cfd,F_GETFL);
flag |= 0_NOBLOCK;
fcntl(cfd,F_SETFL,flag);
缺点:不能跨平台,只能在Linux上使用
epoll反应堆模型
1、概述:
有n个客户端,服务器就会有n个连接(n个客户端和连接端)。
n:有可读事件(包括lfd)和可写事件。
/*一个事件其是可以看成下面结构体的三个成员*/
struct event
{int cfd;read_cb();write_cb();
}
反应堆:一个IO对应多个事件。
2、代码实现(检测业务的实现)
lfd采用accept处理
cfd采用recv和send处理
#define EVENT_LENGTH 1024
unsigned char buf[];
int init_server
{}
struct item
{int cfd;unsigned char rbuffer[1024];//读缓冲区int rlen;unsigned char wbuffer[4096];int wlen;//将要写的长度int wsize;//已经写的长度
};
struct item *get_item_by_clientfd(int clientfd)
{//通过cfd来找item数组
};int main()
{int epfd = epoll_create(1);int sockfd = init_server();//最开始的时候,红黑树只有一个lfdepoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);struct epoll_event events[EVENT_LENGTH] = {0};while(1){int nready = epoll_wait(epfd,events,EVENT_LENGTH,-1);int i = 0;for(i = 0; i < nready; i++){//第一层if用来判断是lfd还是cfd//第二层if用来判断是读cfd,还是写cfdif(events[i].data.fd == sockfd){struct sockaddr_in client;socklen_t len = sizeof(client);int cfd = accept(sockfd,(struct sockaddr*)&client,len);//来一个客户端连接一个服务器struct epoll_event ev = {0};ev.event = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);}else{struct item *it = get_item_by_clientfd(events[i].data.fd);if(event[i].event & EPOLLIN){//读客户端it->len = recv(events[i].data.fd,buffer,1024,0);//只读一半,没有读完}if(event[i].event & EPOLLOUT){//写客户端send(events[i].data.fd,it->wbuffer+it->wsize,it->wlen-it.size,0);//当一次性没有写完时,要放在写缓冲区里面//对于一次性没有读完时,要放在读缓冲区//这里的缓冲区指的是内核的缓冲区it->wsize += ret;}}}}
}
那么对于以上代码,buffer的处理业务在哪里呢?
对于数据来说,有三层操作:
为什么不能直接将解析代码放到recv和send后面呢?因为如果这样写的话,代码的可复用性较差。所以使用回调函数。
反应堆主要是用来检测fd是读还是写。
错误说法:reactor是epoll加上了回调函数: reactor的是用来形容一个事件是否触发
对于下面一个结构体的两种封装方式,哪一个好:
struct reactor
{int epfd;struct item *items;int count;int ucount;
};
封装方式一:
struct reactor *Init_reactor(int size)
{struct reactor *t = malloc(sizeof(struct reactor));}
struct reactor *del_reactor(struct reactor *r)
{free(r);
}
封装方式二:
struct reactor *Init_reactor(struct reactor *r)
{r->epfd = epoll_create(1);r->items = malloc(1024*sizeof(struct reactor *r));r->count = 1024;r->ucount = 0;
}
struct reactor *Init_reactor(struct reactor *r)
{close(r->epfd);
}
封装方式二更好,因为其避免了出现返回值
之后需要封装下面几个函数
int accept_callback(int fd, int events, void *arg)
{
}
int recv_callback(int fd, int events, void *arg)
{
}
int send_callback(int fd, int events, void *arg)
{
}
int set_events(int fd, int events, void *arg)
{
}
这篇关于7、epoll边沿触发与反应堆的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!