【Linux网络编程学习】I/O多路复用——epoll

此为牛客Linux C++课程和黑马Linux系统编程笔记。

1. 关于epoll

epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

目前epoll是linux大规模并发网络程序中的热门首选模型。

epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

2. epoll API介绍

2.1 创建epoll实例:epoll_create

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

功能:在内核中创建一个新的epoll实例,并返回一个操纵该epoll的文件描述符,这个文件描述符和真正的文件没有关系,仅仅是为了后续调用epoll而创建的。该函数调用后在内核中创建了一个存储事件的数据结构,这个数据结构中有两个比较重要的子结构,一个是需要检测的文件描述符的信息(使用红黑树实现),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(使用双向链表实现),关于epoll更详细的内部实现在这里不详细讨论。

参数:size : 自从linux2.6.8之后,size参数是被忽略的。随便写一个数,必须大于0。

返回值:
-1 : 失败
> 0 : 用于操作epoll实例的文件描述符

2.2 注册epoll的监听事件:epoll_ctl

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

功能:向内核中的epoll实例中添加、修改、移除事件。epoll和select的一个显著区别就在这里:select是在监听事件时告诉内核要监听什么类型的事件,而epoll是在这里先注册要监听的事件类型,然后再调用epoll_wait监听。

参数:

  • epfd : epoll实例对应的文件描述符
  • op : 要进行什么操作
    EPOLL_CTL_ADD: 添加
    EPOLL_CTL_MOD: 修改
    EPOLL_CTL_DEL: 删除
  • fd : 要检测的文件描述符
  • event : 检测文件描述符什么事件,这里涉及到epoll_event,定义如下:
struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
};typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;

这里我们只需要关注两个字段即可:eventsdata.fd

其中events表示要检测的事件,有以下选择:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

其中data.fd表示该事件对应的socket的文件描述符。

返回值:

  • 成功,返回发送变化的文件描述符的个数 > 0
  • 失败 -1

2.3 监听事件:epoll_wait

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

功能:等待已注册的事件发生,返回事件的数目,并将已触发的事件写入events数组(第二个参数)中。

参数:

  • epfd : epoll实例对应的文件描述符
  • events : 传出参数,保存了发送了变化的文件描述符的信息,需要调用者先创建好
  • maxevents : 第二个参数结构体数组的大小
  • timeout : 阻塞时间
    • 0 : 不阻塞
    • -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞
    • > 0 : 阻塞的时长(毫秒)

返回值:

  • 成功,返回发送变化的文件描述符的个数 > 0
  • 失败 -1

3. 示例程序

以下用epoll实现了一个简单的服务端,把客户端传来的小写字母转换成大写字母并传回给客户端。

/*用epoll实现一个简单的服务器-客户端通信*/#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <pthread.h>
#include <strings.h>
#include <sys/epoll.h>// 设定一个服务器端口号
#define SERV_IP "127.0.0.1"
#define SERV_PORT 9999int main()
{int lfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT); // 注意转化成网络字节序inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 注意转化成网络字节序bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));listen(lfd, 128);int epfd = epoll_create(100); // 内核创建epoll实例struct epoll_event epev;epev.events = EPOLLIN;        // 要检测lfd的读事件epev.data.fd = lfd;// 注册了对lfd的监听,此后如果不删除这个注册信息,每次调用epoll_wait都将监听lfd的读事件(也就是客户端的连接)epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev); struct epoll_event epevs[1024]; // 用作epoll_wait的第二个参数(传出参数) while(1) {int ret = epoll_wait(epfd, epevs, 1024, -1); // 监听已注册的事件,最后一个参数-1表示阻塞等待if(ret == -1) {perror("epoll_wait error");exit(-1);}// 一旦走到这里说明解除了阻塞,就是指epoll监测到了事件的发生,遍历每个事件:for(int i = 0; i < ret; ++i) {int curfd = epevs[i].data.fd; // 表示当前触发的事件对应的fdif(curfd == lfd) { // 如果监听到lfd的读事件了,说明有一个新客户端建立连接struct sockaddr_in clie_addr;int clie_addr_len = sizeof(clie_addr); int cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_addr_len);char clie_IP[BUFSIZ];printf("Client IP: %s, client port: %d connected\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));epev.events = EPOLLIN;        // 要检测cfd的读事件epev.data.fd = cfd;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); // 把对该cfd的读事件监听注册上,以后epoll会同时监听lfd和cfd} else {   // 说明检测到的是某个cfd的读事件,读该客户端传来的数据char buf[BUFSIZ] = {0};int len = read(curfd, buf, sizeof(buf));if(len > 0) {// 小写转大写int i;for(i = 0; i < len; ++i) {if(buf[i] >= 'a' && buf[i] <= 'z') {buf[i] -= 32;}}write(curfd, buf, len); // 写回给客户端write(STDOUT_FILENO, buf, len);} else if(len == 0) {// 说明读完了,客户端已关闭,此时epoll已经没有必要继续监听该cfd了epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);close(curfd);} else {perror("read error");exit(-1);}}}}close(lfd);close(epfd); // 别忘了关epfdreturn 0;
}

