Linux学习记录——사십삼 高级IO(4)--- Epoll型服务器

文章目录

  • 1、理解Epoll和对应接口
  • 2、实现


1、理解Epoll和对应接口

poll依然需要OS去遍历所有fd。一个进程去多个特定的文件中等待,只要有一个就绪,就使用select/poll系统调用,让操作系统把所有文件遍历一遍,哪些就绪就加上哪些fd,再返回。一旦文件太多了,遍历效率就显而易见地低。epoll是为处理大批量句柄而作了改进的poll,句柄就是访问某种资源时标识这个资源的东西,比如C语言中的FILE结构体,文件描述符等。不过select/poll并不是没有用处,一些老型操作系统并不支持epoll,就得使用poll或者select。epoll是在Linux内核2.5.44时引入的,到现在为止都是Linux中最高效的多路转接IO方案。

epoll有3个接口。

在这里插入图片描述

size是一个被忽略的参数,只要大于0就行。如果成功,返回一个epoll文件描述符,在系统内部创建一些数据结构,帮助进行已就绪的fd的管理,暂且叫做epoll模型,失败返回-1。不用这个epoll文件描述符后要close(epollfd)。

创建后,用户要告诉内核,应当关心哪个文件描述符上的哪个事件是否就绪,select通过一个位图结构fd_set来实现,poll通过poll_fd来实现的。另外,内核要告诉用户,关心的哪些fd上的哪些事件event已经就绪了。epoll还有两个接口去做这两个事。

在这里插入图片描述

epfd就是创建函数的返回值;op表示想做什么,有3个值,EPOLL_ADD,EPOLL_MOD,EPOLL_DEL,分别是添加、修改、删除;fd表示哪一个fd,event表示这个fd上的哪个事件要被关心。

在这里插入图片描述

进行等待的接口。返回值和select,poll接口一样,就绪的fd数量;timeout的作用和poll一样,输入型参数,单位是毫秒ms,为0表示非阻塞,小于0表示阻塞,大于0poll在这段时间内阻塞等待,如果一直没有事件就绪,那么超过时间就返回0;中间两个参数是输出型参数,操作系统通过这两个告知用户就绪的fd上就绪的事件event。

在这里插入图片描述

events是一个32位整数,用户输入的是关心的事件,返回时操作系统通过这个整数来告诉用户哪些fd的events事件就绪了;data的类型是一个联合体,通常会使用prt或者fd。events有几种取值:

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

上面的就是宏。这里只关心EPOLLIN和EPOLLOUT。

TCP报头中6个标记位中有一个代表PSH,用来提示对方应用层立刻从接收缓冲区读取数据。但PSH并不一定能让应用层读取数据,它的催促是让套接字观察的fd对应的文件里的数据处于就绪状态。

操作系统可以把数据从应用层拷贝到缓冲区,然后将数据交给网卡。当网卡收到数据后,网卡会发送硬件中断,操作系统通过查看中断向量表,知道发来的中断号是网卡的,所以就知道网卡有了数据。select/poll都是在软件层面去检测是否有数据的。

CPU有对应的寄存器,寄存器是二进制序列,是一种存储单元,由硬件电路构成。数据拷贝到CPU的硬件本质是利用高低电频对CPU内的寄存器进行充放电,让CPU的寄存器变成和内存一样的值。CPU和所有外设之间都有针脚间接相连。发送中断就像是某个外设产生电流,从和它间接相连的针脚向寄存器充电,把数据放到寄存器中。之后网卡就可以发送中断号让CPU拷贝数据到内存了。所以数据是可以从外设拷贝到内存的。

用户层往下是系统调用层,再往下是操作系统,再往下就是传输层及以下了。当用户层创建epoll时,OS会维护一个红黑树,开始时只有一个根节点,并且epoll还会创建一个就绪队列,为空。红黑树的节点是结构体,里面有fd,有事件event,整个红黑树就是用户告诉OS,要关心哪些fd,以及fd上的哪些事件。所以可以看出epoll_ctl本质是对这个红黑树进行增删改,比如要删,就传对应的fd,事件设为nullptr/NULL,那就是对红黑树某个节点的删除。fd决定节点是红还是黑,左节点还是右节点,插入到哪里。内核中,一个数据结构对象,既可以属于红黑树,也可以属于另一个结构。

