找工作小项目:day15-macOS支持、完善逻辑
macOS支持、完善逻辑
目前的代码可以在Linux上完美运行编译,在Windows上也可以通过WSL编译运行源代码,但是在MacBook上却无法运行编译,这主要是由于macOS上没有epoll,取而代之的很相似的kqueue。由于操作系统不同,我们需要的是在面向操作系统即从用户态向内核态转变时进行修改。目前我们面向这一过程的仅有Epoll,所以在面向不同操作系统的过程中仅需要关心这部分。
为了完善逻辑处理机制,即将不同事件类型进行不同的注册,事件注册在Channel中完成,因为之前是在Channel对不同事件的回调函数进行设置的。
1、错误检测机制
2、Macros(宏定义,去除类移动和复制)
3、Socket(创建地址和socket)
4、Epoll->Poller(事件注册分发从以及红黑树上删除)
声明部分就可以很直观地看出来,两者同属一系,仅仅是多了一部分适用于macOS的定义。
//Epoll
#include "Macros.h"
#include <vector>
#ifdef OS_LINUX
#include <sys/epoll.h>
#endif
class Channel;
class Epoll {public:Epoll();~Epoll();DISALLOW_COPY_AND_MOVE(Epoll);void UpdateChannel(Channel *ch);void DeleteChannel(Channel *ch);std::vector<Channel *> Poll(int timeout = -1);private:int epfd_{1};struct epoll_event *events_{nullptr};
};
//Poller
#include "Macros.h"
#ifdef OS_LINUX
#include <sys/epoll.h>
#endif
#ifdef OS_MACOS
#include <sys/event.h>
#endif
class Channel;
class Poller {public:Poller();~Poller();DISALLOW_COPY_AND_MOVE(Poller);void UpdateChannel(Channel *ch);void DeleteChannel(Channel *ch);std::vector<Channel *> Poll(int timeout = -1);private:int fd_{1};
#ifdef OS_LINUXstruct epoll_event *events_{nullptr};
#endif
#ifdef OS_MACOSstruct kevent *events_{nullptr};
#endif
};
为了完善逻辑,能够为不同事件类型注册不同的事件处理方式,这里采用标志位的方案进行设置,由于多了一种系统所以代码量会多出一倍,这是因为需要对在macOS系统下也进行定义。
在poll中对就绪事件进行分发,利用Channel能够在使用fd的同时获得事件的处理方式。
std::vector<Channel *> Poller::Poll(int timeout) {std::vector<Channel *> active_channels;int nfds = epoll_wait(fd_, events_, MAX_EVENTS, timeout);ErrorIf(nfds == -1, "epoll wait error");for (int i = 0; i < nfds; ++i) {Channel *ch = (Channel *)events_[i].data.ptr;int events = events_[i].events;if (events & EPOLLIN) {ch->SetReadyEvents(Channel::READ_EVENT);}if (events & EPOLLOUT) {ch->SetReadyEvents(Channel::WRITE_EVENT);}if (events & EPOLLET) {ch->SetReadyEvents(Channel::ET);}active_channels.push_back(ch);}return active_channels;
}
之后是对Channel中的监听事件类型标志位进行设置,如果事件标志位判断为真则将该事件设置成对应标志,注意是对红黑树树上的通道进行设置,不在树上都需要放到树上。
注意就绪事件和监听事件之间的关系,
注册监听:程序首先通过 listen_events_ 向内核注册自己感兴趣的事件。
内核监控:内核不断监控这些事件,并在事件发生时通知程序。
事件就绪:当内核检测到某些事件发生时,会将这些事件标记为就绪,并通过 I/O 多路复用机制返回给应用程序。
处理事件:程序读取 ready_events_,了解具体哪些事件已发生,然后进行相应的处理。
void Poller::UpdateChannel(Channel *ch) {int sockfd = ch->GetSocket()->GetFd();struct epoll_event ev {};ev.data.ptr = ch;if (ch->GetListenEvents() & Channel::READ_EVENT) {ev.events |= EPOLLIN | EPOLLPRI;}if (ch->GetListenEvents() & Channel::WRITE_EVENT) {ev.events |= EPOLLOUT;}if (ch->GetListenEvents() & Channel::ET) {ev.events |= EPOLLET;}if (!ch->GetExist()) {ErrorIf(epoll_ctl(fd_, EPOLL_CTL_ADD, sockfd, &ev) == -1, "epoll add error");ch->SetExist();} else {ErrorIf(epoll_ctl(fd_, EPOLL_CTL_MOD, sockfd, &ev) == -1, "epoll modify error");}
}
在macOS系统上的差别不大,主要是函数的使用不同,需要注意一下。
#ifdef OS_MACOSPoller::Poller() {fd_ = kqueue();ErrorIf(fd_ == -1, "kqueue create error");events_ = new struct kevent[MAX_EVENTS];memset(events_, 0, sizeof(*events_) * MAX_EVENTS);
}Poller::~Poller() {if (fd_ != -1) {close(fd_);}
}std::vector<Channel *> Poller::Poll(int timeout) {std::vector<Channel *> active_channels;struct timespec ts;memset(&ts, 0, sizeof(ts));if (timeout != -1) {ts.tv_sec = timeout / 1000;ts.tv_nsec = (timeout % 1000) * 1000 * 1000;}int nfds = 0;if (timeout == -1) {nfds = kevent(fd_, NULL, 0, events_, MAX_EVENTS, NULL);} else {nfds = kevent(fd_, NULL, 0, events_, MAX_EVENTS, &ts);}for (int i = 0; i < nfds; ++i) {Channel *ch = (Channel *)events_[i].udata;int events = events_[i].filter;if (events == EVFILT_READ) {ch->SetReadyEvents(ch->READ_EVENT | ch->ET);}if (events == EVFILT_WRITE) {ch->SetReadyEvents(ch->WRITE_EVENT | ch->ET);}active_channels.push_back(ch);}return active_channels;
}void Poller::UpdateChannel(Channel *ch) {struct kevent ev[2];memset(ev, 0, sizeof(*ev) * 2);int n = 0;int fd = ch->GetSocket()->GetFd();int op = EV_ADD;if (ch->GetListenEvents() & ch->ET) {op |= EV_CLEAR;}if (ch->GetListenEvents() & ch->READ_EVENT) {EV_SET(&ev[n++], fd, EVFILT_READ, op, 0, 0, ch);}if (ch->GetListenEvents() & ch->WRITE_EVENT) {EV_SET(&ev[n++], fd, EVFILT_WRITE, op, 0, 0, ch);}int r = kevent(fd_, ev, n, NULL, 0, NULL);ErrorIf(r == -1, "kqueue add event error");
}void Poller::DeleteChannel(Channel *ch) {struct kevent ev[2];int n = 0;int fd = ch->GetSocket()->GetFd();if (ch->GetListenEvents() & ch->READ_EVENT) {EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, ch);}if (ch->GetListenEvents() & ch->WRITE_EVENT) {EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, ch);}int r = kevent(fd_, ev, n, NULL, 0, NULL);ErrorIf(r == -1, "kqueue delete event error");
}
#endif
5、Channel(根据fd设置对应回调函数并调用,包括了事件标志位)
今天Channel将正式开始处理不同类型的事件,通过上面Poller的事件类型的设置将通过判断进行不同的工作,调用不同的函数。
声明部分多出了EnableWrite、SetWriteCallback方法,正式将写回调函数用上了。
class Socket;
class EventLoop;
class Channel {public:Channel(EventLoop *loop, Socket *socket);~Channel();DISALLOW_COPY_AND_MOVE(Channel);void HandleEvent();void EnableRead();void EnableWrite();Socket *GetSocket();int GetListenEvents();int GetReadyEvents();bool GetExist();void SetExist(bool in = true);void UseET();void SetReadyEvents(int ev);void SetReadCallback(std::function<void()> const &callback);void SetWriteCallback(std::function<void()> const &callback);static const int READ_EVENT; // NOLINTstatic const int WRITE_EVENT; // NOLINTstatic const int ET; // NOLINTprivate:EventLoop *loop_;Socket *socket_;int listen_events_{0};int ready_events_{0};bool exist_{false};std::function<void()> read_callback_;std::function<void()> write_callback_;
};
在实现中,从析构函数就发生了变化,现在由EventLoop进行事件的回收工作,而不是像之前一样将fd置为-1;
const int Channel::READ_EVENT = 1;
const int Channel::WRITE_EVENT = 2;
const int Channel::ET = 4;
Channel::Channel(EventLoop *loop, Socket *socket) : loop_(loop), socket_(socket) {}Channel::~Channel() { loop_->DeleteChannel(this); }
在调用回调函数的方法上没发生什么变化,只不过有了标志位不再需要之前更靠近底层的判断方式。
void Channel::HandleEvent() {if (ready_events_ & READ_EVENT) {read_callback_();}if (ready_events_ & WRITE_EVENT) {write_callback_();}
}
根据事件的标志位将树上的通道中的处理方式进行更新。
void Channel::EnableRead() {listen_events_ |= READ_EVENT;loop_->UpdateChannel(this);
}void Channel::EnableWrite() {listen_events_ |= WRITE_EVENT;loop_->UpdateChannel(this);
}void Channel::UseET() {listen_events_ |= ET;loop_->UpdateChannel(this);
}
获取设置一些基础信息,包括将就绪事件的标志位设置为对应处理方式。
Socket *Channel::GetSocket() { return socket_; }int Channel::GetListenEvents() { return listen_events_; }
int Channel::GetReadyEvents() { return ready_events_; }bool Channel::GetExist() { return exist_; }void Channel::SetExist(bool in) { exist_ = in; }void Channel::SetReadyEvents(int ev) {if (ev & READ_EVENT) {ready_events_ |= READ_EVENT;}if (ev & WRITE_EVENT) {ready_events_ |= WRITE_EVENT;}if (ev & ET) {ready_events_ |= ET;}
}void Channel::SetReadCallback(std::function<void()> const &callback) { read_callback_ = callback; }
void Channel::SetWriteCallback(std::function<void()> const &callback) { write_callback_ = callback; }
6、EventLoop(对树上的通道进行轮询)
在事件处理的类中多了对通道回收的操作并多了一个Quit方法。
#include "Macros.h"
#include <functional>class Poller;
class Channel;
class EventLoop {public:EventLoop();~EventLoop();DISALLOW_COPY_AND_MOVE(EventLoop);void Loop();void UpdateChannel(Channel *ch);void DeleteChannel(Channel *ch);void Quit();private:Poller *poller_{nullptr};bool quit_{false};
};
quit_标志位作用不清楚,而DeleteChannel是调用poller中的deleteChannel方法完成的,这是因为由于不同系统中poller的对于并发Epoll库方法不同。
7、Acceptor(创建连接)
8、Connection(连接上发生的事件)
声明中State枚举类型中将Handshaking转变为了Connecting(从握手变成连接中…),多了Send、SetOnMessageCallback、Business、OnMessage以及on_message_callback_回调函数。
class EventLoop;
class Socket;
class Channel;
class Buffer;
class Connection {public:enum State {Invalid = 1,Connecting,Connected,Closed,Failed,};Connection(EventLoop *loop, Socket *sock);~Connection();DISALLOW_COPY_AND_MOVE(Connection);void Read();void Write();void Send(std::string msg);void SetDeleteConnectionCallback(std::function<void(Socket *)> const &callback);void SetOnConnectCallback(std::function<void(Connection *)> const &callback);void SetOnMessageCallback(std::function<void(Connection *)> const &callback);void Business();State GetState();void Close();void SetSendBuffer(const char *str);Buffer *GetReadBuffer();const char *ReadBuffer();Buffer *GetSendBuffer();const char *SendBuffer();void GetlineSendBuffer();Socket *GetSocket();void OnConnect(std::function<void()> fn);void OnMessage(std::function<void()> fn);private:EventLoop *loop_;Socket *sock_;Channel *channel_{nullptr};State state_{State::Invalid};Buffer *read_buffer_{nullptr};Buffer *send_buffer_{nullptr};std::function<void(Socket *)> delete_connectioin_callback_;std::function<void(Connection *)> on_connect_callback_;std::function<void(Connection *)> on_message_callback_;void ReadNonBlocking();void WriteNonBlocking();void ReadBlocking();void WriteBlocking();
};
从实现上看看发生了那些改变,注意在ReadNonBlocking中开始在连接断开的情况下内部调用Close而在昨天的项目中是在测试程序中进行的调用。
在这多出来的实现中,为外部设置了写数据的接口,封装更为简单的对外接口。SetOnMessageCallback设置on_message_callback_ 并进行回显调用
void Connection::Send(std::string msg){SetSendBuffer(msg.c_str());Write();
}void Connection::Business(){Read();on_message_callback_(this);
}void Connection::SetOnMessageCallback(std::function<void(Connection *)> const &callback) {on_message_callback_ = callback;std::function<void()> bus = std::bind(&Connection::Business, this);channel_->SetReadCallback(bus);
}
9、Buffer(缓冲区,用以存放双工过程中发送的数据)
10、ThreadPool(线程池,用以管理复用线程)
11、服务器类
从声明来看是将回显、新建连接进行了封装
class EventLoop;
class Socket;
class Acceptor;
class Connection;
class ThreadPool;
class Server {private:EventLoop *main_reactor_;Acceptor *acceptor_;std::map<int, Connection *> connections_;std::vector<EventLoop *> sub_reactors_;ThreadPool *thread_pool_;std::function<void(Connection *)> on_connect_callback_;std::function<void(Connection *)> on_message_callback_;std::function<void(Connection *)> new_connect_callback_;public:explicit Server(EventLoop *loop);~Server();DISALLOW_COPY_AND_MOVE(Server);void NewConnection(Socket *sock);void DeleteConnection(Socket *sock);void OnConnect(std::function<void(Connection *)> fn);void OnMessage(std::function<void(Connection *)> fn);void NewConnect(std::function<void(Connection *)> fn);
};
相关文章:

