I/O多路转接之epoll

承接上文 I/O多路转接之poll-CSDN博客

简介

epoll的相关系统调用

epoll底层原理

编写epoll的server

重新回归一下epoll原理,LT,ET

epoll改成ET工作模式 -- 初识(有bug)

epoll初识

按照man手册的说法: 是为处理大批量句柄而作了改进的poll.
它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)
它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法
 

epoll的相关系统调用

epoll 有3个相关的系统调用,这一点和之前的poll是完全不一样的,还有一点是epoll还是一样只负责等待

epoll_create

int epoll_create(int size);

创建一个epoll的句柄

自从linux2.6.8之后, size参数是被忽略的.
用完之后, 必须调用close()关闭

epoll_ctl

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

epoll的事件注册函数

它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型;
第一个参数是epoll_create()的返回值(epoll的句柄);
第二个参数表示动作,用三个宏来表示;
第三个参数是需要监听的fd;
第四个参数是告诉内核需要监听什么事;

第二个参数的取值:

EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd;

events可以是以下几个宏的集合

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

 
 

epoll_wait

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

收集在epoll监控的事件中已经发送的事件

参数events是分配好的epoll_event结构体数组;
epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存);
maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size;
参数timeout是超时时间 (毫秒, 0会立即返回, -1是永久阻塞);
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败;

epoll底层原理

当某一进程调用epoll_create方法时, Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关
 

struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};

1.每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件;
2.这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度);
3.而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法;
4.这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中;
5.在epoll中,对于每一个事件,都会建立一个epitem结构体;

struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1)

总结一下, epoll的使用过程就是三部曲:

调用epoll_create创建一个epoll句柄;
调用epoll_ctl, 将要监控的文件描述符进行注册;
调用epoll_wait, 等待文件描述符就绪

编写epoll的server

