tarsCpp写代码比较符合中国人的习惯,代码很清晰简洁易懂,并且中文注释规范完善。
智能指针
TC_HandleBase:智能指针基类,需要继承才能使用。使用protcted保护构造函数和析构函数,不让直接实例化。析构函数使用虚函数,常规使用技巧。
TC_AutoPtr:智能指针模板类。
无锁队列,其实还是用了自旋锁,有尝试次数,次数到了还是会睡眠
协程上下文切换声明在tc_fcontext中的make_fcontext和jump_fcontext函数,汇编实现。
协程切换原理是:保存上下文相关的寄存器到参数指定的地址,然后将传入的参数的值传给相应的寄存器,然后jum跳转到准备好栈的新地址。(eax、ebx、ecx、edx、esi、edi通用寄存器,esp栈指针寄存器,ebp基地址指针寄存器,eip下一条指令指针寄存器)。
x86寄存器:
8个通用寄存器:EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
1个标志寄存器:EFLAGS
6个段寄存器:CS、DS、ES、FS、GS、SS
5个控制寄存器:CR0、CR1、CR2、CR3、CR4
8个调试寄存器:DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7
4个系统地址寄存器:GDTR、IDTR、LDTR、TR
其他寄存器:EIP、TSC等。
TC_CoroutineQueue:协程队列,类似线程池。
TC_CoroutineInfo:协程信息类。调度对象和协程对象可以快速查询协程信息。要执行的协程会通过CoroutineDel移除协程,然后设置可执行状态加入到队列尾部。
TC_CoroutineScheduler:协程调度类,每个线程独立拥有,很多地方可以避免加锁,因为这些对象只有一个线程拥有。g_scheduler放在thread_local中,静态的表示初始化的时候就分配好内存。构造函数会创建一个TC_Epoller的对象_epoller,用来处理调度。调度对象由每个协程运行函数run创建。
TC_CoroutineScheduler::run()调度器核心执行函数,wakeup唤醒需要激活的协程,wakeupbytimeout唤醒sleep协程,wakeupbyself唤醒yield协程。如果没有协程可以运行了就退出调度循环。
TC_Coroutine:协程类,继承自TC_Thread线程基类,每个线程内部都有一个指向协程调度器的智能指针_coroSched。run()->handleCoro()真正运行协程初始化回调和加入调度执行。由于协程数量一般比较多,需要暂定协程需要执行_coroSched的terminate,最终调用epoll_ctl系统调用,开销比较大,可以考虑io_uring。
协程总结:协程当线程用,自动调度(当_activeCoroQueue,_avail,_active队列没有协程时,等待epoll事件),调度器发现没有协程可运行自动退出。
system_info:使用静态的局部变量保存信息,static std::once_flag静态局部初始化变量,std::call_once线程安全。实际上放在静态局部变量的构造里也是可以的一样线程安全,但是这就需要定义一个类,比较麻烦。
stack_traits:将静态方法放在一个结构体内,当作属性萃取,值得借鉴,如果是其他静态全局方法建议放在namespace中,不要放在结构体内,这样链接耦合性小点。
stack_traits::allocate分配虚拟内存,linux使用mmap映射,并使用mprotect设置标志位为PROT_NONE,并且大小是一个页表示最后一个页内存不可访问,然后将sp栈顶指针变量值设置为这块内存的底部。munmap解除映射。
TC_Exception异常基类,继承自exception。
getBacktrace获取异常栈:backtrace获取当前栈信息,backtrace_symbols解析符号信息。
TC_DYN_RuntimeClass动态生成类:
createObject类似对象工厂,可指定创建对象的函数指针,对象名,对象大小生成对象。
load函数可根据类名获取对象,类似反射的功能。
isKindOf判断类型是否一致,不是的话递归判断子类,这个功能类似模板traits萃取功能。
TC_Epoller:继承自TC_TimerBase定时器类,内部类EpollInfo(重要属性_cookie和回调函数)和NotifyInfo相关信息类(包含TC_Socket)通过内部指向TC_Epoller的指针操纵对象属性。TC_Epoller又拥有指向NotifyInfo的指针_notify,还可以通过_notify操控EpollInfo。
创建TC_Epoller通过create会创建NotifyInfo,NotifyInfo->init->createEpollInfo创建EpollInfo对象。也就是说每一个连接拥有一个NotifyInfo和EpollInfo。
notify:这里使用了一个技巧,默认et边缘模式,通过epoll_ctl一个fd为EPOLLOUT,会马上唤醒epoll。
syncCallback:同步回调,一般是其他线程调用,epoll监听的线程处理回调。创建一个udp套接字,将套接字包装到NotifyInfo中然后将NotifyInfo指向这个TC_Epoller,接着包装一个执行同步函数和用来通知事件的回调匿名函数,将匿名函数注册到epoll中,然后条件变量等待。此时这个线程就在等待事件的发生。(使用udp作为通知事件的fd,也可以使用管道,管道创建会繁琐一些,但性能不会更差)
服务模型,四种模式:
TC_EpollServer:继承自TC_HandleBase智能指针基类和LogInterface日志接口。包含内部类RecvContext接收包上下文类和SendContext发送包上下文类。
细节:RecvContext继承了std::enable_shared_from_this,拥有了将自己包装成智能指针的成员函数。
initHandle:初始化handle线程。
createEpoll:创建资源。
startHandle:启动handle线程。
waitForReady:等待线程都初始化好。
waitForShutdown:先执行new TC_Epoller(),创建TC_Epoller对象,组合顺序执行上面四个操作。accept阻塞,有连接过来就会创建EpollInfo,并注册这个连接fd的回调函数。然后执行loop循环直到状态变成停止。_bindAdapters需要bind监听操作。manualListen手动监听。只能一个线程epoll_wait,将事件分发给其他线程。
TC_EpollServer维护整个服务对象,核心成员包括:
std::vector<NetThread *> _netThreads,
TC_Epoller *_epoller = NULL;
vector _bindAdapters;。
unordered_map<int, BindAdapterPtr> _listeners;
NetThread维护网络线程对象,核心成员包括:
TC_Epoller* _epoller = NULL;
shared_ptr _list;
send_queue _sbuffer;
std::function<void()> _handle;
BindAdapter维护服务端口的信息,核心成员包括:
vector _handles;
TC_Socket _s;
shared_ptr _dataBuffer;
TC_Fifo创建的管道是命名管道fifo,支持没有亲缘关系进程间通信。
文件锁:lock->fcntl(,flock)
malloc内存池实现,一般情况下不需要使用,直接使用tcmalloc库就行了,不过如果需要优化可以作为实现模板。
生成c++代码,tars的idl语言和c++语法非常接近,所以转换代码也比较简单。
tup rpc框架的统一的通信协议(tars/tup协议)和协议的编解码实现方式
protocol rpc框架定义的底层通信的返回码和与框架基础服务进行交互的通信接口文件
servant/libservant rpc框架的源码实现
jmem 基于tars协议的内存数据结构组件的源码实现
promise 基于promise异步编程的源码实现
makefile 使用TARS框架C++语言的编译源代码的makefile实现
script 生成TARS服务模版代码的脚本工具