找工作小项目:day15-macOS支持、完善逻辑
macOS支持、完善逻辑 目前的代码可以在Linux上完美运行编译,在Windows上也可以通过WSL编译运行源代码,但是在MacBook上却无法运行编译,这主要是由于macOS上没有epoll,取而代之的很相似的kqueue。由于操作系统不同,我们…...

植物大战僵尸杂交版 v2.0.88 mac版 Plants vs. Zombies 杂交版下载
特别注意:该游戏最低系统要求为macOS Sonoma 14.X,低于此系统版本的请勿下载! 游戏介绍 植物大战僵尸杂交版是由B站UP主“潜艇伟伟迷”制作的一款结合了《植物大战僵尸》原有元素与创新玩法的游戏。这款游戏以其独特的“杂交”植物概念在B站…...

PHP中的while循环:用法、技巧与最佳实践
在PHP编程中,while循环是一种基本且常用的控制结构,用于重复执行代码块,直到指定条件为假。while循环在处理未知迭代次数的任务时特别有用,例如读取文件内容、处理用户输入或动态生成数据等。与for循环不同,while循环适…...

如何解决跨境传输常见的安全及效率问题?
在当今全球化的商业版图中,企业为了拓展国际市场和增强竞争力,跨境传输数据已成为一项不可或缺的业务活动。合格的数据跨境传输方案,应考虑以下要素: 法律合规性:确保方案符合所有相关国家的数据保护法律和国际法规&am…...

