# 统一通信协议 Tup

## TARS 底层协议

在介绍 TUP 之前, 先介绍 Tars 服务之间通讯的底层协议格式, 具体参见 RequestF.tars & BaseF.tars

```
module tars
{
    //请求包体
    struct RequestPacket
    {
        1  require short        iVersion;
        2  require byte         cPacketType  = 0;
        3  require int          iMessageType = 0;
        4  require int          iRequestId;
        5  require string       sServantName = "";
        6  require string       sFuncName    = "";
        7  require vector<byte> sBuffer;
        8  require int          iTimeout     = 0;
        9  require map<string, string> context;
        10 require map<string, string> status;
    };

    //响应包体
    struct ResponsePacket
    {
        1 require short         iVersion;
        2 require byte          cPacketType  = 0;
        3 require int           iRequestId;
        4 require int           iMessageType = 0;
        5 require int           iRet         = 0;
        6 require vector<byte>  sBuffer;
        7 require map<string, string> status;
        8 optional string        sResultDesc;
        9 optional map<string, string> context;
    };
};
```

```
module tars
{
    ////////////////////////////////////////////////////////////////
    // 定义协议的版本号

    const short TARSVERSION  = 0x01;
    const short TUPVERSION  = 0x03;

    ////////////////////////////////////////////////////////////////
    // 定义消息的类型

    // 普通调用
    const byte TARSNORMAL  = 0x00;

    // 单向调用
    const byte TARSONEWAY  = 0x01;

    ////////////////////////////////////////////////////////////////
    // TARS定义的返回码

    const int TARSSERVERSUCCESS       = 0;       //服务器端处理成功
    const int TARSSERVERDECODEERR     = -1;      //服务器端解码异常
    const int TARSSERVERENCODEERR     = -2;      //服务器端编码异常
    const int TARSSERVERNOFUNCERR     = -3;      //服务器端没有该函数
    const int TARSSERVERNOSERVANTERR  = -4;      //服务器端没有该Servant对象
    const int TARSSERVERRESETGRID     = -5;      //服务器端灰度状态不一致
    const int TARSSERVERQUEUETIMEOUT  = -6;      //服务器队列超过限制
    const int TARSASYNCCALLTIMEOUT    = -7;      //异步调用超时
    const int TARSINVOKETIMEOUT       = -7;      //调用超时
    const int TARSPROXYCONNECTERR     = -8;      //proxy链接异常
    const int TARSSERVEROVERLOAD      = -9;      //服务器端超负载,超过队列长度
    const int TARSADAPTERNULL         = -10;     //客户端选路为空，服务不存在或者所有服务down掉了
    const int TARSINVOKEBYINVALIDESET = -11;     //客户端按set规则调用非法
    const int TARSCLIENTDECODEERR     = -12;     //客户端解码异常
    const int TARSSERVERUNKNOWNERR    = -99;     //服务器端位置异常

    /////////////////////////////////////////////////////////////////
    // 定义按位的消息状态类型,可复合

    const int TARSMESSAGETYPENULL     = 0x00;    //无状态
    const int TARSMESSAGETYPEHASH     = 0x01;    //HASH消息
    const int TARSMESSAGETYPEGRID     = 0x02;    //灰度消息
    const int TARSMESSAGETYPEDYED     = 0x04;    //染色消息
    const int TARSMESSAGETYPESAMPLE   = 0x08;    //stat采样消息
    const int TARSMESSAGETYPEASYNC    = 0x10;    //异步调用程序
    //const int TARSMESSAGETYPELOADED = 0x20;    //按负载值调用程序
    //const int TARSMESSAGETYPESETED = 0x40;     //按set规则调用类型，此字段后面将不使用
    const int TARSMESSAGETYPESETNAME = 0x80;     //按setname规则调用类型
    const int TARSMESSAGETYPETRACK   = 0x100;    //track调用链消息
    /////////////////////////////////////////////////////////////////
}
```

RequestPacket & ResponsePacket 是两个 Tars 服务通信的底层协议, 简单的说如果不通过 Tars 的 Communicator 来通信, 你可以自己组包来完成和 Tars 服务的通信(当然这会有相当的难度, 需要你对 Tars 底层协议非常熟悉), 因此为了方便 TUP 构建出来解决这种问题.

## TUP 概述

### TUP 是什么

TUP（Tars Uni-Protocol 的简称），Tars 统一协议，是基于 Tars 编码的命令字（Command）层协议的封装。

它的存在最早是为了方便各语言客户端调用 Tars 服务, 只提供了编解码, 网路通讯需要自己实现, 当然如果 Tars 提供了这个语言的客户端, 那么就不需要再使用 TUP 协议来调用 Tars 服务了.

在正式使用中, 我们有这种场景, 以供大家参考:

* 后端服务使用 Tars 服务实现
* 实现一个 HTTP+TUP 协议的全异步代理, 即入口是 HTTP+TUP, 出口是 TARS 协议的代理
* Android/Ios 通过 TUP 协议, 并通过这个代理服务, 完成和后台任何 Tars 服务的通信

**当然, 我们未来会提供一种机制, 可以采用 http+json 来完成后端 Tars 服务的通信**