4. epoll的两种触发方式

EPOLL事件有两种模型:

  • Edge Triggered (ET) 边缘触发:只有数据到来才触发,不管缓存区中是否还有数据。
  • Level Triggered (LT) 水平触发:只要有数据都会触发。

LT(level - triggered)是缺省的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。

ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

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

关于LT和ET的详细介绍,以及为什么ET模式要搭配非阻塞IO,见这篇博客,写的极好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384554.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux网络编程学习】阻塞、非阻塞、同步、异步以及五种I/O模型

文章目录1. 基本概念1.1 阻塞与非阻塞1.2 同步与异步1.3 为什么没有“异步阻塞”2. 五种IO模型2.1 阻塞 blocking2.2 非阻塞 non-blocking2.3. IO复用&#xff08;IO multiplexing&#xff09;2.4 信号驱动&#xff08;signal-driven&#xff09;2.5 异步&#xff08;asynchron…

LRU缓存 数据结构设计(C++)

做LeetCode第146题LRU缓存&#xff0c;觉得收获不小&#xff0c;特此记录。 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存。int get(int key) 如果关键…

STM32时钟树解析

本人之前其实也用STM32做过一些小东西&#xff0c;但因为时钟的初始化一般是直接在SystemInit时钟系统初始化函数里直接配置为72MHz&#xff0c;所以对于STM32的时钟框图并没有怎么理会&#xff0c;今天刚好有空就重新看了一下并写一篇博客记录一下吧&#xff0c;以免以后又忘了…

S3C2440时钟体系

S3C2440在默认情况下&#xff0c;整个系统全靠一个12MHz的外部晶振提供频率来工作运行的&#xff0c;也就是说CPU、内存、UART、ADC等所有需要用到时钟频率的硬件都工作在12MHz下&#xff0c;但是通过查阅芯片手册我们知道CPU时钟最高可为400MHZ&#xff0c;那么怎么设置时钟让…

关于MCU、CPU扩展SDRAM的一个小知识

像上图这种硬件电路图上的16个数据位和我们在初始化SDRAM的时候设置的16位数据位宽是指我们读写SDRAM的时候可以同时读写16个数据位&#xff0c;数据线越多肯定越快&#xff0c;但是数据线也不可能无限增加&#xff0c;我们在程序里是可以读写8位&#xff0c;16位&#xff0c;3…

S3C2440扩展SDRAM

本文主要目的是记录一下S3C2440扩展SDRAM的一些知识&#xff0c;方便以后查阅。 通过查阅手册我们知道&#xff0c;2440有8个可以用来扩展内存的BANK&#xff0c;其中第6和第7还可用来扩展SDRAM 下面我们来看一下2440扩展SDRAM需要设置哪些寄存器。 一、BWSCON寄存器 该寄存器…

汇编语言的相对跳转和绝对跳转以及反汇编代码解析

上图第一行的b1 main为相对跳转&#xff0c;即跳转到pcoffset,其中pc为当前pc值&#xff0c;offset可以理解为偏移地址&#xff0c;也就是根据当前所在地址加上偏移地址实现跳转&#xff0c;为相对跳转。 我们来看看它的反汇编代码 上图清除完bss区后使用b1指令跳转到30000668…

韦东山嵌入式第一期14课第004节_und异常模示程序示例_P笔记