『大模型笔记』主成分分析(PCA)解释:简化机器学习中的复杂数据!
主成分分析(PCA)解释:简化机器学习中的复杂数据 文章目录 一. 主成分分析(PCA)解释:简化机器学习中的复杂数据!二. 参考文献一. 主成分分析(PCA)解释:简化机器学习中的复杂数据! 主成分分析(Principal Component Analysis,简称PCA)通过 将大型数据集中的维度减少…...

springboot与flowable(5):任务分配(表达式)
在做流程定义时我们需要给相关的用户节点指派对应的处理人。在flowable中提供了三种分配的方式。 一、固定分配 在分配用户时选择固定值选项确认即可。 二、表达式 1、值表达式 2、方法表达式 三、表达式流程图测试 1、导出并部署 导出流程图,复制到项目中 部署流…...

如何使用CCS9.3打开CCS3.0工程
如何使用CCS9.3打开CCS3.0工程 点菜单栏上的project,选择Import Legacy CCSv3.3 Porjects…,弹出对话框,通过Browse…按钮导入一个3.3版本的工程项目; 选择.pjt文件,选择Copy projects into worlkspace 右击选择P…...

Stable Diffusion 3 Medium 模型
开源SD3,中型版本,20亿参数,Stable Diffusion 3 Medium,系统内存要求32G,显卡6G。 a female character with long, flowing hair that appears to be made of ethereal, swirling patterns resembling the Northern Li…...

