您现在的位置是:首页 > 正文

epoll总结

2024-01-30 20:54:10阅读 0

Epoll

epoll操作由三个函数构成:头文件为 #include <sys/epoll.h>

#include <sys/epoll.h>

int epoll_create(int size);

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

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

1. epoll_create函数
函数声明:int epoll_create(int size)
  创建一个epoll的句柄(文件描述符),size用来告诉内核这个epoll句柄能监听的socket fd个数。需要注意的是,当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2. epoll_ctl函数

函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。 
参数: 
epfd:由 epoll_create 生成的epoll专用的文件描述符; 
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除 

fd:关联的文件描述符; 
event:指向epoll_event的指针; 

第四个参数的数据结构:

typedef union epoll_data { 
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t; 

struct epoll_event {
__uint32_t events;  /* Epoll events */
epoll_data_t data;  /* User data variable */
};

如:
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

常用的事件类型:
  1. EPOLLIN :表示对应的文件描述符可以读;
  2. EPOLLOUT:表示对应的文件描述符可以写;
  3. EPOLLPRI:表示对应的文件描述符有紧急的数据可读
  4. EPOLLERR:表示对应的文件描述符发生错误;
  5. EPOLLHUP:表示对应的文件描述符被挂断;
  6. EPOLLET:表示对应的文件描述符有事件发生;

3. epoll_wait函数

函数声明:int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位ms);-1相当于阻塞,0相当于非阻塞。

return:返回在规定的时间内获取到IO数据的个数

epoll的工作模式

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