### TUP 能做什么

1.支持 java、c++等语言

2.支持对象的序列化和反序列化

3.支持协议动态扩展

4.提供 put/get 泛型接口，快速实现客户端/服务端的编解码

5.支持直接调用 Tars 的服务端

### TUP 不能做什么

1.只做协议封装，不包含网络层

2.不支持数据压缩（可在业务层处理）

3.不支持加密协议（可在业务层处理）

### 依赖和约束

1.依赖 Tars 协议，TUP 用到的结构体对象必须通过 Tars 定义后生成

2.依赖各个语言的代码生成工具，如：tars2cpp/tars2java 等

3.TarsUniPacket 中封装的 Tars 相关的接口（如 getTars.../setTars...），只有在调用 Tars 服务是需要用到

4.使用过程中，使用 UniPacket 完成请求与相应的数据传递，其中 ServantName（服务对象名） 与 FuncName（接口名）为必须设定的参数，否则编码失败

5.不建议 get/put 太多数据，如果有比较多数据，建议在 tars 文件中组成一个 struct，然后 put/get 到 UniPacket 中，便于提高效率和减少网络包大小

6.UniPacket 编码后的结果在包头包含了 4 个字节网络字节序的包长信息，长度包括包头，接收方收到包后需根据包头的内容，判断包长，确保包完整后，传入解码接口进行解码（无需去掉包头）

7.Tars c++语言的 string 类型接口建议不要包含二进制数据，二进制数据用 vector 传输

## TUP 使用

### 类结构图

