新闻详情

新闻详情

首页 / 资讯中心 / 详情

30天自制C++服务器(day15):macOS/kqueue 跨平台支持与 OnMessage 业务逻辑自定义

发布时间:2026/10/2 1:55:27来源:尧图网络
30天自制C++服务器(day15):macOS/kqueue 跨平台支持与 OnMessage 业务逻辑自定义
示例工程【免费下载链接】30dayMakeCppServer30天自制C服务器包含教程和源代码项目地址https://gitcode.com/GitHub_Trending/30/30dayMakeCppServer点击查看免费下载导读本篇文章对应仓库教程 day15-macOS支持、完善业务逻辑自定义核心解决两个问题一是让基于 epoll 的事件驱动网络库能在 macOS/FreeBSD 上通过 kqueue 原生编译运行二是把业务逻辑的进入点从可读事件触发后自己处理改进为服务器完整读完数据之后回调进一步隐藏网络细节。读完本文你将掌握用位标志设计跨平台事件抽象、用宏定义隔离平台代码、以及用OnMessage回调编写 echo 服务器等实战能力。一、问题背景epoll 是 Linux 专属macOS 没有作为程序员使用 MacBook 作为开发机非常常见其本质与 Linux 几乎没有区别。但本教程EventLoop中一直使用的是 Linux 内核提供的epoll而 macOS 中并没有 epoll取而代之的是 FreeBSD 的kqueue功能和用法与 epoll 十分相似。Windows 用户可以通过 WSL 完美编译运行本仓库源码但 MacBook 用户此前只能借助 Docker、云服务器或虚拟机非常麻烦。day15 的目标就是使用 kqueue 作为EventLoop的 Poller让网络库在 macOS 等 FreeBSD 系统上原生运行。二、架构分层为什么只需要改动最底层网络库已有的类中Socket和Epoll是最底层的、直接与操作系统打交道的类而上一层的EventLoop类只是使用Epoll提供的接口并不关心其底层实现见 EventLoop.h 中EventLoop仅持有Poller *poller_通过UpdateChannel、DeleteChannel、Poll三个接口工作。因此在考虑支持不同操作系统时只应该改变最底层的 Poller 实现而不需要改动上层的EventLoop类。day15 中代码去掉了原来的Epoll类改为通用的Poller在不同平台自适应地编译不同的代码这正是依赖倒置思想在实践中的体现。三、Channel 自定义事件用位标志与操作系统解耦对于分发fd的Channel类epoll 和 kqueue 的读、写、ET 模式等事件各自不同因此需要自定义一套与平台无关的事件在Channel中只注册自定义事件再由Poller映射到 epoll 或 kqueue 的真实事件。Channel中定义了三个静态常量见 Channel.cppconst int Channel::READ_EVENT 1; const int Channel::WRITE_EVENT 2; const int Channel::ET 4;需要注意用户自定义事件必须是 1、2、4、8、16 等十进制数即 2 的幂次因为在Poller中判断、更新事件时需要用到按位与、按位或|等操作。这里的本质是将一个整数的每一个二进制位当作一个标志位bit flag从而可以用一次整型运算表达同时监听读 写 ET等组合状态。例如READ_EVENT | WRITE_EVENT表示同时监听读写。如果对位运算理解有困难可以先学习《深入理解计算机系统第三版》相关章节。Channel的对外行为保持不变见 Channel.cppEnableRead()/EnableWrite()把对应监听事件置位并调用loop_-UpdateChannel(this)注册到 PollerUseET()把 ET 事件置位并注册HandleEvent()根据ready_events_判断当前就绪事件调用对应的read_callback_/write_callback_。四、Poller用宏定义隔离平台代码Poller类在头文件中按操作系统包含不同的头文件见 Poller.h#ifdef OS_LINUX #include sys/epoll.h #endif #ifdef OS_MACOS #include sys/event.h #endif成员变量events_也根据平台分别指向epoll_event或kevent数组构造函数中分别调用epoll_create1(0)与kqueue()创建实例见 Poller.cpp。4.1 操作系统宏由 CMake 定义宏定义放在 CMakeLists.txt 中根据CMAKE_SYSTEM_NAME判断平台并注入编译选项if (CMAKE_SYSTEM_NAME MATCHES Darwin) message(STATUS Platform: macOS) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -DOS_MACOS) elseif (CMAKE_SYSTEM_NAME MATCHES Linux) message(STATUS Platform: Linux) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -DOS_LINUX) endif()CMake 的CMAKE_SYSTEM_NAME在 macOS 上为Darwin在 Linux 上为Linux因此编译时只会定义其中一个宏对应平台代码才会被编译。4.2 Linux 实现注册/更新 Channel在 Linux 系统下Poller::UpdateChannel编译为以下代码见 Poller.cppvoid Poller::UpdateChannel(Channel *ch) { int sockfd ch-GetSocket()-GetFd(); struct epoll_event ev {}; ev.data.ptr ch; // 将 Channel 指针挂到事件上就绪时直接取回 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); } }要点ev.data.ptr ch把用户数据直接指向Channel对象Poll返回后无需再查询 fd 到对象的映射是否已注册由Channel::GetExist()标记决定首次用EPOLL_CTL_ADD之后用EPOLL_CTL_MODErrorIf封装错误检查见 util.h。对应的Poll将内核事件反向映射回自定义事件EPOLLIN → READ_EVENT、EPOLLOUT → WRITE_EVENT、EPOLLET → ET然后调用ch-SetReadyEvents(...)并收集到活动 Channel 列表返回。DeleteChannel使用EPOLL_CTL_DEL删除事件。4.3 macOS 实现kqueue 版本在 macOS 系统下同样的Poller::UpdateChannel编译为 kqueue 版本见 Poller.cppvoid 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; // kqueue 的边缘触发等价物 } 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); }与 epoll 版本的关键差异kqueue 没有epoll_event而是用struct kevent数组描述事件通过EV_SET宏填充udata字段最后一个参数保存Channel *指针一个kevent调用可以同时提交多个事件ev数组、n个而 epoll 一次epoll_ctl只操作一个 fd 的一组事件ET 模式对应EV_CLEAR事件被报告后自动清除需配合非阻塞读循环删除时使用EV_DELETE代替EV_ADD见 Poller.cpp 的DeleteChannel。Poll函数中kqueue 版本把timeout转换为struct timespectv_sec毫秒转秒、tv_nsec取余毫秒转纳秒timeout -1时传NULL表示无限等待就绪事件通过events_[i].udata取回Channel *并按EVFILT_READ / EVFILT_WRITE映射为READ_EVENT | ET/WRITE_EVENT | ETkqueue 边缘触发下读事件通常伴随 ET 语义见 Poller.cpp。五、完善业务逻辑自定义进入点后移到读完数据之后在之前的教程中Connection类以OnConnect回调函数的方式初步支持了业务逻辑自定义但自定义的业务逻辑是从服务器端可读事件触发后开始进入的需要自己处理读取数据的逻辑。这显然不合理怎样事件触发、读取数据、异常处理等流程应该是网络库提供的基本功能用户只应当关注怎样处理业务。因此业务逻辑的进入点应该改为服务器读取完客户端的所有数据之后。此时客户端传来的请求已经在Connection类的读缓冲区read_buffer_里用户只需根据请求分发、处理业务即可。day15 通过设置OnMessage回调函数实现这一设计。在服务器完全接收到客户端数据之后该函数触发。源码层面的实现链如下见 Connection.cppSetOnMessageCallback(callback)保存业务回调并把它包装为Business()绑定到Channel的读回调channel_-SetReadCallbackBusiness()内部先调用Read()完成读取数据到读缓冲区这一网络库职责再调用on_message_callback_(this)进入用户业务见 Connection.cppRead()按非阻塞/阻塞区分ReadNonBlocking/ReadBlocking非阻塞版本用while(true)循环 EAGAIN/EWOULDBLOCK判断数据全部读完并处理EINTR中断与 EOF 断连全部读完后数据才留在read_buffer_中Buffer *GetReadBuffer()与const char *ReadBuffer()分别暴露缓冲对象与 C 字符串形式的已读数据。5.1 一个 echo 服务器的业务逻辑以下是一个完整的 echo 服务器业务逻辑与 echo_server.cpp 一致server-OnMessage([](Connection *conn){ std::cout Message from client conn-ReadBuffer() std::endl; if(conn-GetState() Connection::State::Connected){ conn-Send(conn-ReadBuffer()); } });在进入该函数之前服务器已经完成了接受客户端数据并保存在读缓冲区里业务逻辑只需要将读缓冲区里的数据发送回即可——Send内部通过SetSendBuffer写入发送缓冲区并调用Write()写回客户端。这样的设计更加符合服务器的功能准则与设计准则网络库负责 I/O 与状态管理用户只写核心业务。六、运行与验证day15 采用 CMake 工程化结构源码在 code/day15/src测试程序在 code/day15/testecho、chat、http 服务器等由 test/CMakeLists.txt 自动收集并生成可执行目标。在 Linux 或 macOS 上可分别执行mkdir build cd build cmake .. make # 运行 echo 服务器示例 ./bin/echo_server构建时会打印Platform: Linux或Platform: macOS提示当前平台。若在 macOS 上构建成功说明 kqueue 分支代码已正确编译。七、小结与已知限制day15 完成了两件事跨平台支持去掉Epoll类改为通用的Poller通过-DOS_LINUX/-DOS_MACOS宏自适应编译 epoll 或 kqueue 代码Channel与操作系统脱离开来通过自定义事件1/2/4 等位标志表示监听与发生的事件。现在网络库在 Linux 和 macOS 上都可以原生编译运行。业务逻辑自定义完善通过OnMessage回调把业务进入点统一放到数据读完之后进一步简化服务器编程、隐藏更多细节使用者只需完全关注核心业务逻辑。需要说明的是具体功能可能因操作系统不同存在细微差异例如 macOS 平台下的并发支持度明显不如 Linux 平台高这部分会在后续开发中不断完善day16 起将重构核心库并引入智能指针见仓库后续目录。赞分享示例工程【免费下载链接】30dayMakeCppServer30天自制C服务器包含教程和源代码项目地址https://gitcode.com/GitHub_Trending/30/30dayMakeCppServer点击查看免费下载相关推荐gods-eye-view CCTV 地面高程预计算为监控相机监控平面提供零采样放置的侧车数据gods eye view CCTV 地面高程预计算为监控相机监控平面提供零采样放置的侧车数据 导读 gods eye view https://link.g示例工程30天自制C服务器从零掌握TLV自定义协议设计与实现30天自制C服务器从零掌握TLV自定义协议设计与实现 在网络通信中自定义协议是实现高效数据交换的核心。本文将带你通过30天自制C服务器项目系统学习示例工程RxDB 附件Attachments完全指南在 RxDocument 中存储二进制数据、加密与压缩RxDB 附件Attachments完全指南在 RxDocument 中存储二进制数据、加密与压缩 导读 本文围绕 RxDB 的 attachments示例工程上一篇Magika扩展开发指南编写自定义内容类型检测器插件下一篇终极Serverless部署指南为什么Up/Up是传统工具的最佳替代方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

