《TCP/IP网络编程》阅读笔记--epoll的使用
1--epoll的优点
select()的缺点:
① 调用 select() 函数后针对所有文件描述符的循环语句;
② 调用 select() 函数时需要向操作系统传递监视对象信息;
epoll()的优点:
① 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句;
② 调用 epoll_wait() 函数时无需每次传递监视对象信息;
2--epoll的常用操作
epoll_create: 创建保存 epoll 文件描述符的空间;
epoll_ctl: 向空间注册并注销文件描述符;
epoll_wait: 等待文件描述符发生变化;
#include <sys/epoll.h>
int epoll_create(int size);
// 成功时返回 epoll 文件描述符,失败时返回 -1
// size 表示epoll实例的大小,只是建议给操作系统的一个参考
// 调用 epoll_create 函数时创建的文件描述符保存空间称为: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 表示监视对象的事件类型struct epoll_event event;
...
event.events = EPOLLIN; // 发生需要读取数据的事件时
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
...
// event.events 常用的事件有:
// EPOLL_IN 表示需要读取数据的情况
// EPOLLOUT 表示输出缓冲为空,可以立即发送数据的情况
// EPOLLPRI 表示收到 OOB 数据的情况
// EPOLLRDHUP 表示断开连接或半关闭的情况,常用于边缘触发方式
// EPOLLERR 表示发生错误的情况
// EPOLLET 表示以边缘触发的方式得到事件通知
// EPOLLONESHOT 表示发生一次事件后,相应的文件描述符不再收到事件通知
// 通过位或运算可以同时传递上述多个参数 
第二个参数 op 的常见常量和含义如下:
① EPOLL_CTL_ADD: 将文件描述符注册到 epoll 例程;
② EPOLL_CTL_DEL: 从 epoll 例程中删除文件描述符;
③ EPOLL_CTL_MOD: 更改注册的文件描述符的关注事件;
epoll_ctl(A, EPOLL_CTL_ADD, B, C);
// 表示在 epoll 例程 A 中注册文件描述符 B,主要目的是监视参数 C 中的事件;epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);
// 表示在 epoll 例程 A 中删除文件描述符 B 
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
// 成功时返回发生事件的文件描述符数
// epfd 表示 epoll 例程的文件描述符
// events 表示保存发生事件的文件描述符集合的结构体地址值
// maxevents 表示第二个参数可以保存的最大事件数
// timeout 表示以 ms 为单位的等待事件,传递 -1 时表示一直等待直到事件发生 
3--基于 epoll 的回声服务端
// gcc echo_epollserv.c -o echo_epollserv
// ./echo_epollserv 9190#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 50void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}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); // 创建保存 epoll 文件描述符的空间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); // 将 serv_sock 注册到 epoll 例程中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){ // 当发生时间的文件描述符等于设置的 serv_sock 时adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);event.events = EPOLLIN;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){ // 接收的数据是 EOF,则关闭连接epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); // 收到EOF,删除注册的文件描述符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); // echo}}}}close(serv_sock);close(epfd);return 0;
} 
4--条件触发和边缘触发
条件触发和边缘触发的区别在于发生事件的时间点;
条件触发中,只要输入缓冲有数据就会一直通知该事件;
边缘触发中输入缓冲收到数据时仅注册 1 次该事件,即使输入缓冲中还留有数据,也不会再进行注册;
epoll 默认以条件触发的方式工作;
条件触发代码:
// gcc echo_EPLTserv.c -o echo_EPLT_serv
// ./echo_EPLT_serv 9190#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 4 // 减少缓冲大小,阻止服务器一次性读取接收的数据,验证条件触发
#define EPOLL_SIZE 50void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}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){// 条件触发中,每次收到客户端数据,都会调用 epoll_wait() 函数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);event.events = EPOLLIN;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); // 一次只能读取 4 个字节if(str_len == 0){ // 收到 EOF 关闭连接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); // echo}}            }}close(serv_sock);close(epfd);return 0;
}
 
