我们通过官网的demo来进行分析,安装demo如下代码所示。
// 1.安装tars相关工具
go install github.com/TarsCloud/TarsGo/tars/tools/tarsgo@latest
go install github.com/TarsCloud/TarsGo/tars/tools/tars2go@latest
// 2.初始化生成代码(完成代码初始化)
tarsgo make App Server Servant GoModuleName
tarsgo make TestApp HelloGo SayHello github.com/Tars/test
基本文件结构如下所示:
CREATED HelloGo/SayHello.tars (171 bytes)// 协议文件
CREATED HelloGo/SayHello_imp.go (620 bytes)
CREATED HelloGo/client/client.go (444 bytes)
CREATED HelloGo/config.conf (967 bytes)
CREATED HelloGo/debugtool/dumpstack.go (412 bytes)
CREATED HelloGo/go.mod (37 bytes)
CREATED HelloGo/main.go (517 bytes)
CREATED HelloGo/makefile (193 bytes)
CREATED HelloGo/scripts/makefile.tars.gomod (4181 bytes)
CREATED HelloGo/start.sh (56 bytes)
协议文件名称:SayHello.tars,Tars才用自研的Tars协议和其他的Rpc框架有所不同,不过Tars也可以兼容proto协议。是从生成的.tars.go开始触发,obj.servant.TarsInvoke为入口开始执行,将参数进行组装传递到Invoke,在doInvoke中执行Rpc调用,通过adapterProxy发送Rpc调用,通过send发包给对应的RPC服务。对应的发送流程图如下。
func (c *AdapterProxy) Send(req *requestf.RequestPacket) error {
// 添加发送次数
c.sendAdd()
// 发送请求打包
sbuf, err := c.servantProxy.proto.RequestPack(req)
if err != nil {
TLOG.Debug("protocol wrong:", req.IRequestId)
return err
}
// 发送请求
return c.tarsClient.Send(sbuf)
}
其中Send的实现是一个消息队列。
在收到RPC请求的调用后,执行TCPHandler的invoke方法进行请求处理和解析,解析完再通过链接推回去。
rsp := h.ts.invoke(ctx, pkg) // 处理请求
connSt.conn.Write(rsp) // 结果回写
总的来说就是逐字节读取数据,通过特定算法来进行识别标签类型,并传递给对应的解析器进行解析。
func (b *Reader) readHead() (ty, tag byte, err error) {
data, err := b.buf.ReadByte()
if err != nil {
return
}
// 识别标签的算法
ty = data & 0x0f
tag = (data & 0xf0) >> 4
return
}
Tars对于数据的参数位于codec文件夹,其中包含了所有的数据类型的解析。我们以字符串解析为例。
1.获取字符串类型,通过Reader的SkipToNoCheck实现,看是string1还是string4。
2.根据类型去执行对应的解析器,分别是两种部分的情况,string1 为单行字符串,string4为多行字符串。
3.获取要获取的字符串的长度,(bReadU32,逐字节读取)。
4.通过b.Next提取对应的字符串,并返回。
func (b *Reader) ReadString(data *string, tag byte, require bool) error {
have, ty, err := b.SkipToNoCheck(tag, require)
if err != nil {
return err
}
if !have {
return nil
}
//此处省略了string4的判断,重点是突出处理方案
if ty == STRING1 {
var length uint8
err = bReadU8(b.buf, &length)
if err != nil {
return fmt.Errorf("read string1 tag:%d error:%v", tag, err)
}
buff := b.Next(int(length))
*data = string(buff)
} else {
return fmt.Errorf("need string, tag:%d, but type is %s", tag, getTypeStr(int(ty)))
}
return nil
}
执行完成ReadForm之后,我们就可以得到一个基本的请求结构信息了,比如调用的是哪个,方法是哪个,超时情况,都一一描述清楚。