![](https://211854462-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LjKWVpRE8j8fOTPPSus%2Fuploads%2Fgit-blob-74310e6dfeb4fb838eb2709a47962f245467c808%2Ftars_tupclass.png?alt=media)

### 类的使用

1.RequestPacket：请求包及回应包的基类，通过 tars 文件定义生成，和 Tars 服务的基础包一致，一般不直接使用。

2.UniAttribute：属性类，用户可以通过对该类的对象进行操作，添加属性和获取属性，类提供了 put/get 泛型接口，并可实现编解码。编码序列化后的字节流可用于压缩、加密，网络传输或持久化存储，在需要的时候反序列化出原对象。

3.UniPacket：请求回应包类，继承于 UniAttribute，可以添加请求的属性值，设置需要请求远程对象及方法名，编码后发送到服务端，服务端解码后可获取属性参数进行处理。服务端处理完请求后同样通过该类的对象返回结果，客户端解码获取处理结果。

4.TarsUniPacket：Tars 请求回应包类，继承于 UniPacket，调用 Tars 远程服务的时候使用，用户添加属性及设置相关属性后，进行编码，组成请求包通过网络发到 Tars 服务进行处理。Tars 服务端收到 TUP 协议的请求，处理完后会以该类的对象组返回包返回给客户端。客户端收包后使用该类进行解码处理，获取结果。

### 使用 TUP 协议调用 Tars 服务

以下是 C++版本例子, 其他语言类似.

1.客户端调用时，使用 TarsUniPacket 对象进行请求包的参数设置及输入参数赋值，其中必须指定的请求参数信息包括：

```cpp
setRequestId();           设置消息id，递增

setServantName("");       设置远程对象名称

setFuncName("");          设置远程接口名称

setTarsPacketType();       包类型版本，TUP协议默认为3
```

针对特定远程接口的调用，只需通过 put 接口对输入参数进行赋值，属性名称为 tars 接口定义的参数名称，比如对于接口：

```
int testFunc(string inputString, int inputInt, out string outputString);
```

输入参数赋值的方式是：

```cpp
TarsUniPacket<> req;

req.put<string>("inputString", "testInput");

req.put<Int32>("inputInt", 12345);

req.encode(buff);
```

**这里 inputString 对应 testFunc 的第一个参数, 以此类推**

TarsUniPacket 对象必须为 tars 定义的每个输入参数设置属性值，否则服务端处理请求时会返回缺少某个属性值的异常错误，输出参数也可以作为输入，但是不是必选。

put 接口的模板类型选用 tars 参数定义的对应的类型，但枚举类型例外，需换用 Int32 作为模板类型赋属性值。

2、TUP 返回包同样使用 TarsUniPacket 对象进行解码，解码后使用 getTarsResultCode()接口获取 tars 服务的处理结果，0 为成功，非 0 为失败，失败的原因可以通过 getTarsResultDesc()接口获取错误描述。

返回成功的结果包的各输出参数使用 tars 定义的输出参数名称作为属性名称去获取，接口的返回值使用空字符串的属性名去获取。

如上述接口获取返回结果的方式是：

```cpp
TarsUniPacket<> rsp;

rsp.decode(recvBuff, recvLen);

if(rsp.getTarsResultCode() == 0)
{
    int ret = rsp.get<int32_t>("");						//获取返回值
    string retString = rsp.get<string>("outputString"); //获取输出参数
}
else
{
    cout <<　rsp.getTarsResultDesc() << endl;
}
```

## TUP 各版本接口介绍

### Linux c++

#### 类接口

UniAttribute 类

| 公共接口                                                               | 功能描述                 |
| ------------------------------------------------------------------ | -------------------- |
| template void put(const string& name, const T& t)                  | 添加属性值                |
| template void get(const string& name, T& t)                        | 获取属性值                |
| template T get(const string& name)                                 | 获取属性值                |
| template void getByDefault(const string& name, T& t, const T& def) | 获取属性值(忽略异常，def 为缺省值) |
| template T getByDefault(const string& name, const T& def)          | 获取属性值(忽略异常，def 为缺省值) |
| void clear()                                                       | 清除全部属性值              |
| void encode(string& buff)                                          | 将属性对象编码到字节流          |
| void encode(vector& buff)                                          | 将属性对象编码到字节流          |
| void encode(char\* buff, size\_t & len)                            | 将属性对象编码到字节流          |
| void decode(const char\* buff, size\_t len)                        | 将字节流解码               |
| void decode(const vector& buff)                                    | 将字节流解码               |
| const map\<string, vector >& getData() const                       | 获取已有的属性              |
| bool isEmpty()                                                     | 判断属性集合是否为空           |
| size\_t size()                                                     | 获取属性集合大小             |
| bool containsKey(const string & key)                               | 判断属性是否存在             |

UniPacket 类

| 公共接口                                          | 功能描述                                         |
| --------------------------------------------- | -------------------------------------------- |
| void setVersion(short iVer)                   | 设置协议版本号                                      |
| UniPacket createResponse()                    | 通过请求包生成回应包，生成过程会从请求包获取请求 ID、对象名称、方法名等回填到回应包中 |
| void encode(string& buff)                     | 将对象编码到字节流                                    |
| void encode(vector& buff)                     | 将对象编码到字节流                                    |
| void encode(char\* buff, size\_t & len)       | 将对象编码到字节流                                    |
| void decode(const char\* buff, size\_t len)   | 将字节流解码，其中 len 传入 buffer 长度，输出解码结果的长度         |
| tars::Short getVersion() const                | 获取协议版本号                                      |
| tars::Int32 getRequestId() const              | 获取消息 ID                                      |
| void setRequestId(tars::Int32 value)          | 设置请求 ID                                      |
| const std::string& getServantName() const     | 获取对象名称                                       |
| void setServantName(const std::string& value) | 设置对象名称(编码时对象名不能为空，否则编码失败)                    |
| const std::string& getFuncName() const        | 获取方法名                                        |
| void setFuncName(const std::string& value)    | 设置方法名(编码时方法名不能为空，否则编码失败)                     |

UniPacket 集成至 UniAttribute, 你可以构建自己的服务并使用 UniPacket 来完成服务通信, 这里 setServantName 可以作为协议主命令字, getFuncName 作为协议子命令字, UniPacket 底层包结构即 RequestPackage.

TarsUniPacket 类

| 公共接口                                                             | 功能描述                          |
| ---------------------------------------------------------------- | ----------------------------- |
| void setTarsVersion(tars::Short value)                           | 设置协议版本                        |
| void setTarsPacketType(tars::Char value)                         | 设置调用类型                        |
| void setTarsMessageType(tars::Int32 value)                       | 设置消息类型                        |
| void setTarsTimeout(tars::Int32 value)                           | 设置超时时间                        |
| void setTarsBuffer(const vectortars::Char& value)                | 设置参数编码内容                      |
| void setTarsContext(const map\<std::string, std::string>& value) | 设置上下文                         |
| void setTarsStatus(const map\<std::string, std::string>& value)  | 设置特殊消息的状态值                    |
| tars::Short getTarsVersion() const                               | 获取协议版本                        |
| tars::Char getTarsPacketType() const                             | 获取调用类型                        |
| tars::Int32 getTarsMessageType() const                           | 获取消息类型                        |
| tars::Int32 getTarsTimeout() const                               | 获取超时时间                        |
| const vectortars::Char& getTarsBuffer() const                    | 获取参数编码后内容                     |
| const map\<std::string, std::string>& getTarsContext() const     | 获取上下文                         |
| const map\<std::string, std::string>& getTarsStatus() const      | 获取特殊消息的状态值                    |
| tars::Int32 getTarsResultCode() const                            | 获取 Tars 服务处理结果码，0 为成功，非 0 为失败 |
| string getTarsResultDesc() const                                 | 获取 Tars 服务处理结果描述              |

TarsUniPacket 继承 UniPacket, 当你需要和 Tars 服务通信时, 你可以使用 TarsUniPacket 来完成服务通信, TarsUniPacket 底层包结构即 RequestPackage.

#### 使用注意

以上接口调用出错将抛出 runtime\_error 异常。

#### 使用例子

参见 cpp/test/testServant/testTup/下的示例程序

### Java

#### 类接口

UniAttribute: 与 c++相似

UniPacket: 与 c++相似

TarsUniPacket: 与 c++相似

#### 使用注意

1.目前 TUP 支持基本类型, TarsStruct，已经存放基本类型或 TarsStruct 的 map 和 list。对数组只支持 byte\[]，放入别的类型会抛 IllegalArgumentException 异常；

2.put 和 get 方法调用出错将抛出 ObjectCreateException 异常；
