当前位置: 首页 > news >正文

《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()的缺点&#xff1a; ① 调用 select() 函数后针对所有文件描述符的循环语句&#xff1b; ② 调用 select() 函数时需要向操作系统传递监视对象信息&#xff1b; epoll()的优点&#xff1a; ① 无需编写以监视状态变化为目的的针对所有文件描述符的循环语…...

Python 递归函数

视频版教程 Python3零基础7天入门实战视频教程 在一个函数体内调用它自身&#xff0c;被称为函数递归。函数递归包含了一种隐式的循环&#xff0c;它会重复执行某段代码&#xff0c;但这种重复执行无须循环控制。 实例&#xff0c;求123…100的和&#xff0c;用递归实现。数学…...

Java实现计算两个日期之间的工作日天数

需求&#xff1a; 需要在后端实现 计算当前日期与数据库内保存的日期数据之间相隔的工作日数目 实现 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是集睿致远&#xff08;ASL&#xff09; 新推出的多功能显示控制器芯片&#xff0c;CS5817产品可应用于便携显示器、电竞显示器、桌面显示器、一体式台式机和嵌入式显示系统。 Type-C/DP/HDMI2.0输入转LVDS/eDP/VBO 芯片, 高度集成了多种输入输出接口, 并…...

2023面试知识点一

1、新生代和老年代的比例 默认的&#xff0c;新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )&#xff0c;即&#xff1a;新生代 ( Young ) 1/3 的堆空间大小。老年代 ( Old ) 2/3 的堆空间大小。其中&#xff0c;新生代 ( …...

【算法题】2856. 删除数对后的最小数组长度

题目&#xff1a; 给你一个下标从 0 开始的 非递减 整数数组 nums 。 你可以执行以下操作任意次&#xff1a; 选择 两个 下标 i 和 j &#xff0c;满足 i < j 且 nums[i] < nums[j] 。 将 nums 中下标在 i 和 j 处的元素删除。剩余元素按照原来的顺序组成新的数组&…...

Java面向对象编程

在关系型是数据库中&#xff0c;有两个不同的事务同时操作数据库中同一表的同一行&#xff0c;不会引起冲突的是&#xff1a; A. 其中一个DELETE操作&#xff0c;一个是SELECT操作 B. 其中两个都是UPDATE C. 其中一个是SELECT&#xff0c;一个是UPDATE D. 其中一个SELECT E. 其…...

K8S:Yaml文件详解及编写示例

文章目录 一.Yaml文件详解1.Yaml文件格式2.YAML 语法格式 二.Yaml文件编写及相关概念1.查看 api 资源版本标签2.yaml编写案例&#xff08;1&#xff09;相关标签介绍&#xff08;2&#xff09;Deployment类型编写nginx服务&#xff08;3&#xff09;k8s集群中的port介绍&#x…...

去耦电路设计应用指南(一)MCU去耦设计介绍

&#xff08;一&#xff09;MCU去耦设计介绍 1. 概述2. MCU需要去耦的原因2.1 去耦电路简介2.2 电源噪声产生的原因2.3 插入损耗2.4 去耦电路简介 参考资料来自网上&#xff1a; 1. 概述 我们经常看到单片机或者IC电路管脚常常会放置一个或者多个陶瓷电容&#xff0c;他们主要…...

【c++】杂记

文章目录 预处理器constauto 预处理器 预处理器&#xff1a;运行于编译过程之前的一段程序 预处理变量&#xff1a;不属于命名空间std,由预处理器负责管理 const const对象一旦创建就不再改变 const对象必须初始化 const对象旨在文件内有效 ectern const int bufsizefun() /…...

简记:使用 Django Shell 清空 数据库表

简记 使用 Django Shell 清空所有数据库表 jcLee95的博客&#xff1a;https://blog.csdn.net/qq_28550263 本文地址&#xff1a;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

异常提示&#xff1a; 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

用于将数据系列或数据框中的数据按指定的位置移动。这对于某些时间序列分析特别有用&#xff0c;例如计算数据的变化量或滞后值 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 &#xff0c;有 k k k 次询问&#xff0c;每次给定三个正整数 n i , e i , d i n_i,e_i,d_i ni​,ei​,di​ &#xff0c;求两个正整数 p i , q i p_i,q_i pi​,qi​ &#xff0c;使 n i p i q i &#xff0c; e i d i ( p i − 1 ) ( q i − 1 …...

如何实现Web应用、网站状态的监控?

如何实现Web应用、网站状态的监控&#xff1f; 关键词&#xff1a;网站监控,服务器监控,页面性能监控,用户体验监控本文通过代码分析、网站应用介绍网站状态监控的方式下文主要分为网站应用、技术实现两部分 一、网站应用 现在网络上已经存在一些Web网站监控的服务&#xff…...

手撕排序之堆排序

一、概念&#xff1a; 什么是逻辑结构、物理结构&#xff1f; 逻辑结构&#xff1a;是我们自己想象出来的&#xff0c;就像内存中不存在一个真正的树 物理结构(存储结构)&#xff1a;实际上在内存中存储的形式。 堆的逻辑结构是一颗完全二叉树 堆的物理结构是一个数组 之…...

【奇想星球】重磅!我们的AIGC共创社区平台上线了!

文章目录 01 前言功能模块 02 相识缘起连接价值平台优势 03 奇想星球04 我们做了什么时间线 05 初心愿景06 可爱的小伙伴们后续开发及招募计划 07 结语 公众号原文链接 01 前言 2023年9月10日&#xff0c;我们的平台网站上线了&#xff01; 奇想星球 | AIGC共创社区平台。网站地…...

2023年数维杯数学建模B题节能列车运行控制优化策略求解全过程文档及程序

2023年数维杯数学建模 B题 节能列车运行控制优化策略 原题再现&#xff1a; 在城市交通电气化进程快速推进的同时&#xff0c;与之相应的能耗增长和负面效应也在迅速增加。城市轨道交通中的快速增长的能耗给城轨交通的可持续性发展带来负担。2018 年&#xff0c;北京、上海、…...

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 使…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...