epoll监听的是文件描述符,也可以监控进程间通信的事件。
#include
#include
#include
#include
#include
#define MAXLINE 10
int 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\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
//向管道里写buf
sleep(5);
}
close(pfd[1]);
}
else if (pid > 0)
{
struct epoll_event event;
struct epoll_event resevent[10]; //epoll_wait就绪返回event
int res, len;
close(pfd[1]);
efd = epoll_create(10);//创建一个文件描述字来进行监听cfd
event.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返回
readn:读够一定数量的字节才会返回
**只需要三行代码:**文件描述字设置非阻塞
flag = fcntl(cfd,F_GETFL);
flag |= 0_NOBLOCK;
fcntl(cfd,F_SETFL,flag);
缺点:不能跨平台,只能在Linux上使用
有n个客户端,服务器就会有n个连接(n个客户端和连接端)。
n:有可读事件(包括lfd)和可写事件。
/*一个事件其是可以看成下面结构体的三个成员*/
struct event
{
int cfd;
read_cb();
write_cb();
}
反应堆:一个IO对应多个事件。
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();
//最开始的时候,红黑树只有一个lfd
epoll_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,还是写cfd
if(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)
{
}