当前位置: 代码迷 >> 综合 >> I/O复用 epoll
  详细解决方案

I/O复用 epoll

热度:67   发布时间:2024-01-10 03:09:31.0

一直看书,没写,把这个例子程序敲一遍,很多部分都是通用的,先留着。

 

epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表,由epoll_create函数来创建

#include<sys/epoll.h>

int epoll_create(int size)

size参数不起作用,只是给内核一个提示,告诉它事件表需要多大。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。

 

操作epoll的内核事件表:

#include<sys/epoll.h>

int epoll_ctl(int epfd,    int op,   int  fd,   struct epoll_event *event)

fd是要操作的文件描述符,op参数指定操作类型。操作类型有一下3种:

EPOLL_CTL_ADD 往事件表中注册fd上的事件

EPOLL_CTL_MOD 修改fd上的注册事件

EPOLL_CTL_DEL 删除fd上的注册事件

event参数指定事件,是epoll_event结构指针类型。

epoll_event定义如下:

struct epoll_event

{

    __uint32_t events;//epoll事件 有 数据可读事件EPOLLIN,    EPOLLET,   EPOLLONESHOT

    epoll_data_t data;//用户数据

}

 

typedef union epoll_data

{

    void* ptr;

    int fd;//指定事件所从属的目标文件描述符

    uint32_t u32;

    uint64_t u64;

}epoll_data_t;

 

epollwait函数,在一段超时时间内等待一组文件描述符上的事件。

#include<sys/epoll.h>

int epoll_wait(int epfd,    struct  epoll_event* event,   int    maxevents,   int  timeout);

成功返回就绪的文件描述符个数,失败返回-1并设置errno。

epoll_wait函数如果检测到事件,就将所有就绪事件从内核事件表(由epfd参数指定)中复制到第二个参数event指向的数组中。

 

LT电平触发,应用程序可以不立即处理,但是应用程序下次调用epoll_wait时,还会再次通告此事件。

ET边沿触发,应用程序必须立即处理该事件。

 

边缘触发可能造成饥饿

边缘模式在什么情况下可能会造成饥饿。我们考虑一个应用,从外来连接接收数据,然后对数据进行处理。如果用边缘触发处理,对一个套接字就需要循环读取,直到没有数据可读为止(通过返回EAGIN,实际上如果读取的数据小于提供的缓冲大小也可以做这样的断定)。如果其中一个连接源源不断的发送数据,这个套接口的读循环无法退出,导致其它连接没有机会被处理。

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<pthread.h>
#include<stdbool.h>const int MAX_EVENT_NUMBER = 1024;
const int BUFFER_SIZE = 10;
//将文件描述符设置成非阻塞的
int setnonblocking(int fd)
{int old_option=fcntl(fd,F_GETFL);int new_option= old_option | O_NONBLOCK;fcntl(fd,F_SETFL,new_option);return old_option;
}
// 将文件描述符fd上EPOLLIN注册到epollfd指示的epoll内核事件表中,参数的enable_et指定是否对fd启动ET模式
void addfd(int epollfd, int fd,  bool enable_et)
{struct epoll_event event;event.data.fd=fd;event.events=EPOLLIN;if(enable_et){event.events |= EPOLLET;}epoll_ctl(epollfd,EPOLL_CTL_ADD, fd, &event);setnonblocking(fd);
}
// lt模式工作流程
void lt(struct epoll_event* events, int number, int epollfd, int listenfd)
{char buf[BUFFER_SIZE];for(int i=0;i<number;i++){int sockfd=events[i].data.fd;if(sockfd==listenfd){struct sockaddr_in client_address;socklen_t client_addrlength=sizeof(client_address);int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);addfd(epollfd,connfd,false);}else if(events[i].events & EPOLLIN){// 只要socket读缓存中还有未读出的数据,代码就被触发printf("event trigger once\n");memset(buf,'\0',BUFFER_SIZE);int ret=recv(sockfd,buf,BUFFER_SIZE-1,0);if(ret<=0){close(sockfd);continue;}printf("get %d bytes of content: %s\n",ret,buf);}else{printf("something else happened \n");}}
}
// et模式的工作流程
void et(struct epoll_event* events, int number, int epollfd,  int listenfd)
{char buf[BUFFER_SIZE];for(int i=0;i<number;i++){int sockfd=events[i].data.fd;if(sockfd==listenfd){struct sockaddr_in client_address;socklen_t client_addrlength=sizeof(client_address);int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);addfd(epollfd,connfd,true);//对connfd开启et模式}else if (events[i].events & EPOLLIN){// 这段代码不会重复触发,所以循环读数据printf("event trigger once\n");while(1){memset(buf,'\0',BUFFER_SIZE);int ret=recv(sockfd,buf,BUFFER_SIZE-1,0);if(ret<0){// 对于非阻塞IO,下面条件成立表示数据全部读取完毕,此后epoll就能再次触发sockfd上的EPOLLIN事件,以驱动下一次读操作if((errno==EAGAIN) || (errno==EWOULDBLOCK)){printf("read later\n");break;}close(sockfd);break;}else if(ret==0){close(sockfd);}else{printf("get %d bytes of content: %s\n",ret,buf);} }}else{printf("something else happened \n");}  }
}
int main(int argc,char* argv[])
{if(argc<=2){printf("usage: %s ip_address port_number\n",basename(argv[0]));return 1;}const char* ip=argv[1];int port=atoi(argv[2]);int ret=0;struct sockaddr_in address;bzero(&address,sizeof(address));address.sin_family=AF_INET;inet_pton(AF_INET,ip,&address.sin_addr);address.sin_port=htons(port);int listenfd=socket(PF_INET,SOCK_STREAM,0);assert(listenfd>=0);ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));assert(ret!=1);ret=listen(listenfd,5);assert(ret!=-1);struct epoll_event events[MAX_EVENT_NUMBER];int epollfd=epoll_create(5);assert(epollfd!=-1);addfd(epollfd,listenfd,true);while(1){int ret=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);if(ret<0){printf("epoll failure\n");break;}lt(events,ret,epollfd,listenfd);//使用LT模式// et(events,ret,epollfd,listenfd);//使用ET模式}close(listenfd);return 0;
}

 

  相关解决方案