此项目是根据sylar框架实现,是从零开始重写sylar,也是对sylar丰富与完善
项目地址:https://gitee.com/lzhiqiang1999/server-framework
项目介绍:实现了一个基于协程的服务器框架,支持多线程、多协程协同调度;支持以异步处理的方式提高服务器性能;封装了网络相关的模块,包括socket、http、servlet等,支持快速搭建HTTP服务器或WebSokcet服务器。
详细内容:日志模块,使用宏实现流式输出,支持同步日志与异步日志、自定义日志格式、日志级别、多日志分离等功能。线程模块,封装pthread相关方法,封装常用的锁包括(信号量,读写锁,自旋锁等)。IO协程调度模块,基于ucontext_t实现非对称协程模型,以线程池的方式实现多线程,多协程协同调度,同时依赖epoll实现了事件监听机制。定时器模块,使用最小堆管理定时器,配合IO协程调度模块可以完成基于协程的定时任务调度。hook模块,将同步的系统调用封装成异步操作(accept, recv, send等),配合IO协程调度能够极大的提升服务器性能。Http模块,封装了sokcet常用方法,支持http协议解析,客户端实现连接池发送请求,服务器端实现servlet模式处理客户端请求,支持单Reator多线程,多Reator多线程模式的服务器。
// 上下文结构体定义
// 这个结构体是平台相关的,因为不同平台的寄存器不一样
// 下面列出的是所有平台都至少会包含的4个成员
typedef struct ucontext_t {
// 当前上下文结束后,下一个激活的上下文对象的指针,只在当前上下文是由makecontext创建时有效
struct ucontext_t *uc_link;
// 当前上下文的信号屏蔽掩码
sigset_t uc_sigmask;
// 当前上下文使用的栈内存空间,只在当前上下文是由makecontext创建时有效
stack_t uc_stack;
// 平台相关的上下文具体内容,包含寄存器的值
mcontext_t uc_mcontext;
...
} ucontext_t;
// 获取当前的上下文
int getcontext(ucontext_t *ucp);
// 恢复ucp指向的上下文,这个函数不会返回,而是会跳转到ucp上下文对应的函数中执行,相当于变相调用了函数
int setcontext(const ucontext_t *ucp);
// 修改由getcontext获取到的上下文指针ucp,将其与一个函数func进行绑定,支持指定func运行时的参数,
// 在调用makecontext之前,必须手动给ucp分配一段内存空间,存储在ucp->uc_stack中,这段内存空间将作为func函数运行时的栈空间,
// 同时也可以指定ucp->uc_link,表示函数运行结束后恢复uc_link指向的上下文,
// 如果不赋值uc_link,那func函数结束时必须调用setcontext或swapcontext以重新指定一个有效的上下文,否则程序就跑飞了
// makecontext执行完后,ucp就与函数func绑定了,调用setcontext或swapcontext激活ucp时,func就会被运行
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
// 恢复ucp指向的上下文,同时将当前的上下文存储到oucp中,
// 和setcontext一样,swapcontext也不会返回,而是会跳转到ucp上下文对应的函数中执行,相当于调用了函数
// swapcontext是本模块非对称协程实现的关键,线程主协程和子协程用这个接口进行上下文切换
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
Logger::ptr logger = LOG_ROOT();
void run_in_fiber() {
LOG_INFO(logger) << "run in fiber";
}
int main() {
LOG_INFO(logger) << "main begin";
Fiber::Getthis();
Fiber::ptr fiber(new Fiber(run_fiber))
fiber.call();
LOG_INFO(logger) << "main end";
return 0;
}
enum State
{
INIT , // 初始状态
READY, // 可执行状态
RUNNING, // 运行状态
TERM, // 结束状态
};
class Fiber : public std::enable_shared_from_this<Fiber>
{
public:
typedef std::shared_ptr<Fiber> ptr;
public:
///
/// 无参构造函数
/// 每个线程第一个协程的构造,主协程
///
Fiber();
public:
///
/// 构造函数
///
/// 协程执行函数
/// 协程栈大小
/// Schedule中是否使用主线程
Fiber(std::function<void()> cb, size_t stacksize = 0, bool use_caller = false);
~Fiber();
//重置协程函数,并重置状态
//pre: getState() 为 INIT, TERM, EXCEPT
//post: INIT
void reset(std::function<void()> cb);
// 将当前协程切换到运行状态, Scheduler调度线程 --> 当前线程
void swapIn();
// 退出当前协程,当前协程 --> Scheduler调度协程
void swapOut();
// 将当前协程切换到执行状态 主协程-->当前协程
void call();
// 将当前协程切换到后台,当前协程-->主协程
void back();
public:
///
/// 设置当前线程的运行协程
///
/// 运行协程
static void SetThis(Fiber* fiber);
///
/// 返回当前协程
///
/// 一个全局静态变量 static thread_globle Thread* t_fiber
static Fiber::ptr GetThis();
// 协程切换到后台,并设置为READY状态。回到Scheduler协程
static void YieldToReadyBySwap();
// 协程切换到后台,并设置为HOLD状态。回到Scheduler协程
static void YieldToHoldBySwap();
// 协程切换到后台,并设置为Ready状态。回到主协程
static void YieldToReadyByBack();
// 协程切换到后台,并设置为HOLD状态。回到主协程
static void YieldToHoldByBack();
///
/// 协程的工作函数,执行完成返回到Scheduler协程
///
static void MainFunc();
/**
* @brief 协程执行函数
* @post use_caller时有效,Scheduler会使用主线程的时候
*/
static void CallerMainFunc();
private:
uint64_t m_id = 0; //协程id
uint32_t m_stacksize = 0; //协程运行栈大小
State m_state = INIT; //协程状态
ucontext_t m_ctx; //协程上下文
void* m_stack = nullptr; //协程运行栈指针
std::function<void()> m_cb; //协程工作函数
};
Fiber::Fiber() {
m_state = EXEC; //主协程创建好后,是运行中状态
SetThis(this); //设置当前协程 t_fiber
if (getcontext(&m_ctx)) { DO_ASSERT2(false, "getcontext"); }
++s_fiber_count;
m_id = ++s_fiber_id;
LOG_DEBUG(g_logger) << "Fiber::Fiber main id=" + std::to_string(GetId());
}
Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller)
:m_id(++s_fiber_id)
,m_cb(cb)
{
++s_fiber_count;
m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
m_stack = StackAllocator::Alloc(m_stacksize); // 分配协议栈
if (getcontext(&m_ctx)) { DO_ASSERT2(false, "getcontext"); }
m_ctx.uc_link = nullptr;
m_ctx.uc_stack.ss_sp = m_stack;
m_ctx.uc_stack.ss_size = m_stacksize;
// Scheduler调度协程中使用主线程参与调度
if (use_caller)
{
makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
}
else
{
makecontext(&m_ctx, &Fiber::MainFunc, 0);
}
}
// 主协程-->子协程
void Fiber::call() {
// 设置当前执行的协程:主协程-->字协程
SetThis(this);
m_state = EXEC;
if (swapcontext(&t_threadFiber->m_ctx, &m_ctx)) { DO_ASSERT2(false, "swapcontext"); }
}
// 子协程-->主协程
void Fiber::back() {
// 设置当前执行的协程:子协程-->主协程
SetThis(t_threadFiber.get());
if (swapcontext(&m_ctx, &t_threadFiber->m_ctx)) { DO_ASSERT2(false, "swapcontext"); }
}
//协程切换到后台,并设置为READY状态
//回到主协程
void Fiber::YieldToReadyByBack() {
Fiber::ptr cur = GetThis();
cur->m_state = READY;
cur->back();
}
//协程切换到后台,并设置为HOLD状态
//回到主协程
void Fiber::YieldToHoldByBack() {
Fiber::ptr cur = GetThis();
cur->m_state = HOLD;
cur->back();
}
use_caller
控制是否把主线程加入到调度协程任务上,因此涉及到调度协程和子协程间的切换,主协程和子协程间的切换Fiber::Fiber(std::function<void()> cb, size_t stacksize, bool use_caller)
{
//....
// Scheduler调度协程中使用主线程参与调度
if (use_caller)
{
// 在主协程和主协程间切换
makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
}
else
{
// 在调度协程和子协程间切换
makecontext(&m_ctx, &Fiber::MainFunc, 0);
}
}
void Fiber::swapIn() // 调度协程-->当前协程
void Fiber::swapOut() // 当前协程-->调度协程
/**
* @brief 协程入口函数
* @note 这里没有处理协程函数出现异常的情况,同样是为了简化状态管理,并且个人认为协程的异常不应该由框架处理,应该由开发者自行处理
*/
void Fiber::CallerMainFunc() {
Fiber::ptr cur = GetThis(); // GetThis()的shared_from_this()方法让引用计数加1
SYLAR_ASSERT(cur);
cur->m_cb(); // 这里真正执行协程的入口函数
cur->m_cb = nullptr;
cur->m_state = TERM;
auto raw_ptr = cur.get(); // 手动让t_fiber的引用计数减1
cur.reset();
raw_ptr->back(); // 协程结束时自动back,以回到主协程
}
// 只有TERM状态的协程才可以重置
void Fiber::reset(std::function<void()> cb) {
DO_ASSERT(m_stack);
DO_ASSERT(m_state == TERM || m_state == INIT);
m_cb = cb;
if (getcontext(&m_ctx)) {
DO_ASSERT2(false, "getcontext");
}
m_ctx.uc_link = nullptr;
m_ctx.uc_stack.ss_sp = m_stack;
m_ctx.uc_stack.ss_size = m_stacksize;
makecontext(&m_ctx, &Fiber::MainFunc, 0);
m_state = READY;
}
static thread_local
//当前运行协程
static thread_local Fiber* t_fiber = nullptr;
//主协程
static thread_local Fiber::ptr t_threadFiber = nullptr;
t_fiber
是当前运行的协程,t_threadFiber
是主协程,通过setThis()
设置当前运行的协程,getThis()
获取当前的协程
// 没有当前协程时,会创建一个主协程
Fiber::ptr Fiber::GetThis()
void Fiber::SetThis(Fiber* fiber)
使用swapcontext
来做协程切换,意味着,这两个线程局部变量必须至少有一个是用来保存线程主协程的上下文,如果这两个线程局部变量存储的都是子协程的上下文,那么不管怎么调用swapcontext
,都没法恢复主协程的上下文,也就意味着程序最终无法回到主协程去执行,程序也就跑飞了。
// 当前协程-->主协程
void Fiber::back()
{
//当前协程-->主协程
SetThis(t_threadFiber.get());
if (swapcontext(&m_ctx, &t_threadFiber->m_ctx)) {
DO_ASSERT2(false, "swapcontext");
}
}
Logger::ptr g_logger = LOG_ROOT();
void run_in_fiber2() {
LOG_INFO(g_logger) << "run_in_fiber2 begin";
LOG_INFO(g_logger) << "run_in_fiber2 end";
}
void run_in_fiber() {
LOG_INFO(g_logger) << "run_in_fiber begin";
/**
* 非对称协程,子协程不能创建并运行新的子协程,下面的操作是有问题的,
* 子协程再创建子协程,原来的主协程就跑飞了
*/
Fiber::ptr fiber(new Fiber(run_in_fiber2));
fiber->call();
LOG_INFO(g_logger) << "run_in_fiber end";
}
int main(int argc, char *argv[]) {
LOG_INFO(g_logger) << "main begin";
// 创建主协程,t_threadFiber被设置为当前协程,t_fiber也设置为当前协程
Fiber::GetThis();
// 创建子协程
Fiber::ptr fiber(new Fiber(run_in_fiber));
// 运行子协程,t_fiber设置为子协程
fiber->call();
LOG_INFO(g_logger) << "main end";
return 0;
}
究其原因,在于上面的run_in_fiber本身是一个子协程,在其内部执行另一个协程的call时,swapcontext
会把run_in_fiber的上下文保存到t_threadFiber中,导致t_threadFiber不再指向main函数的上下文,导致程序跑飞。