《UNP》随笔——epoll的理解及应用

《UNP》随笔——epoll的理解及应用,第1张

《UNP》随笔——epoll的理解及应用

文章目录
    • 1. 为什么要使用epoll?
      • 1.1 基于select的I/O复用技术速度慢的原因
    • 2. epoll函数的介绍
      • 2.1 epoll_create
      • 2.2 epoll_ctl
      • 2.3 epoll_wait
    • 3. epoll的使用
      • 3.1 epoll总结
    • 4. 条件触发和边缘触发

1. 为什么要使用epoll?

实现I/O复用的传统方法有select和poll,但这两者都无法实现同时接入上百个客户端,所以select并不适合当前以Web服务端开发为主流的现代开发环境,于是就产生了epoll。epoll是linux平台所支持的,在Windows平台对应支持的是IOCP。

1.1 基于select的I/O复用技术速度慢的原因
  • 调用select函数后,需要对所有文件描述符执行循环检查语句。
  • 每次调用select函数时都需要向该函数传递监视对象信息。

循环语句不是最大的麻烦,每次传递监视对象信息到 *** 作系统中才是最大的麻烦。
应用程序向 *** 作系统传递数据将对程序造成很大负担,而且无法通过优化代码解决,因此将成为性能上的致命弱点。
select函数文件描述符有关,更准确地说,是监视套接字变化的函数。而套接字是由 *** 作系统管理的,所以select函数绝对需要借助于 *** 作系统才能完成功能。select函数的这一缺点可以通过如下方式弥补:

"仅向 *** 作系统传递1 次监视对象,监视范围或内容发生变化时只通知发生变化的事项。”

这样就无需每次调用select函数时都向 *** 作系统传递监视对象信息,但前提是 *** 作系统支持这种处理方式(每种 *** 作系统支持的程度和方式存在差异)。Linux的支待方式是epoll, Windows的支持方式是IOCP 。

select 的优点
epoll方式只在Linux下提供支持,也就是说,改进的I/O复用模型不具有兼容性。相反,大部分 *** 作系统都支持select函数。只要满足或要求如下两个条件, 即使在Linux平台也不应拘泥于epoll 。

  • 服务器端接入者少。
  • 程序应具有兼容性。
2. epoll函数的介绍

epoll函数具有如下优点,这些优点正好与之前的select函数缺点相反。

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

epoll服务器端实现中需要的3个函数,

  • epoll_ create: 创建保存epoll文件描述符的空间。
  • epoll_ctl: 向空间注册并注销文件描述符。
  • epoll_wait: 与select函数类似,等待文件描述符发生变化。

select和epoll的不同

select方式

  • 用fd_set变量保存监视对象文件描述符。
  • 用FD_SET,FD_CLR添加和删除对象。
  • select函数等待文件描述符的变化。
  • 通过FD_ISSET查看状态变化的文件描述符。

epoll方式

  • *** 作系统负责保存监视对象文件描述符,因此需要向 *** 作系统请求创建保存文件描述符的空间。
  • 通过epoll_ctl 函数请求 *** 作系统添加和删除对象。
  • epoll_wait函数等待文件描述符的变化。
  • 通过结构体epoll_event将发生变化的文件描述符单独集中到一起。

epoll_event结构体:

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_even结构体数组后,传递给epoll_wait函数时.发生变化的文件描述符信息将被填入该数组。因此, 无需像select函数那样针对所有文件描述符进行循环。

2.1 epoll_create
#include 
int epoll_create(int size);

调用epoll_creat函数创建的文件描述符保存空间称为“epoll例程”,有些情况下名称会不同。通过传递size参数来决定空间大小,但这个值只是给 *** 作系统一个建议,换言之,size并不能决定epoll的大小。

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

epoll_creat函数创建的资源与套接字相同,也由 *** 作系统管理。因此,此函数和创建套接字的情况相同,也会返回文件描述符。需要终止时,与其他文件描述符相同,也要调用close函数。

2.2 epoll_ctl

生成例程后,应在其内部注册监视对象文件描述符,此时使用epoll_ctl函数。

#include 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);
//成功时返回0 , 失败时返回-1 。
#epfd   用千注册监视对象的epoll例程的文件描述符。
#op     用千指定监视对象的添加、删除或更改等 *** 作。
#fd     需要注册的监视对象文件描述符。
#event  监视对象的事件类型。

该函数看起来复杂,但是调用语句就很容易理解,假设按如下形式调用epoll_ctl函数:

epoll_ctl(A, EPOLL_CTL_ADD, B, C);

第二个参数 EPOLL_CTL_ADD 意味着“添加”,上述语句有如下意义:

epoll例程A中注册文件描述符B,主要目的是为了监视参数C中的事件。

再介绍一个调用语句。

epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);

上述语句中第二个参数意味着这是一条删除语句,其含义为:

从epoll例程A中删除文件描述符B

从示例可以看出,从监视对象中删除文件描述符时,不需要监视类型参数,因此可以向第四个参数传递NULL。

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

  • EPOLL_CTL_ADD: 将文件描述符注册到epoll例程
  • EPOLL_CTL_DEL: 从epoll例程中删除文件描述符。
  • EPOLL CTL MOD: 更改注册的文件描述符的关注事件发生情况。

epoll_event结构体用于保存事件的文件描述符集合,但也可以在epoll例程中注册文件描述符时,用于注册监视事件。如下:

struct epoll_event event;
event.events=EPOLLIN; //发生需要读取数据的情况(事件) 时
event.data.fd=sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);

上述代码将epfd注册到epoll例程epfd中,并在需要读取数据的情况下产生相应事件。下面是epoll_event的成员events中可以保存的常量及所指的事件类型。

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

可以通过位运算同时传递多个上述参数,如EPOLLIN | EPOLLOUT

2.3 epoll_wait

下面是函数原型:

#include 
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
//成功时返回发生事件的文件描述符数,失败时返回-1。
 
#epfd      表示事件发生监视范围的epoll例程的文件描述符。
#events    保存发生事件的文件描述符集合的结构体地址值。
#maxevents 第二个参数中可以保存的最大事件数。
#timeout   以1/1000秒为单位的等待时间,传递-1 时, 一直等待直到发生事件。

该函数调用方式如下。需要注意的是,第二个参数所指缓冲需要动态分配。

int event_cnt;
struct epoll_event * ep_events;
ep_events = malloc(sizeof(struct epoll_event}*EPOLL_SIZE); //EPOLL_SIZE 是宏常量
event cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);

调用函数后,返回发生事件的文件描述符,同时在第二个参数指向的缓冲中保存发生事件的文件描述符集合。因此,无需像select一样插入针对所有文件描述符的循环。

3. epoll的使用

客户端:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *message);

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  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 

编译运行:

3.1 epoll总结

epoll使用的流程:

  1. epoll_create创建一个保存epoll文件描述符的空间,可以没有参数
  2. 动态分配内存,给将要监视的epoll_wait
  3. 利用epoll_ctl控制、添加、删除,
  4. 利用epoll_wait来获取改变的文件描述符,来执行程序。
    select和epoll的区别:
  • 每次调用select函数都会向 *** 作系统传递监视对象信息,浪费大量时间
  • epoll仅向 *** 作系统传递一次监视对象,监视范围或内容发生改变时只通知发生变化的事项。
4. 条件触发和边缘触发

epoll对文件描述符有两种模式:条件触发(Level Trigger),边缘触发(Edge Trigger)。
条件触发和边缘触发

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4663707.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-06
下一篇 2022-11-06

发表评论

登录后才能评论

评论列表(0条)

保存