0%

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器(3、协程模块的设计)

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器(6、HOOK模块的设计)

重写Sylar基于协程的服务器(7、TcpServer & HttpServer的设计与实现)

简述

sylar的IOManager模块本质上就是一个事件池,主要负责向epoll中注册事件和回调。实现了Idle协程的回调,回调是一个阻塞在epoll_wait上的事件循环,将有IO事件发生的协程唤醒。

核心数据结构

将socketfd封装成一个结构体FdContext,对于fd上的读写事件封装成EventContext,提供的TrigleEvent函数,在fd有读写事件发生时,将相应读写事件的EventContext::m_fiber成员放到EventContext::m_scheduler调度器中,可以唤醒阻塞的协程。FdContext结构体定义如下。

阅读全文 »

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器(3、协程模块的设计)

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器(6、HOOK模块的设计)

重写Sylar基于协程的服务器(7、TcpServer & HttpServer的设计与实现)

简述

协程调度模块,让线程池里的每个线程都运行调度协程,并不断切换去执行协程任务。

协程调度器整体架构图

sylar实现的协程是非对称协程,虽然就调度器的架构看来,很反人类,一眼看去很像是对称协程。

问了一下GPT,回答如下:

实现了 IO Hook 模块的协程通常是非对称协程模型。在异步编程中,IO Hook 通常用于异步 IO 操作,而非对称协程模型更适合处理异步 IO 操作。

在非对称协程模型中,一个主协程(通常是事件循环或主任务)可以通过 IO Hook 来注册感兴趣的 IO 事件,并在事件发生时启动相应的协程执行。这样的模型更适用于事件驱动的编程,其中主协程负责管理整体的控制流,而子协程负责处理具体的 IO 操作。

协程调度器模块的设计是基于线程池来完成的,对线程池进行协程的定制化改造,让线程池模型能够适应协程的切换,如图:

协程调度器架构

协程调度模块设计

  1. 构造函数,用户创建协程调度器主要的参数有,设置参与协程调度的线程数量threadCount、主线程是否参与协程调度等,构造函数首先会为主线程原始的上下文创建一个协程(t_threadFiber),其次,如果用户指定了主协程需要参与协程调度,就会为成员变量m_rootFiber创建一个回调函数是Scheduler::run()的协程,并且指定该协程与t_threadFiber做上下文切换。主线程等待后面延迟将m_rootFiber切入,进入Scheduler::run()函数后,t_threadFiber保存主线程原始上下文,t_schRunFiber赋值为m_rootFiber即运行Scheduler::run()函数的协程。而子线程运行的回调函数就是Scheduler::run()函数,所以,子线程的t_threadFiber和t_schRunFiber是同一个协程对象。并且,因为主线程充当了一个调度协程,所以,创建子线程的时候,会少创建一个线程,即子线程的数量等于threadCount-1。当用户没有要使用主线程充当调度协程时,调度器最后会创建threadCount个子线程。

  2. Scheduler::run,协程调度部分,进入调度函数最开始会初始化t_threadFiber和t_schRunFiber变量,然后进入调度循环,在调度循环中,首先到任务队列中取任务,取到任务时,判断任务是协程还是回调,如果是协程,判断协程状态的合法性,只有合法的协程才能切入去执行,对于回调,会被封装成协程,再切入去执行。如果没有任务,就会去执行idle协程,idle协程是IO协程调度模块的重点,主要负责等待事件的到来然后唤醒相应任务协程。

    协程调度函数Scheduler::run伪代码:

阅读全文 »

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器(3、协程模块的设计)

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器(6、HOOK模块的设计)

重写Sylar基于协程的服务器(7、TcpServer & HttpServer的设计与实现)

配置模块存在的必要性

一个服务器软件可能会运行在不同的机器上,机器的配置、网络环境、实际需求等都是千变万化,在服务器软件中,为了适应这些变化,可能就是调整几个变量的值。开发人员不可能每次外在因素的改变就重新编译软件再发布,这明显是不现实的。于是配置模块就在这时发挥它的关键作用,利用好配置模块就不需要再次编译,让配置模块自己加载参数,动态调节就行了。

配置模块的设计与实现

