tars协议序列化分析

2023-11-12

引言

tars序列化过程:TARS编码协议是一种数据编解码规则,它将整形、枚举值、字符串、序列、字典、自定义结构体等数据类型按照一定的规则编码到二进制数据流中。对端接收到二进制数据流之后,按照相应的规则反序列化可得到原始数值。简单理解,TARS编码协议提供了一种将数据序列化、反序列化的方法。其角色和我们认识的protobuf、json、xml等同。

流程分析

在这里插入图片描述

1.客户端原始请求数据---->序列化---->服务端
借助于tars文件生成的头文件,用代理调用相应的接口,该接口利用tars_invoke(invoke 调用)即可完成序列化传输。

class ConfigAdminProxy : public tars::ServantProxy
{
public:
  typedef map<string, string> TARS_CONTEXT;
  tars::Int32 AddConfig(const tars::AddConfigInfo & config,std::string &result,const map<string, string> &context = TARS_CONTEXT(),map<string, string> * pResponseContext = NULL)
  {
      tars::TarsOutputStream<tars::BufferWriter> _os;
      _os.write(config, 1);
      _os.write(result, 2);
      tars::ResponsePacket rep;
      std::map<string, string> _mStatus;
      tars_invoke(tars::TARSNORMAL,"AddConfig", _os.getByteBuffer(), context, _mStatus, rep);
      if(pResponseContext)
      {
          *pResponseContext = rep.context;
      }

      tars::TarsInputStream<tars::BufferReader> _is;
      _is.setBuffer(rep.sBuffer);
      tars::Int32 _ret;
      _is.read(_ret, 0, true);
      _is.read(result, 2, true);
      return _ret;
  }                                    //只显示了一个类成员函数

_objectProxy->getProxyProtocol().requestFunc(msg->request, msg->sReqData); 其中msg中包含成员 RequestPacket request(请求消息体)和ResponsePacket response(响应消息体)。
这里的resquestFunc来自ProxyProtocol::tarsResponse, 即如下所示:
_proxyProtocol.requestFunc = ProxyProtocol::tarsRequest;

void ProxyProtocol::tarsRequest(const RequestPacket& request, string& buff)
{
    TarsOutputStream<BufferWriter> os;

    request.writeTo(os);

    tars::Int32 iHeaderLen = htonl(sizeof(tars::Int32) + os.getLength());

    buff.clear();

    buff.reserve(sizeof(tars::Int32) + os.getLength());

    buff.append((const char*)&iHeaderLen, sizeof(tars::Int32));

    buff.append(os.getBuffer(), os.getLength());
}

_objectProxy->getProxyProtocol().requestFunc(msg->request, msg->sReqData);
std::unique_ptr_trans //客户端利用Transceiver类收发解析请求 。
_trans->sendRequest(msg->sReqData.c_str(),msg->sReqData.size())


2、 服务端---->反序列化---->原始请求数据