红黑树上只有某个fd上有对应的事件发生了,那么就把这个fd的节点接入到就绪队列中,队列只保存已经准备好的fd && 对应的event。队列每一个元素也可以是一个结构体,只取红黑树中已就绪节点里面的值来填充。epoll_wait接口中间两个参数就是从就绪队列中拿取节点,这个接口只看就绪队列,可以以时间复杂度为O(1)的方式来检测事件就绪,也就是队列是否为空。

节点放入队列实际不是将一个节点内容拷贝到队列节点里,而是红黑树节点也是队列节点,节点就是一个结构体,结构体里可以放入表示已经就绪的事件,放入红黑树相关指针信息,放入队列相关指针信息,建立起队列就是用这个队列相关的指针去指向下一个节点。

当数据就绪时,操作系统通过网卡,经过网络协议栈,拷贝到每个文件的文件缓冲区中。每个节点都有回调机制,假设每个文件结构体都有一个变量,如果没设置回调,就置为空,每次操作系统拷贝数据到缓冲区后就去判断一下这个变量,为空就退出,不为空就调用回调函数,回调函数做的工作就是把红黑树上已就绪的节点放到就绪队列中。

红黑树,就绪队列,回调机制这三个整体就是epoll模型,所以epoll_create使用时就是创建了这些,从操作系统内部到系统调用形成了一个体系。红黑树就像select/poll中的数组,但epoll这里核心的维护交由系统来做,不让用户去做。

为什么epoll_create要返回就绪fd的个数,以及另外两个接口还需要用这个数字?整个机制是由系统做的,接口是由进程调用的,进程在运行时,会创建task_struct指向文件描述符表files_struct,表里有一个数组,类型是struct file,012默认被占用,当创建epoll模型,操作系统也创建了一个struct file,里面有个指针指向epoll模型,这个struct file就在调用epoll接口的进程的文件描述符表中。用户,进程,task_struct,files_struct,struct file,这是一整个路线。通过epoll_create的返回值,也就是另外两个接口的参数epfd,两个接口就可以找到进程维护的文件描述符表,进而找到struct file,然后找到epoll模型,就可以对红黑树,就绪队列进行操作了。

epoll的红黑树比数组更有效率;也不需要底层在线性遍历所有节点;上层也不需要遍历节点只需要查看就绪队列;用户只需要调用接口就可以操作整个体系。

2、实现

Main.cc

#include "EpollServer.hpp"
#include <memory>int main()
{std::unique_ptr<EpollServer> svr(new EpollServer());svr->InitServer();svr->Start();return 0;
}

Makefile

epollserver:Main.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f epollserver

EpollServer.hpp中先写基础的

#pragma once#include <iostream>
#include <string>
#include "Sock.hpp"
#include "log.hpp"const static int gport = 8888;class EpollServer
{
public:EpollServer(uint16_t port = gport) : port_(port){}void InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();}void Start(){while(true){sleep(3);}}~EpollServer(){}
private:uint16_t port_;Sock listensock_;
};

现在还不能Accept,因为还不知道底层是否有文件就绪,如果没有,整个服务器就得阻塞了。epoll这里的思路就是把自己的权利交给epoll。要将listensock添加到epoll中,不过得先有epoll模型。

创建一个Epoll.hpp

#pragma once#include <iostream>
#include <string>
#include <sys/epoll.h>static const  int defaultepfd = -1;class Epoller
{
public:Epoller():epfd_(defaultepfd){}~Epoller(){}
private:int epfd_;
};

完善一下Epoll模型,并初始化和析构

Epoll.hpp