配置模块序列化和反序列化效果(支持std::各种容器

YAML 的基本语法如下:

  1. 大小写敏感。

  2. 利用缩进表示层级关系,缩进只能使用空格,空格的数量不重要。

  3. ‘#’表示注释。

  4. 数据类型:对象,键值对的集合,即K-V对。数组,一组按次序排列的值。纯量(scalars),单个的、不可再分的值,包括字符串、布尔值、整数、浮点数、Null、时间、日期。

测试配置文件定义了一个key为space,value也是一个map类型的节点,该map有两个kv对,它们的key分别是vec、num,value分别是数组类型和纯量类型。如下

test_config.yml:

阅读全文 »

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器(3、协程模块的设计)

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器(6、HOOK模块的设计)

重写Sylar基于协程的服务器(7、TcpServer & HttpServer的设计与实现)

前言

关于线程以及线程并发的封装在此略过该部分比较简单,有兴趣的朋友可以看一下我原来写的Muduo的博客:muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)muduo源码阅读笔记(3、线程和线程池的封装),或者直接阅读本文配套的简化版sylar的源码:https://github.com/LunarStore/lunar

协程模块的设计与实现

协程的状态定义

协程分为:初始化状态、执行状态、阻塞状态、就绪状态、结束状态、异常状态。定义如下:

1
2
3
4
5
6
7
8
enum State{
INIT, // 初始化
EXEC, // 执行
HOLD, // 阻塞
READY, // 就绪
TERM, // 结束
EXCE // 异常
};

协程的状态机

任务协程:

阅读全文 »

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器(3、协程模块的设计)

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器(6、HOOK模块的设计)

重写Sylar基于协程的服务器(7、TcpServer & HttpServer的设计与实现)

前言

和Muduo的日志对比,Muduo的同步日志虽然格式固定,但简单高性能。而sylar的日志设计的显得过于冗余,虽然灵活性强、扩展性高,但是性能却不及Muduo。尽管陈硕大大也说过,简单才能保证高性能,日志就没必要设计的那么花里胡哨,但是sylar对日志的设计思想以及设计模式超级超级适合小白去学习。

日志格式

由于用户可能并不需要将日志上下文的每一项都进行输出,而是希望可以自由地选择要输出的日志项。并且,用户还可能需要在每条日志里增加一些指定的字符,比如在文件名和行号之间加上一个冒号的情况。为了实现这项功能,LogFormatter使用了一个模板字符串来指定格式化的方式。模板字符串由普通字符和占位字符构成。在构造LogFormatter对象时会指定一串模板字符,LogFormatter会首先解析该模板字符串,将其中的占位符和普通字符解析出来。在格式化日志上下文时,根据模板字符串,将其中的占位符替换成日志上下文的具体内容,普通字符保持不变。下表是支持的占位符的含义。

占位符 含义
%s 普通字符串(直接输出的字符串)
%d 时间
%t 线程真实id
%N 线程名
%f 协程id
%p 日志级别
%c 日志器名
%F 文件路径
%l 行号
%m 日志消息
%T Tab缩进
%n 换行

%d{%Y-%m-%d %H:%M:%S}%T%t%T%N%T%f%T[%p]%T[%c]%T%F:%l%T%m%n格式串为例,输出效果如图下:

阅读全文 »

重写Sylar基于协程的服务器系列:

重写Sylar基于协程的服务器(0、搭建开发环境以及项目框架 || 下载编译简化版Sylar)

重写Sylar基于协程的服务器(1、日志模块的架构)

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器(3、协程模块的设计)

重写Sylar基于协程的服务器(4、协程调度模块的设计)

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器(6、HOOK模块的设计)

重写Sylar基于协程的服务器(7、TcpServer & HttpServer的设计与实现)

前言

sylar是一个基于协程的服务器框架。同go语言思想一样,整个框架贯彻协程化的思想,淡化线程的存在。笔者有幸反复阅读sylar数次,并重写过base核心模块。该项目是我真正入门C++的第一个项目,我也将其作为本科毕设,顺利通过答辩。非常感谢sylar的作者能将多年从业经验浓缩在这个项目当中,这真的是为后来者点了一扇关键的灯。

环境搭建以及下载安装

开发环境参考如下表:

环境 版本
Linux操作系统 CentOS 7.5 64位(2核2G)
G++编译器 4.8.5
CMake 3.14.5
C++标准 C++11
项目调试工具 GDB
项目开发工具 VSCode

搭建项目框架:

本系统的文件结构如图所示。项目根目录有CMakeList文件和Makefile文件,也即有两种编译方式。编译输出的中间文件输出在build目录,二进制文件输出在bin目录下,静态库以及动态库输出在lib目录下,src目录存放项目源码,源码划分为基础模块、网络模块、初始化模块、以及HTTP模块,本文重点集中在基础模块,即src/base目录下的文件。bin目录存放的是二进制可执行程序。bin/conf以及bin/module目录分别存放的是,可执行程序的配置文件以及动态库模块。