本节课的第一个程序韦老师是想让大家见识一下未定义异常&#xff0c;而第二个程序是对第一个程序进行改进&#xff0c;防止在某些条件下执行不了&#xff0c;下面就来讲一下第2个程序改进了哪些地方并且有什么用。 程序在此路径中&#xff1a;源码文档图片\源码\源码_20180321…

关于NOR FLASH地址左右移的问题

问题引入&#xff1a;不知道你会不会有这样的疑问&#xff1a;为什么在发送解锁命令时&#xff0c;我们不用右移一位&#xff0c;而发送扇区地址时却要右移一位&#xff08;nor_cmd函数内部已经左移一位&#xff09;&#xff0c;这里先补充说明一下说明是cpu角度和nor角度&…

在linux下利用ls命令进行模糊查找

如上图&#xff0c;我们当前路径下有三个文件&#xff0c;分别为helloworld.c以及helloworld和1.c&#xff0c;直接输入命令ls则显示所有文件&#xff0c;我们可以利用ls 加*的方向进行模糊查找。 输入ls 目录名 形式的命令行&#xff0c;则是对该目录名下的文件全部进行显示&a…

Makefile常见符号意思

Makefile里有许许多多的符号&#xff0c;对于新手而言如果没有经常使用&#xff0c;就很容易忘记&#xff0c;所以我把常见符号的意义写下&#xff0c;方便日后忘记查询。本文章会持续更新... 1.$&#xff1a;代表目标&#xff1b;$^代表所有依赖&#xff0c;$^代表第一个依赖。…

Linux下串口通信详解

https://blog.csdn.net/u010783226/article/details/73369097

fstat、stat和lstat 区别

nt fstat(int filedes, struct stat *buf); int stat(const char *path, struct stat *buf); int lstat(const char *path, struct stat *buf); 一眼就能看出来fstat的第一个参数是和另外两个不一样的&#xff0c;fstat区别于另外两个系统调用的地方在于&#xff0c;fstat系…

Linux的帧缓冲设备

Linux的帧缓冲设备 帧缓冲&#xff08;framebuffer&#xff09;是 Linux 为显示设备提供的一个接口&#xff0c;把显存抽象后的一种设备&#xff0c;他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。这种操作是抽象的&#xff0c;统一的。用户不必关心物理显存的…

Linux下没有包含头文件(不知是哪个)导致编译无法通过的解决心得

最近写程序的时候编译出错了&#xff0c;提示信息为&#xff1a;invalid use of undefined type fb_var_screeninfo。显示根据英文知道是没有定义 fb_var_screeninfo这个类型&#xff0c;明显是缺少了某个头文件&#xff0c;但是缺少哪个头文件以及有什么又快又好的解决方法呢&…

gcc编译缺少数学库

Linux下编译出现以下提示可以在编译的后面加上-lm&#xff0c;例如&#xff0c;arm-none-linux-gnueabi-gcc -o example1 example1.c -lm&#xff0c;意思就是添加数学库的意思&#xff0c;编译就能通过了 example1.c:(.text0x3e8): undefined reference to cos example1.c:(.…

Linux编译程序时加-I指定头文件位置

Linux下编译出现以下错误&#xff0c;错误的原因是在/usr/local/arm/arm-2009q3/bin/../arm-none-linux-gnueabi/libc/usr/include/freetype/config/下找不到ftheader.h&#xff0c;而我到该目录下看&#xff0c;发现路径是这样的rootubuntu:/usr/local/arm/arm-2009q3/arm-non…

树莓派远程监控的实现

原文&#xff1a;https://blog.csdn.net/ayz123456/article/details/79252923 http://shumeipai.nxez.com/2016/09/01/raspberry-pi-motion-cameras-for-remote-monitoring.html https://blog.csdn.net/wto882dim/article/details/82195001 https://blog.csdn.net/qq_3950082…

公网访问树莓派

公网访问树莓派控制小车 上篇已经介绍了小车在局域网中的控制方法&#xff0c;比较简单&#xff0c;既然是远程遥控那就要能够进行公网访问&#xff0c;使得你的小车可以在任何有网络的地方都能访问到&#xff0c;并且后续还会加上摄像头&#xff0c;进行实时监控&#xff0c;想…