  • 借助于tars文件生成的头文件,用代理调用相应的dispatch接口即可完成反序列化。消息处理线程主循环遍历所有的BindAdapter的消息队列是否有消息。如果没有就wait在_handleGroup的条件变量上(插入消息后会被触发)。wait返回后上报心跳处理业务自有消息等,而后遍历所有BindAdapter的消息队列,取出收包消息。对于状态正常的消息(非超时、过载等)调用ServantHandle的handle方法处理消息,根据消息类型是否为tars分别调用不同的处理方法进行处理,并根据数据包的Servant名找到Servant实体,调用ondispatch方法(业务实现,传入current结构(内含数据buffer),以及用作返回的vector结构)处理消息。
  • onDispatch方法由tars2cpp工具自动生成的servant实现。它根据请求包中的函数名得知需要调用的函数,并将请求数据反序列化成对应的参数,然后调用相应的函数进行处理。也就是说,onDispatch负责对请求数据进行分发并处理。Servant实体处理完消息后回到ServantHandle处理线程,ServantHandle线程调用TC_EpollServer中的Send将待发消息buffer传输(ServantHandle线程不知道消息应该通过哪个网络线程返回),TC_EpollServer通过连接fd获得管理这个fd连接的网络线程,将消息buffer交给该网络线程处理。网络线程将消息buffer构造成tagSendData消息包放在自己的发送队列中。(详细的过程参考这两个函数的详解)。网络线程通过epoll_wait notify触发网络线程线程函数响应epoll,将消息队列中的包发送出去。
int onDispatch(tars::TarsCurrentPtr _current, vector<char> &_sResponseBuffer)
{
  static ::std::string __tars__ConfigAdmin_all[]=
  {
      "AddConfig",
      .......
  };

  pair<string*, string*> r = equal_range(__tars__ConfigAdmin_all, __tars__ConfigAdmin_all+14, _current->getFuncName());
  if(r.first == r.second) return tars::TARSSERVERNOFUNCERR;
  switch(r.first - __tars__ConfigAdmin_all)
  {
      case 0:
      {
          tars::TarsInputStream<tars::BufferReader> _is;
          _is.setBuffer(_current->getRequestBuffer());
          tars::AddConfigInfo config;
          std::string result;
          if (_current->getRequestVersion() == TUPVERSION)
          {
              UniAttribute<tars::BufferWriter, tars::BufferReader>  tarsAttr;
              tarsAttr.setVersion(_current->getRequestVersion());
              tarsAttr.decode(_current->getRequestBuffer());
              tarsAttr.get("config", config);
              tarsAttr.getByDefault("result", result, result);
          }
          else
          {
              _is.read(config, 1, true);
              _is.read(result, 2, false);
          }
          tars::Int32 _ret = AddConfig(config,result, _current);
          if(_current->isResponse())
          {
              if (_current->getRequestVersion() == TUPVERSION )
              {
                  UniAttribute<tars::BufferWriter, tars::BufferReader>  tarsAttr;
                  tarsAttr.setVersion(_current->getRequestVersion());
                  tarsAttr.put("", _ret);
                  tarsAttr.put("result", result);
                  tarsAttr.encode(_sResponseBuffer);
              }
              else
              {
                  tars::TarsOutputStream<tars::BufferWriter> _os;
                  _os.write(_ret, 0);
                  _os.write(result, 2);
                  _os.swap(_sResponseBuffer);
              }
          }
        return tars::TARSSERVERSUCCESS;
}

服务端利用onDispatch接口处理收到的二进制请求流,从_current中得到请求数据,然后把本地的调用结果放到vector & _sResponseBuffer中返回。


3、 服务端原始返回数据---->序列化----->客户端

RequestF.h 中定义了ResponsePacket结构体。
//位置:cpp/servant/libservant/TarsCurrent.cpp 221
void TarsCurrent::sendResponse(int iRet, const vector<char>& buffer, const map<string, string>& status, const string & sResultDesc)
{
//省略部分代码
………………
    TarsOutputStream<BufferWriter> os;
    if (_request.iVersion != TUPVERSION)
    {
        //将数据放到ResponsePacket结构中
        ResponsePacket response;
        response.iRequestId     = _request.iRequestId;
        response.iMessageType   = _request.iMessageType;
        response.cPacketType    = TARSNORMAL;
        response.iVersion       = TARSVERSION;
        response.status         = status;
        response.sBuffer        = buffer;
        response.sResultDesc    = sResultDesc;
        response.context        = _responseContext;
        response.iRet           = iRet;
        TLOGINFO("[TARS]TarsCurrent::sendResponse :"
                   << response.iMessageType << "|"
                   << _request.sServantName << "|"
                   << _request.sFuncName << "|"
                   << response.iRequestId << endl);
 //调用序列化方法,response中的数据都保存在了os中,调用了tars.h中的write接口
        response.writeTo(os);
    }
//省略部分代码
…………………………
    //获取内容长度
    tars::Int32 iHeaderLen = htonl(sizeof(tars::Int32) + os.getLength());
    string s = "";
    //返回的s的格式是内容长度+内容
    s.append((const char*)&iHeaderLen, sizeof(tars::Int32));
    s.append(os.getBuffer(), os.getLength());
    _servantHandle->sendResponse(_uid, s, _ip, _port, _fd);
}


4.客户端----->反序列化----->原始返回数据

//位置:cpp/servant/libservant/Transceiver.cpp 331
int TcpTransceiver::doResponse(list<ResponsePacket>& done)
{
…………
 if(!_recvBuffer.IsEmpty())
 {
     try
     {
         //接收到的服务端的序列化好的数据
         const char* data = _recvBuffer.ReadAddr();
         size_t len = _recvBuffer.ReadableSize();
         size_t pos = 0;
         //获取协议封装类
         ProxyProtocol& proto = _adapterProxy->getObjProxy()->getProxyProtocol();
         if (proto.responseExFunc) 
         {
             long id = _adapterProxy->getId();
              //将data反序列化到done中
             pos = proto.responseExFunc(data, len, done, (void*)id);
         }
…………
      }
}
这里的responseExFunc来自ProxyProtocol::tarsRespons(cpp/servant/AppProtocal.h 398)
    template<uint32_t iMaxLength>
    static size_t tarsResponseLen(const char* recvBuffer, size_t length, list<ResponsePacket>& done)
    {
    …………
                TarsInputStream<BufferReader> is;
                //将数据放入is中
  is.setBuffer(recvBuffer + pos + sizeof(tars::Int32), iHeaderLen - sizeof(tars::Int32));
                pos += iHeaderLen;
                //将is中的数据进行反序列化,填充到rsp中
                ResponsePacket rsp;
                rsp.readFrom(is);
    …………//在tars生成的代理接口函数中对rsp解析RPC调用的返回值。
    }

从上面代码中可以看出:
序列化数据使用的是:ResponsePacket.writeTo()
反序列化数据使用的是:ResponsePacket.readFrom()


协议分析:

把结构化数据序列化,用大白话解释就是想办法把不同类型的数据按照顺序放在一个字符串里。反序列化就是还能从这个字符串里把类型和数据正确解析出来。一般来说,要达成正确的效果,有三个因素是必须考虑的:

  • 标记数据的位置。例如是位于字符串头部还是字符串末尾,或者中间某个部分
  • 标记数据的类型,例如int char float vector等
  • 标记数据内容

Tars协议也跳不出这个基本规则,它的数据是由两部分组成:

| HEAD | BUF |

  • HEAD为头部信息(包含了数据位置和数据类型),BUF为实际数据。注意BUF里可以继续嵌套| HEAD | BUF |这样的类型,以满足复杂数据结构的需要
    像char、short、int之类的简单类型时,只需要:| HEAD | BUF |
    当数据类型为vector< char >时,就变为了| HEAD1 | HEAD2 | BUF |。这时候HEAD1 存储vector类型,HEAD2 存储char类型
    我们再具体看下HEAD中包括的内容:

| TAG1(4 bits) | TYPE(4 bits) | TAG2(1 byte或者8 bits)

  • TYPE表示类型,用4个二进制位表示,取值范围是0~15,用来标识数据类型。下面的Tars官方代码标明了具体数据类型的TYPE值
//位置:/cpp/servant/tup/Tars.h 60行
//数据头类型
#define TarsHeadeChar  0
#define TarsHeadeShort 1
#define TarsHeadeInt32 2
#define TarsHeadeInt64 3
#define TarsHeadeFloat 4
#define TarsHeadeDouble 5
#define TarsHeadeString1 6
#define TarsHeadeString4 7
#define TarsHeadeMap 8
#define TarsHeadeList 9
#define TarsHeadeStructBegin 10
#define TarsHeadeStructEnd 11
#define TarsHeadeZeroTag 12
#define TarsHeadeSimpleList 13
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

tars协议序列化分析 的相关文章

  • Latex公式排版(编号、换行、括号内换行、对齐)

    最近写论文刚上手了Latex 因为有模板 所以用起来还是很方便的 但是在实际使用中 由于论文是双栏的 因此比较长的公式在排版时会比较困难 下面对Latex中的公式排版方法做一些记录 公式的编写方法在此不再赘述 可以选择网页版的Latex公式
  • 机器学习项目

    文章来源 ATYUN AI平台 8800个开源机器学习项目 并从中选取了前30个制成这份清单 它涵盖了2017年1月和12月之间发布的最佳开源机器学习库 数据集和应用程序 Mybridge AI通过受欢迎程度 参与度和新近度来评估质量 为了
  • 安卓开发移植他人项目 配置问题

    在开发移植他人项目会出现各种配置问题 解决方法 1 将build gradle中的包版本改成跟自己本地项目相同的版本 2 在gradle properties中写入 android overridePathCheck true 3 在app
  • 离线包实现app内H5的秒开

    前言 市面上业务复杂 App中近半数业务页面使用H5 页面承载 H5的优势很明显 跨平台 迭代快 开发体验好 H5的劣势同样明显 加载慢 用户体验差 为了提高页面加载速度和成功率 我们在app H5 部分业务加载 采用了离线包方式 如果有业
  • 如何选取合适的运算放大器?

    首先呢 我不是大牛 本文也会有很多不足之处 欢迎大家提出意见 进入正题 在模拟输入部分 一个重要的大类是单端电压和电流的调理和转换 如 0 5V 10V 0 20mA 等 另一个重要的大类是传感器信号的调理和转换 最常用的如电桥 R TD
  • 2021哈工大深入理解计算机系统Lab5(linklab)

    2021哈工大计算机系统lab5 linklab 实验目的 实验环境与工具 硬件环境 软件环境 开发工具 实验内容 LinkBomb程序框架 phase1 全局变量 数据节 phase2 指令 代码节 phase3 符号解析 phase4

随机推荐

  • Python 各种数据保存与读取方法——numpy,dict,dataframe等等

    文章目录 前言 一 写入与读取 1 Dataframe转csv xlsx 2 numpy ndarray转npy 3 dict转txt 总结 前言 往往在做机器学习或者深度学习的时候 数据预处理部分需要大量的时间 如果每次debug都重新预
  • shiro SecurityManager简介说明

    转自 shiro SecurityManager简介说明 下文笔者讲述Shiro SecurityManager的相关简介说明 如下所示 SecurityManager是Shiro框架的核心 典型的Facade模式 Shiro通过Secur
  • leetcode刷题(3)

    各位朋友们大家好 今天是我leedcode刷题系列的第三篇 废话不多说 直接进入主题 文章目录 分割链表 题目要求 用例输入 提示 做题思路 c语言代码实现 Java代码实现 相交链表 题目要求 用例输入 提示 做题思路 c语言实现代码 J
  • ajax异步按顺序执行,Javascript异步执行不按顺序解决方案

    案例分析 比如执行懒加载时候 onscroll 事件触发多次事件时候会调用多次 ajax 回调事件 由于每个事件返回先后次序并不能保证和触发前一致 所以在数据响应返回后所添加的数据顺序就很在 push 到数组上顺序不一致 例子1 var r
  • maven 检查依赖冲突和版本冲突

    在项目发布的时候 一般都需要进行依赖冲突检查或者重复类的检查 这个时候我一般会使用下面的两个命令 mvn U clean package Dmaven test skip true enforcer enforce DcheckDeploy
  • python argparse变量到class变量的转换代码

    github上的项目总喜欢使用argparse bash来运行 这对于快速运行一个项目来说可能有好处 但在debug的时候是很难受的 因为我们需要在 sh文件中修改传入参数 并且不能使用jupyter 以下是把parser转换成显式clas
  • Docker容器 - 启动报错:No space left on device

    目录 运行 报错 解决问题 具体操作 重新运行 运行 docker run it 镜像名 bin bash 报错 docker Error response from daemon mkdir var lib docker overlay2
  • WSL2 忘记用户密码

    步骤一 将默认用户切换为root 在Windows里启动命令提示符 输入 ubuntu2004 config default user root 这就已经将我的ubuntu20 04的默认用户切换为了root 不同的WSL版本可能命令的第一
  • python程序作功率谱、倒频谱

    1 功率谱 from scipy fftpack import fft fftshift ifft from scipy fftpack import fftfreq import numpy as np import matplotlib
  • 服务器cpu占用过高一般是什么原因,如何解决服务器cpu使用率过高的問題

    方法 步骤 1 打开任务管理器 关闭一些占用cpu较高的进程 但是不要关闭System Idle Process这个进程 这个进程是系统管理的进程 另外有很多个svchost exe 这些进程也不要随便关闭 不然会引起关机或者系统重启 2
  • 磁盘分区管理

    普通分区 root zhaikaiyun fdisk dev sdb 给磁盘sdb创建分区 Welcome to fdisk util linux 2 23 2 Changes will remain in memory only unti
  • GB2312 汉字拼音对照表(6727字)

    http zh transwiki org wiki index php GB2312 E6 B1 89 E5 AD 97 E6 8B BC E9 9F B3 E5 AF B9 E7 85 A7 E8 A1 A8 啊 a 阿 a e 埃 a
  • Java.lang.Byte类之shortValue()方法的功能说明

    转自 Java lang Byte类之shortValue 方法的功能说明 下文笔者将讲述Java lang Byte类中shortValue 方法的功能简介说明 如下所示 shortValue 方法的功能 java lang Byte s
  • pycharm错误ERROR: Command errored out with exit status 1:

    报错 ERROR Command errored out with exit status 1 错误原因 python版本与pycharm要求的版本不一致 解决方法 1 在pycharm中找到File 打开Settings 2 在Setti
  • FMQL在linux下GPIO的映射关系与使用

    最近几年国产化芯片的使用如火如荼 部分国产手册说明杂且描述不清 尤其是一些映射关系使用文字描述非常不直观 博主在使用GPIO功能输出的时候反复看了几遍再去尝试控制耗费了不少时间 现结合相关文档总结GPIO映射表格 方便读者使用 1 GPIO
  • 性能测试——结果量含义(系统吞吐量(TPS)、用户并发量)

    性能测试 首先 开发软件的目的是为了让用户使用 我们先站在用户的角度分析一下 用户需要关注哪些性能 对于用户来说 当点击一个按钮 链接或发出一条指令开始 到系统把结果已用户感知的形式展现出来为止 这个过程所消耗的时间是用户对这个软件性能的直
  • Run-Time Check Failure #2 – Stack around the variable 'a' was corrupted.

    Run Time Check Failure 2 Stack around the variable a was corrupted 今天在用VS调代码的时候 会出现这个问题 经过查找以及DEBUG 最终发现是由于建立的数组下标溢出造成的
  • 在linux上开关swap

    swapon a 会开启 etc fstab上定义的所有swap swapoff a会关闭所有swap cat proc meminfo会显示swap的情况 另外下面的命令也会显示swap swapon s Filename Type Si
  • 什么是域名,怎么买服务器

    1 域名 域名 是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称 用于在数据传 输时标识计算机的电子方位 www baidu com com是顶级域名 baidu是一级域名 www是二级域名 每个域名可以对应一个i
  • tars协议序列化分析

    引言 tars序列化过程 TARS编码协议是一种数据编解码规则 它将整形 枚举值 字符串 序列 字典 自定义结构体等数据类型按照一定的规则编码到二进制数据流中 对端接收到二进制数据流之后 按照相应的规则反序列化可得到原始数值 简单理解 TA