epollServer.cc
#include "epollServer.hpp"
#include <memory>using namespace std;
using namespace epoll_ns;static void usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " port"<< "\n\n";
}// 一个简单的回调方法
std::string echo(const std::string &message)
{return "I am epollserver, " + message;
}// ./select_server port
int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]);unique_ptr<EpollServer> epollsvr(new EpollServer(echo, port));epollsvr->initServer();epollsvr->start();return 0;
}
epollServer.hpp
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <sys/epoll.h>
#include "err.hpp"
#include "log.hpp"
#include "sock.hpp"namespace epoll_ns
{static const int defaultport = 8888; // 直接预设一个portstatic const int size = 128;         // 只要不小于0就行了,这个参数现在没有用static const int defaultvalue = -1;static const int defaultnum = 64;using func_t = std::function<std::string(const std::string &)>; // 创建一个处理函数class EpollServer{public:EpollServer(func_t f, uint16_t port = defaultport,int num = defaultnum) : func_(f), _num(num), _revs(nullptr), _port(port), _listensock(defaultvalue), _epfd(defaultvalue){}void initServer(){// 1.创建socket_listensock = Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);// 2.创建epoll模型_epfd = epoll_create(size); // 这里的参数没有用if (_epfd < 0){logMessage(FATAL, "epoll create error: %s", strerror(errno));exit(EPOLL_CREATE_ERR);}// 3.添加listensock到_epfd中去struct epoll_event ev;    // 这是一个联合体,目前看成fd使用ev.events = EPOLLIN;      // 读事件ev.data.fd = _listensock; // 当事件就绪,被重新捞取上来的时候,我们要知道是哪一个fd就绪了!epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);// 4.申请就绪事件的空间_revs = new struct epoll_event[_num];logMessage(NORMAL, "init server success");}// 处理就绪事件void HandlerEvent(int readyNum){logMessage(DEBUG, "HandlerEvent in");for (int i = 0; i < readyNum; ++i){uint32_t events = _revs[i].events; // 按照顺序拿取就绪事件,但是我们并不清楚这个事件是谁(分不清是读还是写等等)// 这时候联合体作用就体现出来了,因为在初始化的时候我们就已经填进去了int sock = _revs[i].data.fd; // 拿到了对应就绪的描述符if (sock == _listensock && (events & EPOLLIN)){// _listensock读事件就绪, 获取新连接std::string clientip;uint16_t clientport;int fd = Sock::Accept(sock, &clientip, &clientport);if (fd < 0){logMessage(WARNING, "accept error");continue;}// 获取fd成功,可以直接读取吗?还是不能,因为无法保证是否有数据到来了,所以我们还得继续添加到epoll模型中去struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);}else if (events & EPOLLIN){// 普通的读事件就绪// 注意这里的读取是有很大问题的// 这样直接读取还是有问题,可以保证第一次读取有数据,但是无法保证读取完整// 比如对方的报文差分多份就不可以读取完整了,为了简洁我们先这样读// 打循环也无法保证char buffer[1024];int n = recv(sock, buffer, sizeof(buffer), 0);if (n > 0){buffer[n] = 0;logMessage(DEBUG, "client# %s", buffer);// TODO -- 这里的发送是有问题的,不能保证发送的条件是就绪的std::string respose = func_(buffer);send(sock, respose.c_str(), respose.size(), 0);}else if (n == 0){// 这里表示对端把链接关了// 关于这个套接字的所有都得清理掉epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);logMessage(NORMAL, "client quit");}else{// 出错误了epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);logMessage(ERROR, "recv error, code: %d, errstring: %s", errno, strerror(errno));}}else // 自己可以添加其他的条件,比如写事件之类的{}}logMessage(DEBUG, "HandlerEvent out");}void start(){int timeout = -1;for (;;){int n = epoll_wait(_epfd, _revs, _num, timeout); // 就绪队列中拿取// n有几个就说明拿到了几个switch (n){case 0:logMessage(NORMAL, "timeout...");break;case -1:logMessage(WARNING, "epoll_wait failed, code: %d, errstring: %s", errno, strerror(errno));break;default:logMessage(NORMAL, "have event ready");HandlerEvent(n); // 需要知道有几个事件就绪了break;}}}~EpollServer(){if (_listensock != defaultvalue)close(_listensock);if (_epfd != defaultvalue)close(_epfd);if (_revs)delete[] _revs;}private:uint16_t _port;int _listensock;int _epfd;struct epoll_event *_revs; // 获取事件int _num;func_t func_;};
}

err.hpp
#pragma once#include <iostream>enum
{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR,EPOLL_CREATE_ERR
};

log.hpp
#pragma once#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4const char * to_levelstr(int level)
{switch(level){case DEBUG : return "DEBUG";case NORMAL: return "NORMAL";case WARNING: return "WARNING";case ERROR: return "ERROR";case FATAL: return "FATAL";default : return nullptr;}
}void logMessage(int level, const char *format, ...)
{
#define NUM 1024char logprefix[NUM];snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",to_levelstr(level), (long int)time(nullptr), getpid());char logcontent[NUM];va_list arg;va_start(arg, format);vsnprintf(logcontent, sizeof(logcontent), format, arg);std::cout << logprefix << logcontent << std::endl;
}

makefile
epoll_server: epollServer.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f epoll_server