强化学习实战:用DQN从零训练坦克大战智能体 2026/10/2 2:44:58

强化学习实战:用DQN从零训练坦克大战智能体

简介:这份资源面向想入门强化学习与游戏AI的Python开发者,以经典坦克大战为载体,演示如何用BP神经网络结合强化学习训练AI坦克自动索敌与攻击。包内共84个文件,以41张png贴图、18个py脚本、14个pyc缓存为主,另含xml配置…

阅读更多 →
混合模型时间序列预测实战:LSTM与Transformer融合路径 2026/10/2 2:44:51

混合模型时间序列预测实战:LSTM与Transformer融合路径

简介:这份资源面向具备一定深度学习基础、希望上手时间序列预测实战的开发者与学习者,聚焦LSTM与Transformer混合模型的构建与落地。内容围绕如何将LSTM的局部序列建模能力与Transformer自注意力机制的全局依赖捕捉相结合,用于处理金融、气象…

阅读更多 →
纯PyTorch手写Transformer:本科生可跑通的轻量级中文问答模型 2026/10/2 2:44:51

纯PyTorch手写Transformer:本科生可跑通的轻量级中文问答模型

简介:本资源是一套基于Transformer架构实现的单轮对话机器人完整Python项目,面向计算机、人工智能、通信工程等专业的本科生及初学者,适用于毕业设计、课程设计与AI实践入门。项目包含训练代码、数据预处理脚本、配置文件、词表与模型权重等核…