#pragma once#include <iostream>
#include <string>
#include <cstdlib>
#include <sys/epoll.h>
#include "err.hpp"static const  int defaultepfd = -1;
static const int gsize = 128;class Epoller
{
public:Epoller():epfd_(defaultepfd){}void Create(){epfd_ = epoll_create(gsize);if(epfd_ < 0){logMessage(Fatal, "epoll_create error, code: %d, errstring: %s", errno, strerror(errno));exit(EPOLL_CREAT_ERR);//err.hpp里加上这个错误}}int Fd(){return epfd_;}void Close(){if(epfd_ != defaultepfd) close(epfd_);}~Epoller(){}
private:int epfd_;
};

EpollServer.hpp

#pragma once#include "Epoll.hpp"
#include "Sock.hpp"
#include "log.hpp"const static int gport = 8888;class EpollServer
{
public:EpollServer(uint16_t port = gport) : port_(port){}void InitServer(){listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();epoller_.Create();logMessage(Debug, "init server success");}void Start(){//1、将listensock添加到epoll中,要先有epoll模型while(true){sleep(3);}}~EpollServer(){listensock_.Close();epoller_.Close();}
private:uint16_t port_;Sock listensock_;Epoller epoller_;
};

接下来关注事件。

Epoll.hpp

    //用户告诉内核要关心哪些事件bool AddEvent(int fd, uint32_t events){struct epoll_event ev;ev.events = events;ev.data.fd = fd;//fd就是就绪的文件描述符int n = epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);if(n < 0){logMessage(Fatal, "epoll_ctl error, code: %d, errstring: %s", errno, strerror(errno));return false;}return true;}

EpollServer.hpp

    void Start(){//1、将listensock添加到epoll中,要先有epoll模型bool r = epoller_.AddEvent(listensock_.Fd(), EPOLLIN);//只关心读事件assert(r);//可以做别的判断(void)r;while(true){;}}

然后就可以在循环中获取事件了,使用wait。从队列里拿数据这个过程是线性拷贝的,因为系统不相信用户,所以要定义一个struct epoll_event类型的数组来接收。以及wait接口中的events参数里,由于拷贝的缘故,数据是从左到右连续有效的,而返回值 - 1就是当前最后一个有效的下标。

EpollServer.hpp

    void Start(){//1、将listensock添加到epoll中,要先有epoll模型bool r = epoller_.AddEvent(listensock_.Fd(), EPOLLIN);//只关心读事件assert(r);//可以做别的判断(void)r;struct epoll_event revs_[gnum];int timeout = 1000;while(true){int n = epoller_.Wait(revs_, gnum, timeout);switch (n){case 0:logMessage(Debug, "timeout...");break;case -1:logMessage(Warning, "epoll_wait failed");break;default:logMessage(Debug, "有%d个事件就绪了", n);HandlerEvents(n);//一定有数据就绪break;}}}void HandlerEvents(int num){for(int i = 0; i < num; i++){int fd = revs_[i].data.fd;uint32_t events = revs_[i].events;logMessage(Debug, "当前正在处理%d上的%s", fd, (events&EPOLLIN) ? "EPOLLIN" : "OTHER");int fd = revs_[i].data.fd;uint32_t events = revs_[i].events;if(events & EPOLLIN)//判断读事件就绪{if (fd == listensock_.Fd()){// 1、新连接到来std::string clientip;uint16_t clientport;int sock = listensock_.Accept(&clientip, &clientport);if (sock < 0)continue;logMessage(Debug, "%s:%d 已经连上服务器了", clientip.c_str(), clientport);// 还不能recv,即使有了连接但也不知道有没有数据// 只有epoll知道具体情况,所以将sock添加到epoll中bool r = epoller_.AddEvent(sock, EPOLLIN);assert(r);(void)r;}else // 2、读事件{char buffer[1024];ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s - 1] = 0;//对打印格式buffer[s - 2] = 0;//做一下调整std::string echo = buffer;echo += " [epoll server echo]\r\n";std::cout << "client# " << echo << std::endl;send(fd, echo.c_str(), echo.size(), 0);}else{if (s == 0)logMessage(Info, "client quit ...");elselogMessage(Warning, "recv error, client quit...");close(fd);//将文件描述符移除//在处理异常的时候,fd必须合法才能被处理epoller_.DelEvent(fd);}}}}}

Epoll.hpp

    //用户告诉内核要关心哪些事件bool AddEvent(int fd, uint32_t events){struct epoll_event ev;ev.events = events;ev.data.fd = fd;//属于用户的数据,epoll底层不对该数据做任何修改,为了给未来就绪返回int n = epoll_ctl(epfd_, EPOLL_CTL_ADD, fd, &ev);if(n < 0){logMessage(Fatal, "epoll_ctl error, code: %d, errstring: %s", errno, strerror(errno));return false;}return true;}bool DelEvent(int fd){return epoll_ctl(epfd_, EPOLL_CTL_DEL, fd, nullptr) == 0;}int Wait(struct epoll_event* revs, int num, int timeout){return epoll_wait(epfd_, revs, num, timeout);}

读事件处理中,我们目前无法读到一个完整的报文。因为完整报文由应用层协议规定,我们的代码没有应用层协议,所以得自定义一个。

先用回调函数来处理数据

#include <functional>
using func_t = std::function<std::string (std::string)>;public:EpollServer(func_t func, uint16_t port = gport) : func_(func), port_(port){}
private:uint16_t port_;Sock listensock_;Epoller epoller_;struct epoll_event revs_[gnum];func_t func_;

读事件处理时

                else // 2、读事件{char request[1024];ssize_t s = recv(fd, request, sizeof(request) - 1, 0);if (s > 0){request[s - 1] = 0;//对打印格式request[s - 2] = 0;//做一下调整std::string response = func_(request);send(fd, response.c_str(), response.size(), 0);}else{if (s == 0)logMessage(Info, "client quit ...");elselogMessage(Warning, "recv error, client quit...");close(fd);//将文件描述符移除//在处理异常的时候,fd必须合法才能被处理epoller_.DelEvent(fd);}}

在Main.cc中传入函数

#include "EpollServer.hpp"
#include <memory>std::string echoServer(std::string r)
{std::string resp = r;resp += "[echo]\r\n";return resp;
}int main()
{std::unique_ptr<EpollServer> svr(new EpollServer(echoServer));svr->InitServer();svr->Start();return 0;
}

下一篇仍然是Epoll代码,但基于本篇代码所写,体现出Reactor的思路。

基本版Epoll

结束。

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

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

相关文章

ssm基于VUE.js的在线教育系统论文

摘 要 随着学习压力越来越大&#xff0c;课外参加补习班的学生越来越多。现在大多数学生采用请家教、自学、报名补习班的方式进行课外的额外学习。请家教费用昂贵&#xff0c;自学效率低&#xff0c;碰到自己不会的知识不能及时得到解达&#xff0c;报名补习班需要时间、地点的…

x-cmd pkg | trash-cli - 类 Unix 系统的命令行垃圾桶

目录 简介首次用户技术特点竞品和相关作品进一步阅读 简介 trash-cli 是类 Unix 系统的命令行垃圾桶&#xff0c;用于移动文件到回收站&#xff0c;同时会记录文件的原地址和删除日期。 该工具使用与 GNOME、KDE ​​和 XFCE 等桌面环境相同的垃圾桶&#xff0c;所以即使是非 …

PVE虚拟机配置文件恢复

一、pve 创建的虚拟机的配置文件位置 在宿主机的 /etc/pve/qemu-server&#xff0c;这里有创建虚拟机的相关硬件信息。 rootpve1:/etc/pve/qemu-server# pwd /etc/pve/qemu-server二、故障现象 在命令行执行qm list不显示虚拟机&#xff0c;查看 宿主机的 /etc/pve/qemu-ser…

Codeforces Round 913 (Div. 3)E 不进位各数位和与打表

Problem - E - Codeforces digsum(a)digsum(b)digsum(c)digsum(n) 要点一&#xff1a; 当左边和发生进位&#xff0c;比如56 11&#xff0c;那么数位和会变小。其实下一位就是相加后对9取余&#xff0c;各数位和必定变小的。 要点二&#xff1a; 然后就是组合情况了&#x…

Echarts可视化-数据请求-代码实现和思路

需求&#xff1a;为前端可视化图表提供数据支持。 实现&#xff1a; 时间戳获取优化&#xff08;细化到秒&#xff09; 根据时间获取数据&#xff08;SQL编写&#xff09; 前端需求数据返回&#xff08;数据VO&#xff09; 内容 Apache ECharts 营业额统计 用户统计 订单…

搜维尔科技:【简报】元宇宙数字人赛道,《全息影像技术应用》!

期待着看展的主角来到今天要参观的全息影像展&#xff0c;平时就喜欢看展的她对于所谓的全息影像非常好奇&#xff0c;于是她带着期待的心情进入展内。进入展内的主角看到的是与之前完全不同的画展&#xff0c;每幅画看起来就像真的一样&#xff0c;充满好奇的她在展览的各处游…

【ONE·MySQL || 复合查询】

总言 主要内容&#xff1a;主要介绍一些复杂的查询&#xff0c;涉及多表查询、交叉连接、内连接、外连接、子查询、合并查询等。       文章目录 总言1、基本查询回顾1.0、博文说明1.1、一些例题1.1.1、查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的…

Python爬虫|使用urllib获取百度首页源码

在这个博客中&#xff0c;我们将一起探索百度首页的源码&#xff0c;深入了解从URL请求到页面呈现的全过程。我们将使用Python的urllib.request库来模拟浏览器发送请求&#xff0c;并解码响应中的页面源码。通过分析源码&#xff0c;我们将揭示网页的结构、内容和背后的工作原理…

【Redis】AOF 源码

在上篇, 我们已经从使用 / 机制 / AOF 过程中涉及的辅助功能等方面简单了解了 Redis AOF。 这篇将从源码的形式, 进行深入的了解。 1 Redis 整个 AOF 主要功能 Redis 的 AOF 功能概括起来就 2 个功能 AOF 同步: 将客户端发送的变更命令, 保存到 AOF 文件中AOF 重写: 随着 Red…

电脑重置网络后连不上网了怎么办

一般电脑重置网络后都会自动重新下载好网络配置&#xff0c;但是不免会出现一些意外&#xff0c;接下来就我遇到的重置后无法联网的解决方案 做一个分享&#xff1a; 1、按下“winR”打开运行输入 services.msc 。 2、找到 WLAN AutoConfig 和 Wired AutoConfig 服务&#xff…

如何安装Python

Python是一种流行的编程语言&#xff0c;拥有广泛的应用领域。为了开始使用Python&#xff0c;首先需要在计算机上安装Python解释器。在本文中&#xff0c;我们将介绍如何安装Python以及一些常见的安装步骤。 1. 下载Python 首先&#xff0c;你需要前往Python官方网站&#xf…

第 9 章 哈希表

文章目录 9.1 哈希表(散列)-Google 上机题9.2 哈希表的基本介绍9.3 google 公司的一个上机题: 9.1 哈希表(散列)-Google 上机题 看一个实际需求&#xff0c;google 公司的一个上机题:有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员…

软件测试|PyQt5实战教程(一)安装与环境配置

简介 我们之前介绍过使用tkinter创建图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;现在我们来介绍另外一个用于创建GUI的神器——pyQt5&#xff0c;它可以用于创建跨平台的桌面应用程序。PyQt5 实现了一个 Python模块集&#xff0c;有 620 个类&#xff0c;60…

乐意购项目前端开发 #1

一、创建vue项目 1. vue create 项目名 2. 运行项目 npm install npm run dev3.使用Git管理项目 创建远程仓库 leyigou 在项目文件终端执行以下代码 git init # git 初始化#这个要使用自己的仓库 git remote add origin gitgitee.com:xie-weijia/leyigou.git # 添加远程仓…

[Docker] 基本名词

镜像(iamge)&#xff1a; Docker 镜像就好比是一个模板&#xff0c;可以通过这个模板来创建容器服务&#xff0c; 容器&#xff08;container&#xff09;: Docker利用容器技术&#xff0c;独立运行一个或则多个应用&#xff0c;通过镜像来创建的。 启动&#xff0c;停止&a…

docker应用:vocechat

简介&#xff1a;VoceChat是一款超轻量级的Rust聊天应用程序、API和SDK&#xff0c;优先考虑私人托管。使用VoceChat建立您自己的聊天功能&#xff01;作为一款非常好用的通讯应用程序&#xff0c;它可以让你与朋友、家人和同事进行即时消息聊天&#xff0c;支持图片视频的分享…

Linux 压缩与解压缩

参考资料 linux 压缩和解压缩命令gz、tar、zip、bz2tar命令 – 压缩和解压缩文件 目录 一. gzip命令1.1 压缩1.1.1 -k 压缩后保留源文件1.1.2 -l 查看压缩文件中的文件信息1.1.3 -r 递归压缩文件夹中的所有文件 1.2 解压缩 二. zip命令2.1 zip 压缩2.1.1 -r 压缩文件夹2.1.2 压…

温和去油去黑头,只需敷上一刻钟的泥膜就够了

冬季天气干燥&#xff0c;很多朋友脸部更容易出油&#xff0c;连带着黑头也变多了。这是因为干燥的环境会刺激皮脂腺分泌&#xff0c;导致皮肤油脂分泌过多&#xff0c;容易堵塞毛孔形成黑头。因此&#xff0c;在冬季特别需要注意控油去黑头的工作。 控油去黑头需要清洁毛孔&am…

mysql原理--undo日志1

1.事务回滚的需求 我们说过 事务 需要保证 原子性 &#xff0c;也就是事务中的操作要么全部完成&#xff0c;要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况&#xff0c;比如&#xff1a; (1). 事务执行过程中可能遇到各种错误&#xff0c;比如服务器本身的错误&…

并发编程之并发容器

目录 并发容器 CopyOnWriteArrayList 应用场景 常用方法 读多写少场景使用CopyOnWriteArrayList举例 CopyOnWriteArrayList原理 CopyOnWriteArrayList 的缺陷 扩展迭代器fail-fast与fail-safe机制 ConcurrentHashMap 应用场景 常用方法 并发场景下线程安全举例 Con…