项目结构

下载、编译、试玩重写的简化版sylar:

阅读全文 »

Muduo源码笔记系列:

muduo源码阅读笔记(0、下载编译muduo)

muduo源码阅读笔记(1、同步日志)

muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)

muduo源码阅读笔记(3、线程和线程池的封装)

muduo源码阅读笔记(4、异步日志)

muduo源码阅读笔记(5、Channel和Poller)

muduo源码阅读笔记(6、ExevntLoop和Thread)

muduo源码阅读笔记(7、EventLoopThreadPool)

muduo源码阅读笔记(8、定时器TimerQueue)

muduo源码阅读笔记(9、TcpServer)

muduo源码阅读笔记(10、TcpConnection)

muduo源码阅读笔记(11、TcpClient)

前言

本章新涉及的文件有:

  1. TcpClient.h/cc:和TcpServer不同的是,TcpClient位于客户端,主要是对客户发起的连接进行管理,TcpClient只有一个loop,也会和TcpConnection配合,将三次握手连接成功的sockfd交由TcpConnection管理。

  2. Connector.h/cc:Muduo将一个客户端的sock分成了两个阶段,分别是:连接阶段、读写阶段,Connector就是负责fd的连接阶段,当一个sockfd连接成功后,将sockfd传给TcpClient,由TcpClient将sockfd传给TcpConnection进行读写管理,Connector和TcpServer的Acceptor在设计上有这类似的思想,不同的是,Connector是可以针对同一个ip地址进行多次连接,产生不同的sockfd、而Acceptor是去读listen sock来接收连接,产生不同sockfd。

总体来说,TcpClient的实现是严格遵循TcpServer的实现的,

Connector的实现

提供的接口:

阅读全文 »

Muduo源码笔记系列:

muduo源码阅读笔记(0、下载编译muduo)

muduo源码阅读笔记(1、同步日志)

muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)

muduo源码阅读笔记(3、线程和线程池的封装)

muduo源码阅读笔记(4、异步日志)

muduo源码阅读笔记(5、Channel和Poller)

muduo源码阅读笔记(6、ExevntLoop和Thread)

muduo源码阅读笔记(7、EventLoopThreadPool)

muduo源码阅读笔记(8、定时器TimerQueue)

muduo源码阅读笔记(9、TcpServer)

muduo源码阅读笔记(10、TcpConnection)

前言

本章涉及两个新模块:TcpConnection、Buffer。本文重点集中在TcpConnection上,对于Buffer会进行简单的描述。

Buffer

Muduo的Buffer类实际上就是基于vector<char>实现了一个缓存区,在vector的基础上,自己封装了扩容和缩容的接口。每个TcpConnection都会自带两个Buffer,一个读缓存区和一个写缓存区。

这里只列出TcpConnection用到的接口的实现。

提供的接口:

阅读全文 »

Muduo源码笔记系列:

muduo源码阅读笔记(0、下载编译muduo)

muduo源码阅读笔记(1、同步日志)

muduo源码阅读笔记(2、对C语言原生的线程安全以及同步的API的封装)

muduo源码阅读笔记(3、线程和线程池的封装)

muduo源码阅读笔记(4、异步日志)

muduo源码阅读笔记(5、Channel和Poller)

muduo源码阅读笔记(6、ExevntLoop和Thread)

muduo源码阅读笔记(7、EventLoopThreadPool)

muduo源码阅读笔记(8、定时器TimerQueue)

muduo源码阅读笔记(9、TcpServer)

muduo源码阅读笔记(10、TcpConnection)

前言

本章涉及的文件有:

  1. TcpServer.h/cc:一个主从Reactor模型的TcpServer,主EventLoop接收连接,并且将连接sock fd负载均衡分发给一个IOLoop。

  2. Acceptor.h/cc:一个监听套接字的包装器,内部创建了一个Channel管理连接套接字的回调。

  3. Socket.h/cc:封装原生socket,提供绑定、监听、接受连接、设置socket属性等接口。

  4. SocketsOps.h/cc:Socket.h/cc接口的底层实现,在创建套接字(::socket()/::accept())时,会将socketfd设置为非阻塞。

  5. InetAddress.h/cc:对sockaddr_in/sockaddr_in6网络地址进行封装,使其更方便使用。

本章重点集中在1、2,对于3、4、5,见名知意即可,感兴趣的读者,可以自行深入阅读。

Acceptor的实现

提供的接口:

阅读全文 »