微服务开源框架TARS的RPC源码解析 之 初识TARS C++服务端

本文深入解析微服务开源框架TARS的C++服务端源码,涵盖服务端初始化、网络模块和业务模块的工作流程,以及RPC请求的处理。通过类图和代码分析,展示了TARS如何建立服务实体、监听客户端连接、接收和处理RPC请求,以及发送响应的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:Cony

导语:微服务开源框架TARS的RPC调用包含客户端与服务端,《微服务开源框架TARS的RPC源码解析》系列文章将从初识客户端、客户端的同步及异步调用、初识服务端、服务端的工作流程四部分,以C++语言为载体,深入浅出地带你了解TARS RPC调用的原理。

什么是TARS

TARS是腾讯使用十年的微服务开发框架,目前支持C++、Java、PHP、Node.js、Go语言。该开源项目为用户提供了涉及到开发、运维、以及测试的一整套微服务平台PaaS解决方案,帮助一个产品或者服务快速开发、部署、测试、上线。目前该框架应用在腾讯各大核心业务,基于该框架部署运行的服务节点规模达到数十万。

TARS的通信模型中包含客户端和服务端。客户端服务端之间主要是利用RPC进行通信。本系列文章分上下两篇,对RPC调用部分进行源码解析。本文是下篇,我们将以C++语言为载体,带大家了解一下TARS的服务端。

初识服务端

在使用TARS构建RPC服务端的时候,TARS会帮你生成一个XXXServer类,这个类是继承自Application类的,声明变量XXXServer g_app,以及调用函数:

g_app.main(argc, argv);
g_app.waitForShutdown();

便可以开启TARS的RPC服务了。在开始剖析TARS的服务端代码之前,先介绍几个重要的类,让大家有一个大致的认识。

Application

正如前面所言,一个服务端就是一个Application,Application帮助用户读取配置文件,根据配置文件初始化代理(假如这个服务端需要调用其他服务,那么就需要初始化代理了)与服务,新建以及启动网络线程与业务线程。

TC_EpollServer

TC_EpollServer才是真正的服务端,如果把Application比作风扇,那么TC_EpollServer就是那个马达。TC_EpollServer掌管两大模块——网络模块与业务模块,就是下面即将介绍的两个类。

NetThread

代表着网络模块,内含TC_Epoller作为IO复用,TC_Socket建立socket连接,ConnectionList记录众多对客户端的socket连接。任何与网络相关的数据收发都与NetThread有关。在配置文件中,利用/tars/application/server 下的netthread配置NetThread的个数

HandleGroup与Handle

代表着业务模块,Handle是执行PRC服务的一个线程,而众多Handle组成的HandleGroup就是同一个RPC服务的一组业务线程了。业务线程负责调用用户定义好的服务代码,并将处理结果放到发送缓存中等待网络模块发送,下文将会详细讲解业务线程如何调用用户定义的代码的,这里用到了简单的C++反射,这点在很多资料中都没有被提及。在配置文件中,利用/tars/application/server/xxxAdapter 下的threads配置一个HandleGroup中的Handle(业务线程)的个数。

BindAdapter

代表一个RPC服务实体,在配置文件中的/tars/application/server下面的xxxAdapter就是对BindAdapter的配置,一个BindAdapter代表一个服务实体,看其配置就知道BindAdapter的作用是什么了,其代表一个RPC服务对外的监听套接字,还声明了连接的最大数量,接收队列的大小,业务线程数,RPC服务名,所使用的协议等。

BindAdapter本身可以认为是一个服务的实例,能建立真实存在的监听socket并对外服务,与网络模块NetThread以及业务模块HandleGroup都有关联,例如,多个NetThread的第一个线程负责对BindAdapter的listen socket进行监听,有客户连接到BindAdapter的listen socket就随机在多个NetThread中选取一个,将连接放进被选中的NetThread的ConnectionList中。BindAdapter则通常会与一组HandleGroup进行关联,该HandleGroup里面的业务线程就执行BindAdapter对应的服务。可见,BindAdapter与网络模块以及业务模块都有所关联。

好了,介绍完这几个类之后,通过类图看看他们之间的关系:
图(2-1)服务端相关类图

服务端TC_EpollServer管理类图中左侧的网络模块与右侧的业务模块,前者负责建立与管理服务端的网络关系,后者负责执行服务端的业务代码,两者通过BindAdapter构成一个整体,对外进行RPC服务。

初始化

与客户端一样,服务端也需要进行初始化,来构建上面所说的整体,按照上面的介绍,可以将初始化分为两模块——网络模块的初始化与业务模块的初始化。初始化的所有代码在Application的void main()以及void waitForQuit()中,初始化包括屏蔽pipe信号,读取配置文件等,这些将忽略不讲,主要看看其如何通过epoll与建立listen socket来构建网络部分,以及如何设置业务线程组构建业务部分。

TC_EpollServer的初始化

在初始化网络模块与业务模块之前,TC_EpollServer需要先初始化,主要代码在:

void Application::main(int argc, char *argv[])
{
	......
        //初始化Server部分
        initializeServer();
	......
}

在initializeServer()中会填充ServerConfig里面的各个静态成员变量,留待需要的时候取用。可以看到有_epollServer = new TC_EpollServer(iNetThreadNum),服务端TC_EpollServer被创建出来,而且网络线程NetThread也被建立出来了:

