《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 使…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...