参考

  1. 《TCP/IP网络编程》 尹圣雨

epoll

epoll也是Linux下实现I/O复用的一种方法,其性能优于select。

基于select的I/O复用服务器的设计缺陷

  1. 调用select函数后,针对所有文件描述符的循环语句。调用select函数后,需要观察作为监视对象的fd_set变量的变化,找出变化的文件描述符,因此需要针对所有监视对象的循环语句。

  2. 作为fd_set变量会发生变化,调用select函数前需要复制并保存原有信息,并在每次调用select函数时传递新的监视对象。

其中,第二点对性能影响最大。因为,select函数是监视套接字的变化的,而套接字是由操作系统管理的,向操作系统传递数据将对程序造成很大负担,而且无法通过优化代码解决。

解决select函数的缺点,可以仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。Linux的处理方式是epoll,Windows则是IOCP。但是当服务器端接入者少,或程序应具有兼容性时,select还是较好的选择。

epoll的使用

epoll的优点与select的缺点相反:

  1. 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句
  2. 调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息

epoll服务器端实现需要3个函数:

  1. epoll_create:创建保存epoll文件描述符的空间
  2. epoll_ctl:向空间注册并注销文件描述符
  3. epoll_wait:与select函数类似,等待文件描述符发生变化

使用epoll前需要验证Linux内核版本,epoll是从Linux的2.5.44版内核开始引入的。可以通过如下命令验证:

cat /proc/sys/kernel/osrelease

另外,epoll函数的使用还利用了一种结构体:

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

声明足够大的epoll_event结构体数组后,传递给epoll_wait函数,发生变化的文件描述符信息将被填入该数组。epoll_event结构体也可以在epoll例程中注册文件描述符,用于注册关注的事件。

epoll_create

#include <sys/epoll.h>int epoll_create(int size);

成功时返回epoll文件描述符,失败时返回-1。调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”,该文件描述符主要用于区分epoll例程,需要终止时,也要调用close函数;通过参数size传递的值并非用来决定epoll例程的大小,而仅供操作系统参考。

Linux 2.6.8之后的内核将完全忽略传入epoll_create函数的size参数,因为内核会根据情况调整epoll例程的大小。

epoll_ctl

利用epoll_ctl在epoll例程内注册监视对象文件描述符。

#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

成功时返回0,失败时返回-1。其中,epfd为用于注册监视对象的epoll例程的文件描述符;op用于指定监视对象的添加、删除或更改等操作;fd为需要注册的监视对象的文件描述符;event为监视对象的事件类型。

第二个参数传递的常量及含义:

  1. EPOLL_CTL_ADD:将文件描述符注册到epoll例程
  2. EPOLL_CTL_DEL:从epoll例程中删除文件描述符(此时第四个参数传递NULL,但Linux 2.6.9之前不行)
  3. EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况

epoll_event中的成员events中可以保存的常量及其所指的事件类型:

  1. EPOLLIN:需要读取数据的情况
  2. EPOLLOUT:输出缓冲为空,可以立即发送数据的情况
  3. EPOLLPRI:收到OOB数据的情况
  4. EPOLLDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用
  5. EPOLLERR:发生错误的情况
  6. EPOLLET:以边缘触发的方式得到事件通知
  7. EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件

epoll_wait

#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

成功时返回发生事件的文件描述符数,失败时返回-1。其中epfd为epoll例程的文件描述符;events为保存发生事件的文件描述符集合的结构体地址值,其所指缓冲需要动态分配;maxevents为第二个参数中可以保存的最大事件数;timeout是以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件。