在 event.events 中设置 EPOLLET 来设置边缘触发;
在边缘触发中,从客户端接收数据只会注册 1 次事件;
边缘触发可以分离接收数据和处理数据的时间点;
// gcc echo_EPETserv.c -o echo_EPETserv
// ./echo_EPETserv 9190#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 50void error_handling(char *message){fputs(message, stderr);fputc('\n', stderr);exit(1);
}void setnonblockingmode(int fd){int flag = fcntl(fd, F_GETFL, 0);fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}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 = EPOLLIN|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){ // 边缘触发中,接收数据仅注册 1 次事件,因此需要循环读取完输入缓冲中的所有数据str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);if(str_len == 0){ // 接收到 EOF 后,关闭连接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){ // read 函数发现输入缓冲中没有数据可读时返回 -1,同时errno中保存EAGAIN常量if(errno == EAGAIN){break;}}else{write(ep_events[i].data.fd, buf, str_len); // echo}}}}}close(serv_sock);close(epfd);return 0;
} 
相关文章:
《TCP/IP网络编程》阅读笔记--epoll的使用
1--epoll的优点 select()的缺点: ① 调用 select() 函数后针对所有文件描述符的循环语句; ② 调用 select() 函数时需要向操作系统传递监视对象信息; epoll()的优点: ① 无需编写以监视状态变化为目的的针对所有文件描述符的循环语…...
Python 递归函数
视频版教程 Python3零基础7天入门实战视频教程 在一个函数体内调用它自身,被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。 实例,求123…100的和,用递归实现。数学…...
Java实现计算两个日期之间的工作日天数
需求: 需要在后端实现 计算当前日期与数据库内保存的日期数据之间相隔的工作日数目 实现 import java.time.DayOfWeek; import java.time.LocalDateTime;public class WorkdaysCalculator {public static void main(String[] args) {String givenDateTimeStr &q…...
CS5817规格书|CS5817芯片参数|多功能便携式显示器方案芯片规格
CS5817支持最高4K 60Hz是集睿致远(ASL) 新推出的多功能显示控制器芯片,CS5817产品可应用于便携显示器、电竞显示器、桌面显示器、一体式台式机和嵌入式显示系统。 Type-C/DP/HDMI2.0输入转LVDS/eDP/VBO 芯片, 高度集成了多种输入输出接口, 并…...
2023面试知识点一
1、新生代和老年代的比例 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) 1/3 的堆空间大小。老年代 ( Old ) 2/3 的堆空间大小。其中,新生代 ( …...
【算法题】2856. 删除数对后的最小数组长度
题目: 给你一个下标从 0 开始的 非递减 整数数组 nums 。 你可以执行以下操作任意次: 选择 两个 下标 i 和 j ,满足 i < j 且 nums[i] < nums[j] 。 将 nums 中下标在 i 和 j 处的元素删除。剩余元素按照原来的顺序组成新的数组&…...
Java面向对象编程
在关系型是数据库中,有两个不同的事务同时操作数据库中同一表的同一行,不会引起冲突的是: A. 其中一个DELETE操作,一个是SELECT操作 B. 其中两个都是UPDATE C. 其中一个是SELECT,一个是UPDATE D. 其中一个SELECT E. 其…...
K8S:Yaml文件详解及编写示例
文章目录 一.Yaml文件详解1.Yaml文件格式2.YAML 语法格式 二.Yaml文件编写及相关概念1.查看 api 资源版本标签2.yaml编写案例(1)相关标签介绍(2)Deployment类型编写nginx服务(3)k8s集群中的port介绍&#x…...
去耦电路设计应用指南(一)MCU去耦设计介绍
(一)MCU去耦设计介绍 1. 概述2. MCU需要去耦的原因2.1 去耦电路简介2.2 电源噪声产生的原因2.3 插入损耗2.4 去耦电路简介 参考资料来自网上: 1. 概述 我们经常看到单片机或者IC电路管脚常常会放置一个或者多个陶瓷电容,他们主要…...
【c++】杂记
文章目录 预处理器constauto 预处理器 预处理器:运行于编译过程之前的一段程序 预处理变量:不属于命名空间std,由预处理器负责管理 const const对象一旦创建就不再改变 const对象必须初始化 const对象旨在文件内有效 ectern const int bufsizefun() /…...
简记:使用 Django Shell 清空 数据库表
简记 使用 Django Shell 清空所有数据库表 jcLee95的博客:https://blog.csdn.net/qq_28550263 本文地址:https://blog.csdn.net/qq_28550263/article/details/132862795 目 录 1. 描述2. 步骤备份重要数据进入 Django Shell输入脚本 1. 描述 由于历史的…...
Web项目测试
http: //localhost: 8080 /shop1/ 协议 服务器的地址 端口号 相应的代码文件或文件夹 127.0.0.1 (服务器所在的端口) Web项目测试:系统测试 Web项目测试要做什么类型:接口测试、功能测试、性能测试、兼容性测试、安全测试、界面测试、易…...
Springboot 集成 Ehcache 提示 Cannot find cache named ‘employee_all‘ for Builder
异常提示: java.lang.IllegalArgumentException: Cannot find cache named employee_all for Builder[public java.lang.Iterable org.bc.device.service.EmployeeService.findAll()] caches[employee_all] | key | keyGeneratorkeyGenerator | cacheManager | cac…...
pandas 笔记:shift
用于将数据系列或数据框中的数据按指定的位置移动。这对于某些时间序列分析特别有用,例如计算数据的变化量或滞后值 1 对Series/DataFrame数据进行移动 1.0 原始数据 import pandas as pd import numpy as np df1pd.DataFrame(np.arange(12).reshape(3,4),column…...
解密(2023寒假每日一题 20)
给定一个正整数 k k k ,有 k k k 次询问,每次给定三个正整数 n i , e i , d i n_i,e_i,d_i ni,ei,di ,求两个正整数 p i , q i p_i,q_i pi,qi ,使 n i p i q i , e i d i ( p i − 1 ) ( q i − 1 …...
如何实现Web应用、网站状态的监控?
如何实现Web应用、网站状态的监控? 关键词:网站监控,服务器监控,页面性能监控,用户体验监控本文通过代码分析、网站应用介绍网站状态监控的方式下文主要分为网站应用、技术实现两部分 一、网站应用 现在网络上已经存在一些Web网站监控的服务ÿ…...
手撕排序之堆排序
一、概念: 什么是逻辑结构、物理结构? 逻辑结构:是我们自己想象出来的,就像内存中不存在一个真正的树 物理结构(存储结构):实际上在内存中存储的形式。 堆的逻辑结构是一颗完全二叉树 堆的物理结构是一个数组 之…...
【奇想星球】重磅!我们的AIGC共创社区平台上线了!
文章目录 01 前言功能模块 02 相识缘起连接价值平台优势 03 奇想星球04 我们做了什么时间线 05 初心愿景06 可爱的小伙伴们后续开发及招募计划 07 结语 公众号原文链接 01 前言 2023年9月10日,我们的平台网站上线了! 奇想星球 | AIGC共创社区平台。网站地…...
2023年数维杯数学建模B题节能列车运行控制优化策略求解全过程文档及程序
2023年数维杯数学建模 B题 节能列车运行控制优化策略 原题再现: 在城市交通电气化进程快速推进的同时,与之相应的能耗增长和负面效应也在迅速增加。城市轨道交通中的快速增长的能耗给城轨交通的可持续性发展带来负担。2018 年,北京、上海、…...
Python--测试代码
目录 1、使用pip安装pytest 1.1 更新pip 1.2 安装putest 2、测试函数 2.1 单元测试和测试用例 2.2 可通过的测试 2.3 运行测试 2.4 未通过的测试 2.5 解决测试未通过 2.6 添加新测试 3、测试类 3.1 各种断言 3.2 一个测试的类 3.3 测试AnonymousSurvey类 3.4 使…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
云原生安全实战:API网关Envoy的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口,负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...
起重机起升机构的安全装置有哪些?
起重机起升机构的安全装置是保障吊装作业安全的关键部件,主要用于防止超载、失控、断绳等危险情况。以下是常见的安全装置及其功能和原理: 一、超载保护装置(核心安全装置) 1. 起重量限制器 功能:实时监测起升载荷&a…...
react菜单,动态绑定点击事件,菜单分离出去单独的js文件,Ant框架
1、菜单文件treeTop.js // 顶部菜单 import { AppstoreOutlined, SettingOutlined } from ant-design/icons; // 定义菜单项数据 const treeTop [{label: Docker管理,key: 1,icon: <AppstoreOutlined />,url:"/docker/index"},{label: 权限管理,key: 2,icon:…...
统计学(第8版)——统计抽样学习笔记(考试用)
一、统计抽样的核心内容与问题 研究内容 从总体中科学抽取样本的方法利用样本数据推断总体特征(均值、比率、总量)控制抽样误差与非抽样误差 解决的核心问题 在成本约束下,用少量样本准确推断总体特征量化估计结果的可靠性(置…...
OpenGL-什么是软OpenGL/软渲染/软光栅?
软OpenGL(Software OpenGL)或者软渲染指完全通过CPU模拟实现的OpenGL渲染方式(包括几何处理、光栅化、着色等),不依赖GPU硬件加速。这种模式通常性能较低,但兼容性极强,常用于不支持硬件加速…...
Axure Rp 11 安装、汉化、授权
Axure Rp 11 安装、汉化、授权 1、前言2、汉化2.1、汉化文件下载2.2、windows汉化流程2.3、 macOs汉化流程 3、授权 1、前言 Axure Rp 11官方下载链接:https://www.axure.com/downloadthanks 2、汉化 2.1、汉化文件下载 链接: https://pan.baidu.com/s/18Clf…...
window 显示驱动开发-如何查询视频处理功能(三)
D3DDDICAPS_GETPROCAMPRANGE请求类型 UMD 返回指向 DXVADDI_VALUERANGE 结构的指针,该结构包含特定视频流上特定 ProcAmp 控件属性允许的值范围。 Direct3D 运行时在D3DDDIARG_GETCAPS的 pInfo 成员指向的变量中为特定视频流的 ProcAmp 控件属性指定DXVADDI_QUER…...