阅读更多 →
从Kubernetes到Serverless容器:成本降70%的迁移实战 2026/10/2 2:44:51

从Kubernetes到Serverless容器:成本降70%的迁移实战

1. 迁移决策:我们为什么放弃 Kubernetes事情得从去年的一次成本复盘说起。当时我们的业务大概有 15 个后端服务跑在自建的 Kubernetes 集群上,3 个 4C8G 的工作节点,加上负载均衡、云盘、日志收集,每个月账单算下来差不多 5000 块…

阅读更多 →
提示词工程入门到实践:大模型稳定输出的系统方法 2026/10/2 2:44:51

提示词工程入门到实践:大模型稳定输出的系统方法

同一个大模型,为什么在不同人手里效果差别巨大?有人把它当成“高级搜索框”,一问一答就结束;有人却能用它产出结构化数据、稳定代码,甚至搭起一个业务原型。真正拉开差距的,往往不是模型本身,而…

阅读更多 →
Android Studio 从安装到运行:SDK配置、模拟器与APK打包全指南 2026/10/2 2:44:50

Android Studio 从安装到运行:SDK配置、模拟器与APK打包全指南

聊到Android开发,不管你是刚入行的学生、准备转行的程序员,还是想给自己做个App试试水的人,第一道门槛基本都是同一个:把Android Studio装好,并且成功跑起来一个程序。这个工具就是Android开发的绝对主力,从…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