基于epoll的回声服务器端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *buf);int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;socklen_t adr_sz;int str_len, i;char buf[BUF_SIZE];struct epoll_event *ep_events;struct epoll_event event;int epfd, event_cnt;if (argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){error_handling("bind() error");}if (listen(serv_sock, 5) == -1){error_handling("listen() error");}epfd = epoll_create(EPOLL_SIZE);                                                      // 创建例程ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);event.events = EPOLLIN;event.data.fd = serv_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);                                    // 注册服务端while (1){event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);if (event_cnt == -1){puts("epoll_wait() error");break;}for (i = 0; i < event_cnt; i++){if (ep_events[i].data.fd == serv_sock)                                        // 说明有客户端连接{adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);event.events = EPOLLIIN;event.data.fd = clnt_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);                        // 注册客户端printf("connected client: %d \n", clnt_sock);}else{str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);if (str_len == 0)                                                         // 数据接收完毕{epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);close(ep_events[i].data.fd);printf("closed client: %d \n", ep_events[i].data.fd);}else{write(ep_events[i].data.fd, buf, str_len);}}}}close(serv_sock);close(epfd);return 0;
}void error_handling(char *buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

条件触发和边缘触发

条件触发和边缘触发的区别在于发生事件的时间点。条件触发方式中,只要输入缓冲中还剩有数据,就将以事件方式再次注册;边缘触发中输入缓冲收到数据时仅注册1次该事件。

epoll默认以条件触发工作,可以将调用read函数时使用的缓冲大小缩减验证这一点。select函数也是以条件触发的方式工作的。

实现边缘触发的服务器端

边缘触发方式中,接收数据时仅注册1次该事件。一旦发生相关事件,就应该读取输入缓冲中的全部数据。因此需要验证输入缓冲是否为空。为此,需要使用errno变量,read函数发现输入缓冲中没有数据可读时返回-1,同时在errno中保存EAGAIN常量。

为了访问errno变量,需要引入error.h头文件,因此次头文件中有errno变量的extern声明。

另外,边缘触发方式下,以阻塞方式工作的read&write函数有可能引起服务器的长时间停顿。因此,边缘触发方式中一定要采用非阻塞read&write函数。为此,需要将套接字变成非阻塞模式。Linux提供了更改或读取文件属性的方法fcntl()

#include <fcntl.h>
int fcntl(int filedes, int cmd, ...);

成功时返回cmd参数相关值,失败时返回-1。其中filedes为属性更改目标的文件描述符;cmd用于表示函数调用的目的

fcntl具有可变参数的形式。如果向第二个参数传递F_GETFL,可以获得第一个参数所指的文件描述符属性(int型)。反之,如果传递F_SETFL,可以更改文件描述符的属性。可以按如下方式将文件(套接字)改为非阻塞模式:

int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag|O_NONBLOCK);