sock.hpp
#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "err.hpp"class Sock
{const static int backlog = 32;public:static int Socket(){// 1. 创建socket文件套接字对象int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){logMessage(FATAL, "create socket error");exit(SOCKET_ERR);}logMessage(NORMAL, "create socket success: %d", sock);int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));return sock;}static void Bind(int sock, int port){// 2. bind绑定自己的网络信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind socket error");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success");}static void Listen(int sock){// 3. 设置socket 为监听状态if (listen(sock, backlog) < 0) // 第二个参数backlog后面在填这个坑{logMessage(FATAL, "listen socket error");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success");}static int Accept(int listensock, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(listensock, (struct sockaddr *)&peer, &len);if (sock < 0)logMessage(ERROR, "accept error, next");else{logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?*clientip = inet_ntoa(peer.sin_addr);*clientport = ntohs(peer.sin_port);}return sock;}
};

至此一个简单的epoll服务器就完成了,代码中提及了种种问题,我们得到下一文章才能更好的解决了

重新回归一下epoll原理,LT,ET

因为select/poll/epoll的默认通知机制都是LT模式!

epoll改成ET工作模式

 

预告

如何正确的处理I0+协议定制+业务逻辑=Reactor

基于ET模式下的Reactor,处理所有的IO

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

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

相关文章

CANdelaStudio 使用教程4 编辑State

文章目录 简述1、State Groups2、Dependencies3、 Defaults State1、 会话状态2、 新增会话状态3、 编辑 服务对 State 的依赖关系 State Diagram 简述 1、State Groups 2、Dependencies 在这里&#xff0c;可以编辑现有服务在不同会话状态或安全访问状态的支持情况和状态转换…

粒子群算法Particle Swarm Optimization (PSO)的定义,应用优点和缺点的总结!!

文章目录 前言一、粒子群算法的定义二、粒子群算法的应用三、粒子群算法的优点四、粒子群算法的缺点&#xff1a;粒子群算法的总结 前言 粒子群算法是一种基于群体协作的随机搜索算法&#xff0c;通过模拟鸟群觅食行为而发展起来。该算法最初是由Eberhart博士和Kennedy博士于1…

概念解析 | 玻尔兹曼机

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:玻尔兹曼机。 概念解析 | 玻尔兹曼机 引言 随着人工智能技术的飞速发展,玻尔兹曼机作为一种重要的生成模型,受到了广泛的关注。 背景介绍 玻尔兹曼机(Boltzmann Machine)是一…

uniapp+vue基于Android的校园二手跳蚤市场的设计与实现 微信小程序

实现功能&#xff1a; 用户管理&#xff1a;登陆、注册、注销、修改密码、上传头像、修改资料 发布与检索&#xff1a;发布商品、模糊搜索、人气排序、价格排序、时间排序、推送商品&#xff08;协同过滤算法实现个性化推荐&#xff09;&#xff0c;最新发布、分类检索 核心交易…

1、分布式锁实现原理与最佳实践(一)

在单体的应用开发场景中涉及并发同步时&#xff0c;大家往往采用Synchronized&#xff08;同步&#xff09;或同一个JVM内Lock机制来解决多线程间的同步问题。而在分布式集群工作的开发场景中&#xff0c;就需要一种更加高级的锁机制来处理跨机器的进程之间的数据同步问题&…

安卓系统修图软件(二)

晚上好&#xff0c;自上一次博主分享修图软件之后&#xff0c;今天博主将带来第二期安卓修图软件的推送&#xff0c;个个都是宝藏&#xff0c;建议大家赶紧体验哦。 1.canva可画 如果说有一款手机APP可以与PS媲美&#xff0c;那么一定非canvas莫属。这款强大的修图软件支持海报…

java springboot测试类虚拟MVC环境 匹配请求头指定key与预期值是否相同

上文 java springboot测试类虚拟MVC环境 匹配返回值与预期内容是否相同 (JSON数据格式) 版 中 我们展示 json匹配内容的方式 那么 本文我们来看看Content-Type属性的匹配方式 首先 我们从返回体可以看出 Content-Type 在请求头信息 Headers 中 我们直接将测试类代码更改如下 …

企业文档文件管理软件推荐:提升管理效率与数据安全性

Zoho WorkDrive企业网盘是一种高效的文件管理工具&#xff0c;它不仅可以为组织搭建统一、高效、安全、智能的内容管理体系&#xff0c;还能够提供大规模支撑、海量数据处理、非结构化数据治理、智能挖掘与洞察等服务能力。通过这些服务&#xff0c;企业可以更好地管理和利用其…

leetcode 41. 缺失的第一个正数

目录 暴力排序 桶排序 桶排序Set 桶排序分治思想 官方题解 桶排序数组内标记 桶排序额外数组标记&#xff08;更好理解&#xff09; 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额…

Rust在Web开发中的应用

欢迎关注我的公众号lincyang新自媒体&#xff0c;回复关键字【程序员经典书单】&#xff0c;领取程序员的100本经典书单 大家好&#xff01;我是lincyang。 今天我们将一起深入探索Rust在Web开发领域的应用。尽管Rust最初设计用于系统编程&#xff0c;但其性能、安全性和现代并…

只狼 资源分享

版本介绍 v1.06版|容量15GB|官方简体中文|支持键盘.鼠标.手柄|赠官方原声4首BGM|赠多项修改器|赠一周目全义手忍具强化通关存档|2020年01月15号更新 只狼中文设置&#xff1a; https://jingyan.baidu.com/article/cb5d6105bc8556005d2fe048.html 只狼键盘对应按键&#xff1…

windows本地dockr的clickhouse链接本地mysql服务,连接不上

不想看过成的&#xff0c;解决办法在最后面 报错信息&#xff1a; SQL 错误 [1000] [08000]: Poco::Exception. Code: 1000, e.code() 0, Exception: Connections to all replicas failed: test1localhost:3306 as user root (version 21.12.3.32 (official build)) , serve…

C# WPF上位机开发(掌握一点c#基础)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 wpf虽然比较简单&#xff0c;但是最好还是要有一点c#的基础比较好。本身wpf有点类似于web开发&#xff0c;前端和html差不多&#xff0c;后端则和j…

SpringBoot事务处理

一、事务回顾 回顾地址&#xff1a; 深入理解数据库事务&#xff08;超详细&#xff09;_数据库事务操作_Maiko Star的博客-CSDN博客 事务&#xff1a; 是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 事…

隐写-MISC-bugku-解题步骤

——CTF解题专栏—— 题目信息&#xff1a; 题目&#xff1a;隐写 作者&#xff1a;CyberFl0wer 提示&#xff1a;无 解题附件&#xff1a; 解题思路&#xff1a; 这张图片一看&#xff01;哦呦~背景还是透明的&#xff0c;那我肯定要尝试给他换换色&#xff08;不可以色色.jpg…

重生之我是一名程序员 42——字符分类函数

哈喽啊大家晚上好&#xff01;今天呢给大家带来一些超简单的知识&#xff0c;大家是需要浅浅理解就行了。所以今天给大家带来的知识是——字符分类函数。 首先呢还是给大家介绍一下它们&#xff0c;字符分类函数是一种函数&#xff0c;它根据一定的规则将字符分组或分类。在编…

Map和Set小总结【温习】

目录 一、概念与模型 二、Map的使用 三、Set的说明 一些小练习 四、哈希表 1.概念 2.冲突 2.1、概念 2.2、冲突-->避免 2.3、冲突-->解决 &#xff08;1&#xff09;闭散列 &#xff08;2&#xff09;开散列 2.4、其他问题 一、概念与模型 1.概念&#xff1a…

三菱GX WORRKS3 下载与安装

目录 下载 安装 准备好安装包 对电脑系统要求 安装 因为小编公司需要&#xff0c;所以开始了三菱plc软件的学习&#xff0c;并从今天开始记录学习&#xff0c;希望小编的内容能帮到你&#xff0c;对你的学习有帮助&#xff01; 下载 三菱电机官网 当然了&#xff0c;需要…

【C++】类和对象(下篇)

这里是目录 构造函数&#xff08;续&#xff09;构造函数体赋值初始化列表 explicit关键字隐式类型转换 static成员友元友元函数友元类 内部类匿名对象匿名对象的作用const引用匿名对象 构造函数&#xff08;续&#xff09; 构造函数体赋值 在创建对象时&#xff0c;编译器通…

五种多目标优化算法(MOGWO、MOLPB、MOJS、NSGA3、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标灰狼优化算法MOGWO 多目标应用&#xff1a;基于多目标灰狼优化算法MOGWO求解微电网多目标优化调度&#xff08;MATLAB代码&#xff09;-CSDN博客 &#xff08;2&#xff09;多目标学习者行为优化算法MOLPB 多目标学习…