测试代码:
服务器代码:server.cpp

  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <string.h>
 11 #include <netinet/in.h>
 12 #include <sys/types.h>
 13 #include <sys/socket.h>
 14 #include <unistd.h>
 15 #include <sys/epoll.h>
 16 #include <arpa/inet.h>
 17 #include <iostream>
 18 #include <netdb.h>
 19 
 20 #define MAX_DATA_SIZE 4096
 21 #define SERVER_PORT 8000
 22 #define CON_QUEUE 20
 23 #define MAX_EVENTS 10
 24 
 25 void acceptConn(int sockfd, int epollfd);
 26 void recvHandle(int clientfd);
 27 
 28 int main()
 29 {
 30    int sockfd;
 31    struct sockaddr_in servaddr;
 32 
 33    if((sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
 34    {
 35      perror("创建socket错误");
 36      exit(-1);
 37    }
 38 
 39    //设置套接口为可重用状态
 40    int reuse = 1;  //0关闭 ,1 开启 
 41    if(-1 == ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)))
 42    {
 43       perror("不能设置套接口为可重用状态");
 44       ::close(sockfd);
 45       exit(-1);
 46    }
 47    //设置套接口发送接收缓冲,并且服务器的必须在accept之前设置
 48    socklen_t recv_size =  4 * 1024;
 49    if(-1 == ::setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_size, sizeof(recv_size)))
 50    {
 51        perror("设置套接口接收缓冲错误");
 52        ::close(sockfd);
 53        exit(-1);
 54    }
 55    if(-1 == ::setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &recv_size, sizeof(recv_size)))
 56    {
 57        perror("设置套接口接收缓冲错误");
 58        ::close(sockfd);
 59        exit(-1);
 60    }
 61 
 62    bzero(&servaddr, sizeof(servaddr));
 63    servaddr.sin_family = AF_INET;
 64    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 65    servaddr.sin_port = htons(SERVER_PORT);
 66 
 67    int ret = ::bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
 68    if(-1 == ret)
 69    {
 70        perror("bind 错误");
 71       ::close(sockfd);
 72       exit(-1);
 73    }
 74    if(::listen(sockfd,CON_QUEUE) == -1)
 75    {
 76        perror("监听失败!");
 77        ::close(sockfd);
 78        exit(-1);
 79    }
 80 
 81    //epoll 初始化
 82    int epollfd; //epoll 描述符
 83    struct epoll_event eventlist[MAX_EVENTS];
 84 
 85    epollfd = epoll_create(1);
 86    struct epoll_event event;
 87    event.events = EPOLLIN|EPOLLET;
 88    event.data.fd = sockfd;
 89    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) < 0)
 90    {
 91        printf("epoll 加入失败 fd:%d\n",sockfd);
 92        exit(-1);
 93    }
 94   
 95    printf("server init success!\n");
 96    while(1)
 97    {
 98       int timeout = 10000;
 99 
100       //epoll_wait处理
101       //ret会返回在规定的时间内获取到IO数据的个数,并把获取到的event保存在eventList中,注意在每次执行该函数时eventList都会清空,由epoll_wait函数填写。
102      //而不清除已经EPOLL_CTL_ADD到epollfd描述符的其他加入的文件描述符。
103     //epoll里面的文件描述符要手动通过EPOLL_CTL_DEL进行删除。
104       int ret = epoll_wait(epollfd, &eventlist[0], MAX_EVENTS, timeout);
105       if(ret < 0)
106       {
107           perror("epoll error\n");
108           break;
109       }
110      else if(ret == 0)
111      {
112          //超时
113          printf("epoll_wait 等待超时\n");
114          continue;
115      }
116      printf("ret = %d\n",ret); 
117      for(int i = 0; i < ret; ++i)
118       { 
119           if(eventlist[i].events & EPOLLERR || !(eventlist[i].events & EPOLLIN))
120           {
121               printf("epoll error");
122               ::close(eventlist[i].data.fd);
123               exit(-1);
124           }
125           
126           if(eventlist[i].data.fd == sockfd)
127           {
128              acceptConn(sockfd, epollfd);
129             // handle(eventlist[i].data.fd);
130           }
131 
132           else if(eventlist[i].events & EPOLLIN) //接收消息
133           {
134         //    acceptConn(sockfd, epollfd);
135               recvHandle(eventlist[i].data.fd);
136           }
137       } 
138 
139    }
140    ::close(epollfd);
141    ::close(sockfd);     
142    return 0;
143 }
144 
145 //接收客户端的连接,addr返回的地址,返回的客户端套接口
146 /*int accept(int sock, struct sockaddr_in *addr) 
147 { 
148     socklen_t len = sizeof(struct sockaddr_in); 
149     bzero(addr, sizeof(struct sockaddr_in)); 
150     struct epoll_event ev; 
151     int rc = epoll_wait(kdpfd, &ev, 1, 2000);
152     //这里kdpfd is the library function epoll_create( int size)的reture value.Here is called by epoll_create(1).You can man it.
153     if(1 == rc && (ev.events & EPOLLIN)) 
154         return TEMP_FAILURE_RETRY(::accept(sock, (struct sockaddr*)addr, &len)); 
155     return -1; 
156 }*/
157 
158 void acceptConn(int sockfd, int epollfd)
159 {
160    struct sockaddr_in cliaddr;
161    socklen_t len = sizeof(struct sockaddr_in);
162    bzero(&cliaddr,len);
163    int confd = ::accept(sockfd, (struct sockaddr*)&cliaddr, &len);
164    if(confd < 0)
165    {
166      printf("accept 错误\n");
167      exit(-1);
168    }
169    printf("sockfd:%d\n",sockfd);
170   	 /* else
171    	{
172      	  int v = getpeername(confd,(sockaddr*) &cliaddr,&len);
173      	  if(v == -1)
174           	 std::cout<<"获取客户端的地址和端口失败!"<<std::endl;
175      	  std::cout<<"address:"<<inet_ntoa(cliaddr.sin_addr)<<" port:"<<htons(cliaddr.sin_port)<<std::endl; 
176      	  int recvlen= 0;
177      	  char recvBuf[MAX_DATA_SIZE];
178       	 memset(recvBuf, 0, sizeof(recvBuf));
179      	  recvlen = ::recv(confd, (char*)recvBuf, MAX_DATA_SIZE, MSG_NOSIGNAL );
180     	   if(recvlen == 0)
181        	    return;
182      	  if(recvlen < 0)
183        	{
184         	   printf("server recv error\n");
185        	    exit(-1);
186       	 }
187       	 printf("接收到的数据:%s\n",recvBuf);
188 	   }*/
189         struct epoll_event event;
190         event.data.fd=confd;
191         event.events=EPOLLIN;
192         epoll_ctl(epollfd,EPOLL_CTL_ADD,confd,&event);
193 }
194 
195 void recvHandle(int clientfd)
196 {
197     struct sockaddr_in cliaddr;
198     socklen_t len = sizeof(struct sockaddr_in);
199     bzero(&cliaddr,len);
200 
201     int v = getpeername(clientfd,(sockaddr*) &cliaddr,&len);
202     if(v == -1)
203         std::cout<<"获取客户端的地址和端口失败!"<<std::endl;
204     std::cout<<"address:"<<inet_ntoa(cliaddr.sin_addr)<<" port:"<<htons(cliaddr.sin_port)<<std::endl; 
205 
206     int recvlen= 0;
207     char recvBuf[MAX_DATA_SIZE];
208     memset(recvBuf, 0, sizeof(recvBuf));
209     recvlen = ::recv(clientfd, (char*)recvBuf, MAX_DATA_SIZE, MSG_NOSIGNAL );   
210     if(recvlen == 0)
211         return;
212     if(recvlen < 0)
213     {
214         printf("server recv error\n");
215         exit(-1);
216     }
217     printf("接收到来自clientfd:%d的数据:%s\n", clientfd,recvBuf);
218     return;
219 }