通过第一条语句获取之前设置的属性信息,通过第二条语句在此基础上添加非阻塞O_NONBLOCK标志。调用read&write函数时,无论是否存在数据,都会形成非阻塞文件(套接字)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>#define BUF_SIZE 4
#define EPOLL_SIZE 50
void setnonblockingmode(int fd);
void error_handling(char *buf);int main(int argc, char *argv[])
{int serv_sock, clnt_sock;struct sockaddr_in serv_adr, clnt_adr;socklen_t adr_sz;int str_len, i;char buf[BUF_SIZE];struct epoll_event *ep_events;struct epoll_event event;int epfd, event_cnt;if (argc != 2){printf("Usage : %s <port>\n", argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1){error_handling("bind() error");}if (listen(serv_sock, 5) == -1){error_handling("listen() error");}epfd = epoll_create(EPOLL_SIZE);                                                      // 创建例程ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);setnonblockingmode(serv_sock);event.events = EPOLLIN;event.data.fd = serv_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);                                    // 注册服务端while (1){event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);if (event_cnt == -1){puts("epoll_wait() error");break;}puts("return epoll_wait");for (i = 0; i < event_cnt; i++){if (ep_events[i].data.fd == serv_sock)                                        // 说明有客户端连接{adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);setnonblockingmode(clnt_sock);                                            // 将套接字改为非阻塞模式event.events = EPOLLIIN|EPOLLET;                                          // 将事件注册方式改为边缘触发event.data.fd = clnt_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);                        // 注册客户端printf("connected client: %d \n", clnt_sock);}else{while(1){str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);if (str_len == 0)                                                         // 数据接收完毕{epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);close(ep_events[i].data.fd);printf("closed client: %d \n", ep_events[i].data.fd);break;}else if (str_len < 0){if (errno == EAGAIN)                                                 // 读取了输入缓冲中的全部数据{break;}}else{write(ep_events[i].data.fd, buf, str_len);}}}}}close(serv_sock);close(epfd);return 0;
}void setnonblockingmode(int fd)
{int flag = fcntl(fd, F_GETFL, 0);fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}
void error_handling(char *buf)
{fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

条件触发与边缘触发孰优孰劣

边缘触发可以分离接收数据和处理数据的时间点。即使输入缓冲收到数据(注册相应事件),服务器端也能决定读取和处理这些数据的时间点,这样就给服务器端的实现带来巨大的灵活性

条件触发虽然也可以区分数据接收和处理,但在输入缓冲收到数据的情况下,如果不读取(延迟处理),则每次调用epoll_wait函数时都会产生相应事件。而且事件数也会累加,服务器无法承受

条件触发和边缘触发的区别主要应该从服务器端实现模型的角度谈论。从实现模型的角度看,边缘触发更有可能带来高性能,但不能简单地认为,只要使用边缘触发就一定能提高速度

网络编程——epoll相关推荐

  1. linux线程同步 epoll,Linux网络编程--epoll 模型原理详解以及实例

    1.简介 Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数.Linux 2.6内核中有提高网络I/O性能的新方法,即epoll . epoll是什么?按 ...

  2. socket网络编程--epoll小结

    http://www.cnblogs.com/wunaozai/p/3895860.html 以前使用的用于I/O多路复用为了方便就使用select函数,但select这个函数是有缺陷的.因为它所支持 ...

  3. 网络编程——epoll模型编程

    文章目录 目的 内容 代码及测试结果 (1) 使用条件触发方式实现回声服务器端(及客户端): (2) 使用边缘触发方式实现回声服务器端(及客户端): (3) 实现聊天服务器端,使其可以在连接到服务器端 ...

  4. linux网络编程 epoll水平触发、边沿触发、反应堆模型、线程池思想

    打开文件上限的设置可修改配置文件: etc/security/limits.conf 水平.边沿触发: 若客户端发送1000B数据,服务器一次只读500B.在水平触发的模式下,服务器会再调用一次epo ...

  5. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll、Epoll模型处理长连接性能比较

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--模型比较>一文中,我们分析了各种模型在处理短连接时的能力.本文我们将讨论处理长连接时各个模型的性能.(转载请指明出于b ...

  6. 朴素、Select、Poll和Epoll网络编程模型实现和分析——模型比较

    经过之前四篇博文的介绍,可以大致清楚各种模型的编程步骤.现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客) 模型编程步骤对比 <朴素.Select.Poll和Ep ...

  7. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Epoll模型

    在阅读完<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>和<朴素.Select.Poll和Epoll网络编程模型实现和分析--Poll模型> ...

  8. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Poll模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--Select模型>中,我们分析了它只能支持1024个连接同时处理的原因.但是在有些需要同时处理更多连接的情况下,102 ...

  9. 朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型

    在<朴素.Select.Poll和Epoll网络编程模型实现和分析--朴素模型>中我们分析了朴素模型的一个缺陷--一次只能处理一个连接.本文介绍的Select模型则可以解决这个问题.(转载 ...

最新文章

  1. css图片和文字一样高,css里图片和文字如何等高
  2. 泛型Generics
  3. bzoj 2705: [SDOI2012]Longge的问题——欧拉定理
  4. 数据结构学习官方代码
  5. 日期插件rolldate.js的使用
  6. DefenseCode ThunderScan 静态代码审计工具
  7. oracle中慢sql优化思路
  8. 6.解决AXIOS的跨域问题
  9. MongoDB密码设置(基于windows)
  10. PGIS 天地图主题颜色背景修改 图片 filter 蓝色 HTML CSS IMG filter 颜色矩阵在线计算 RGBA转换 SVG  feColorMatrix
  11. mysql 多条件求和_技巧|多条件查询求和,你会几种?
  12. arcgis 你必须有许可证才能使用此activex控件
  13. 安全专业委员会发言_护理专业委员会发言稿
  14. XjhDemo 插入数据
  15. 能力配不上位置,管理只能“累死”
  16. amd k14主板参数_看U选主板:AMD A8-5600K主板怎么选
  17. javaweb+themeleaf+Tomcat学习——org.thymeleaf.exceptions.TemplateProcessingException
  18. hadoop与传统数据库的区别
  19. cefsharp winform 支持视频播放
  20. Android Studio下载gitLab项目

热门文章

  1. 矩阵的定义——MATLAB
  2. Ng深度学习笔记-卷积神经网络-目标检测
  3. 人工智能几乎会影响到 IT 行业的每个方面,包括编程和开发
  4. java解析XML saxReader.read(xml) 错误:org.dom4j.DocumentException: no protocol
  5. python中各种属性与方法深析
  6. C++ 函数指针 指向类成员函数
  7. #Paper reading#DeepInf: Social Influence Prediction with Deep Learning
  8. java抽象类中的变量修饰符_菜鸟整理的三个权限修饰符与接口、抽象类的总结...
  9. ssh登录ubuntu
  10. 静态编译,动态编译,静态库,动态库的区别