TC_EpollServer::TC_EpollServer(unsigned int iNetThreadNum)
{
    if(_netThreadNum < 1)
    {
        _netThreadNum = 1;
    }
    //网络线程的配置数目不能15个
    if(_netThreadNum > 15)
    {
        _netThreadNum = 15;
    }
 
    for (size_t i = 0; i < _netThreadNum; ++i)
    {
        TC_EpollServer::NetThread* netThreads = new TC_EpollServer::NetThread(this);
        _netThreads.push_back(netThreads);
    }
}

此后,其实有一个AdminAdapter被建立,但其与一般的RPC服务BindAdapter不同,这里不展开介绍。

好了,TC_EpollServer被构建之后,如何给他安排左(网络模块)右(业务模块)护法呢?

网络模块初始化

在讲解网络模块之前,再认真地看看网络模块的相关类图:
图(2-2)网络模块类图

先看看Application中哪些代码与网络模块的初始化有关吧:

void Application::main(int argc, char *argv[])
{
	......
        vector<TC_EpollServer::BindAdapterPtr> adapters;
        //绑定对象和端口
        bindAdapter(adapters);
	......
        _epollServer->createEpoll();
}
 
void Application::waitForShutdown()
{
    waitForQuit();
    ......
}

网络部分的初始化,离不开建立各RPC服务的监听端口(socket,bind,listen),接收客户端的连接(accept),建立epoll等。那么何时何地调用这些函数呢?大致过程如下图所示:

图(2-3)网络模块的初始化

1. 创建服务实体的listen socket

首先在Application::main()中,调用:

vector<TC_EpollServer::BindAdapterPtr> adapters;
//绑定对象和端口
bindAdapter(adapters);

在Application::bindAdapter()建立一个个服务实体BindAdapter,通过读取配置文件中的/tars/application/server下面的xxxAdapter来确定服务实体BindAdapter的个数及不同服务实体的配置,然后再调用:

BindAdapterPtr bindAdapter = new BindAdapter(_epollServer.get());
_epollServer->bind(bindAdapter);

来确定服务实体的listen socket。可以看到,在TC_EpollServer::bind()中:

int  TC_EpollServer::bind(TC_EpollServer::BindAdapterPtr &lsPtr)
{
    int iRet = 0;
    for(size_t i = 0; i < _netThreads.size(); ++i)
    {
        if(i == 0)
        {
            iRet = _netThreads[i]->bind(lsPtr);
        }
        else
        {
            //当网络线程中listeners没有监听socket时,list使用adapter中设置的最大连接数作为初始化
            _netThreads[i]->setListSize(lsPtr->getMaxConns());
        }
    }
    return iRet;
}

将上文TC_EpollServer的初始化时创建的网络线程组中的第一条网络线程负责创建并监听服务实体的listen socket,那样就可以避免多线程监听同一个fd的惊群效应。

可以看到,接下来继续调用NetThread::bind(BindAdapterPtr &lsPtr),其负责做一些准备工作,实际创建socket的是在NetThread::bind(BindAdapterPtr &lsPtr)中执行的NetThread::bind(const TC_Endpoint &ep, TC_Socket &s):

void TC_EpollServer::NetThread::bind(const TC_Endpoint &ep, TC_Socket &s)
{
    int type = ep.isUnixLocal()?AF_LOCAL:AF_INET;
 
    if(ep.isTcp())
    {
        s.createSocket(SOCK_STREAM, type);
    }
    else
    {
        s.createSocket(SOCK_DGRAM, type);
    }
 
    if(ep.isUnixLocal())
    {
        s.bind(ep.getHost().c_str());
    }
    else
    {
        s.bind(ep.getHost(), ep.getPort());
    }
 
    if(ep.isTcp() && !ep.isUnixLocal())
    {
        s.listen(1024);
        s.setKeepAlive();
        s.setTcpNoDelay();
        //不要设置close wait否则http服务回包主动关闭连接会有问题
        s.setNoCloseWait();
    }
    s.setblock(false);
}

执行到这里,已经创建了服务实体BindAdapter的listen socket了,代码退回到NetThread::bind(BindAdapterPtr &lsPtr)后,还可以看到NetThread记录fd其所负责监听的BindAdapter:

_listeners[s.getfd()] = lsPtr;

下图是对创建服务实体的listen socket的流程总结

图(2-4)创建服务实体的listen socket

2.创建epoll

代码回到Application::main()中,通过执行:

_epollServer->createEpoll();

来让TC_EpollServer在其掌管的网络线程中建立epoll:

void TC_EpollServer::createEpoll()
{
    for(size_t i = 0; i < _netThreads.size(); ++i)
    {
        _netThreads[i]->createEpoll(i+1);
    }
    //必须先等所有网络线程调用createEpoll(),初始化list后,才能调用initUdp()
    for(size_t i = 0; i < _netThreads.size(); ++i)
    {
        _netThreads[i]->initUdp();
    }
}

代码来到NetThread::createEpoll(uint32_t iIndex),这个函数可以作为网络线程NetThread的初始化函数,在函数里面建立了网络线程的内存池,创建了epoll,还将上面创建的listen socket加入epoll中,当然只有第一条网络线程才有listen socket,此外还

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值