客户端代码:client.cpp

  9 #include <stdio.h>
 10 #include <stdlib.h>
 11 #include <string.h>
 12 #include <netinet/in.h>
 13 #include <sys/types.h>
 14 #include <sys/socket.h>
 15 #include <unistd.h>
 16 #include <netdb.h>
 17 #include <error.h>
 18 #include <arpa/inet.h>
 19 #include <iostream>
 20 
 21 #define MAX_DATA_SIZE 4096
 22 #define SERVER_PORT 8000
 23 
 24 int main()
 25 {
 26    int sockfd;
 27    struct sockaddr_in servaddr;
 28    int pid;
 29    char sendBuf[MAX_DATA_SIZE],revBuf[MAX_DATA_SIZE];
 30    int send_size,recv_size;
 31 
 32    sockfd = ::socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
 33    bzero(&servaddr,sizeof(servaddr));
 34    servaddr.sin_family = AF_INET;
 35    servaddr.sin_port = htons(SERVER_PORT);
 36    servaddr.sin_addr.s_addr = inet_addr("192.168.97.246");
 37 
 38    if(::connect(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr_in)) == -1)
 39    {
 40       perror("connect 失败,请检查端口和ip地址!");
 41       exit(-1);
 42    }
 43 
 44    while(1)
 45    {
 46        printf("请输入:");
 47        fgets(sendBuf,MAX_DATA_SIZE,stdin);
 48        send_size = ::send(sockfd, sendBuf, MAX_DATA_SIZE, MSG_NOSIGNAL);
 49        if(send_size < 0)
 50        {
 51            perror("send error");
 52        }
 53        memset(sendBuf, 0, sizeof(sendBuf));
 54    }
 55    return 0;
 56 }

网站文章

  • 在Linux和Windows上安装sentinel-1.8.5

    在Linux和Windows上安装sentinel-1.8.5

    记录:380 场景:在CentOS 7.9操作系统上,安装sentinel-1.8.5。在Windows上操作系统上,安装sentinel-1.8.5。Sentinel是面向分布式、多语言异构化服务架构的流量治理组件。

    2024-01-30 20:54:03
  • 使用 sqoop从MySQL增量导出数据到hive

    使用 sqoop从MySQL增量导出数据到hive

    本文主要解释incremental参数下append模式和lastmodified模式的区别,想要了解具体其他参数请参考官方文档: http://sqoop.apache.org/docs/1.4.6...

    2024-01-30 20:53:33
  • 完美实现SpringBoot+Angular普通登录

    完美实现SpringBoot+Angular普通登录

    编辑:业余草来源:https://www.xttblog.com/?p=4881零本文基于《SpringBoot+Angular入门实例教程》第5.1节...

    2024-01-30 20:53:28
  • 交换-STP

    交换-STP

    2024-01-30 20:53:22
  • 有关《函数模板》的那些小知识-.-

    有关《函数模板》的那些小知识-.-

    本文介绍函数模板的概念、用途以及如何创建函数模板和函数模板的使用方法......函数模板定义的一般形式如下:之前我们知道的交换两个变量的方法有宏定义、函数,这两种方式都能实现两个变量的交换,但是各有各...

    2024-01-30 20:53:16
  • 辗转相除法求最大公约数

    辗转相除法求最大公约数

    由此可见,a和b都是d的倍数,所以d是a和b的最大公因数(最大是因为我们在第3步时就停止了分割线段,此时恰好整除,所以是最大)我们以及对求最大公因数有了形象上的了解,那么下面要做的就是将其写成代码。最...

    2024-01-30 20:52:47
  • Spring生命周期讲的比较好的文章

    https://www.jianshu.com/p/1dec08d290c1我画的流程图,参照:https://www.processon.com/view/link/5f915d30e401fd06fd9c0b96

    2024-01-30 20:52:35
  • html关系选择符,CSS关系选择器和属性选择器

    html关系选择符,CSS关系选择器和属性选择器

    1.1. 关系选择器关系选择器是通过元素之间的“位置关系的特征”来确定所选元素。1.1.1. 子代选择器:S1>S2语法:选择器1>选择器2匹配S1中的下一级S2。下一级就是“子级”,或子代。其中S1,S2都可以是独立使用的选择器(比如id选择器,class选择器,标签选择器等)。1.1.2. 后代选择器:S1 S2语法:选择器1 选择器2【派生选择器】匹配S1内部的所有后代S2。...

    2024-01-30 20:52:05
  • 本地MySQL数据库其他电脑局域网访问

    本地MySQL数据库其他电脑局域网访问

    一般情况下是访问不了的 ,需要改两个地方首先查看局域网本地ipipconfig第一步首先本地用 Navicat 本地连接 打开数据库 (MySQL)/user表把user字段为root的 Host字段改成%第二步打开Windows防火墙 点击高级设置步骤阅读点击进去 点入站规则在点击新建规则选择端口 进行下一步填入330...

    2024-01-30 20:52:00
  • Intel8086处理器使用NASM汇编语言实现操作系统04-实模式-屏幕显示不定长度的字符串(cmp/je)

    怀念二抱三抱

    2024-01-30 20:51:52