数据分析------统计学知识点(五)
回归算法 想象一下,你和朋友在讨论:大学生活中,每天学习的时间是否真的能影响期末成绩?这个问题看似简单,实则包含了一个潜在的关系:学习时间与成绩之间的联系。我们想要知道,增加学习时间是否会提高成绩,以及这种提…...

Superset二次开发之Git篇 git remote
背景:从GitHub clone Superset项目,基于3.0版本做二次开发,后续通过其他方式把3.0版本未做任何修改过的原始代码上传到企业GitLab库develop分支 任务:本地代码推送到GitLab库develop分支,但是两者似乎没有任何关联关系 操作步骤 克隆 Superset 3.0 版本的项目到本地: …...

记录一下PHP使用微信小程序支付
记录一下PHP使用微信小程序支付V3版本经历 官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml 请详细查看文档中小程序支付接入前准备(https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtmlÿ…...

【数据结构初阶】 --- 单链表
关于链表你应该先了解这些 下图描述了物理模型和逻辑模型,大多数常见的其实是逻辑模型,但这对初学者或者掌握不扎实的同学不太友好,所以这里我重点讲解物理模型,当了解了这些细节,以后做题或是什么就直接画逻辑模型就…...

并发、多线程、HTTP连接数有何关系?
在计算机领域,"并发"、"多线程"和"HTTP连接数"是三个重要的概念,它们之间存在着密切的关系。本文将探讨这三者之间的联系以及它们在现代计算机系统中的作用。 一、并发的概念 并发是指系统能够同时处理多个任务或事件的能…...

鸿蒙轻内核Kconfig使用笔记
鸿蒙轻内核使用Kconfig进行图形化配置,本文专门讲解下鸿蒙轻内核LiteOS-M和LiteOS-A的图形化配置方法。本文中所涉及的源码,均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_a 、 https://gitee.com/openharmony/kernel_liteos_m 获取。本…...

react 0至1 案例
/*** 导航 Tab 的渲染和操作** 1. 渲染导航 Tab 和高亮* 2. 评论列表排序* 最热 > 喜欢数量降序* 最新 > 创建时间降序* 1.点击记录当前type* 2.通过记录type和当前list中的type 匹配*/ import ./App.scss import avatar from ./images/bozai.png import {useState} …...

基于MCU平台的HMI开发的性能优化与实战(上)
随着汽车座舱智能化的不断演进,车内显示设备的数量显著增加,从传统的仪表盘和中控屏扩展至空调控制、扶手、副驾驶区域以及抬头显示(HUD)等多样化的显示单元。为了有效支持这些功能单元,同时控制整车成本,越…...

【Tkinter界面】Canvas 图形绘制(02/5)
文章目录 一、说明二、几何时使用 Canvas 组件2.1 用法2.2 简单范例2.3 对象移动2.4 对象删除2.5 文字对象显示 三、画布和画布对象3.1 画布生成函数原型3.2 使用create_xxx()方法3.3 对参数**options的解释 一、说明 Canvas(画布)组件为 Tkinter 的图形…...

1_常见指令【Linux中常见30个指令的学习和使用】【万字长文】
常见指令以及权限理解 开始学习linux前的注意事项 在学习linux之前,我们要知道linux是一个操作系统。 那操作系统是什么呢?(这里只做大概了解) 操作系统就是一个管理软硬件的软件。 它对上提供良好(稳定、高效、安…...

每日复盘-202406014
今日关注: 这几天市场打板情绪环境转好,轻仓试错 20240614 六日涨幅最大: ------1--------301036--------- 双乐股份 五日涨幅最大: ------1--------301036--------- 双乐股份 四日涨幅最大: ------1--------301036--------- 双乐股份 三日涨幅最大: ------1--------301082-…...

JavaScript 深拷贝和浅拷贝的实现、使用场景和存在的问题
浅拷贝 实现 方式 1(ES 5 语法): const params Object.assign({}, state.dataForm)方式 2(ES 6 语法): const params { ...state.dataForm }使用场景 copy 入参和出参 深拷贝 方式 1(手…...

8个常用的辅助函数!!
在开发各种项目时,我们会发现经常需要一些辅助函数来帮助我们实现一些需求,并且这些函数是在很多项目里都可以进行复用的。下面我就列出我们一些常用的辅助函数,来帮助大家在开发项目时,进行复用。 1. 首字母大写 将字符串的第一…...

服务器数据恢复—OceanStor存储中NAS卷数据丢失如何恢复数据?
服务器存储数据恢复环境&故障: 华为OceanStor某型号存储。工作人员在上传数据时发现该存储上一个NAS卷数据丢失,管理员随即关闭系统应用,停止上传数据。这个丢失数据的卷中主要数据类型为office文件、PDF文档、图片文件(JPG、…...

54.Python-web框架-Django-免费模板django-datta-able
1.Datta Able Django介绍 Detta Able Djiango是什么 Datta Able Django 是一个由AppSeed提供的开源Django管理面板,基于现代设计,为开发者提供了一流的功能和优雅的界面。它源自CodedThemes的高风格化Bootstrap 4模板——Datta Able Bootstrap Lite&…...

XP系统安装Node.js v8.6.0并搭建Vue2开发环境(项目兼容到Vista的IE9浏览器)
下载并安装Node.js v8.6.0 通常我们开发Vue2项目,是通过vue create命令建立Vue2工程,用npm run serve命令启动Vue2网站的。 vue命令是用JavaScript写的,不是用C语言写的,必须要Node.js环境才能运行,由Node.js自带的np…...

redis序列化
文章目录 1、为什么要进行序列化操作?2、序列化方式2.1、自定义序列化2. 2、StringRedisTemplate(重点) 1、为什么要进行序列化操作? 不进行序列化向redis存入数据代码: SpringBootTest class RedisDemoApplicationT…...

IOT-Tree 1.7.0实现了一个类似Node-Red的流程功能
本人一直研究这个软件,1.7.0版本最近刚刚发布,里面有个大变化,增加了消息流的功能,这个功能和IBM的Node-Red很相似。 Node-Red那个图形化流程很多年前就给了我很深刻的印象,我个人理解是,通过这样的图形化…...

nc网络收发测试-tcp客户端\TCP服务器\UDP\UDP广播
netcat(nc): 作用:一个功能强大的网络工具,提供了简单的网络测试和网络编程功能。工作原理:可以用于建立TCP或UDP连接,并发送和接收数据。示例用法: 监听TCP端口:nc -l 1…...

程序员该有怎么样的职业素养
目录 1、持续学习 2、解决问题的能力 3、团队协作能力 4、责任感 5、沟通能力 6、总结 作为一个从业者,我认为对于程序员而言,职业素养是非常重要的。职业素养不仅影响个人的职业发展,也影响团队和企业的整体氛围和效率。在我的职业生涯…...

51交通灯
一、基本原理 利用51单片机控制各个路口红绿灯及时间显示。 设计的重点: 1、各个路口红绿灯亮灭的规则,暂不考虑左转方向; 2、倒计时的实现,利用单片机的定时器进行计数得到秒信号; 3、时间显示:东西南…...

鸿蒙Arkts上传图片并获取接口返回信息
需求: 选择相册图片后,将文件上传到服务器,接口会返回图片地址。 问题: 1、鸿蒙自带的文件上传返回值只会返回上传状态,不会返回接口返回信息。 类似问题 HarmonyOS上传文件以及权限授权_harmonyos中axios上传文件…...