【go语言之thrift协议三之client端分析】
go语言之thrift协议之client端分析
- runClient
- Open
- protocolFactory.GetProtocol
- handleClient
- NewTStandardClient
- NewCalculatorClient
- handleClient的具体实现
上一篇文章分析了thrift协议server端的实现,这边还是基于官方的示例去分析。
import ("crypto/tls""flag""fmt""os""github.com/apache/thrift/lib/go/thrift"
)func Usage() {fmt.Fprint(os.Stderr, "Usage of ", os.Args[0], ":\n")flag.PrintDefaults()fmt.Fprint(os.Stderr, "\n")
}func main() {flag.Usage = Usageserver := flag.Bool("server", false, "Run server")protocol := flag.String("P", "compact", "Specify the protocol (binary, compact, json, simplejson)")framed := flag.Bool("framed", false, "Use framed transport")buffered := flag.Bool("buffered", false, "Use buffered transport")addr := flag.String("addr", "localhost:9090", "Address to listen to")secure := flag.Bool("secure", false, "Use tls secure transport")flag.Parse()var protocolFactory thrift.TProtocolFactoryswitch *protocol {case "compact":protocolFactory = thrift.NewTCompactProtocolFactoryConf(nil)case "simplejson":protocolFactory = thrift.NewTSimpleJSONProtocolFactoryConf(nil)case "json":protocolFactory = thrift.NewTJSONProtocolFactory()case "binary", "":protocolFactory = thrift.NewTBinaryProtocolFactoryConf(nil)default:fmt.Fprint(os.Stderr, "Invalid protocol specified", protocol, "\n")Usage()os.Exit(1)}var transportFactory thrift.TTransportFactorycfg := &thrift.TConfiguration{TLSConfig: &tls.Config{InsecureSkipVerify: true,},}if *buffered {transportFactory = thrift.NewTBufferedTransportFactory(8192)} else {transportFactory = thrift.NewTTransportFactory()}if *framed {transportFactory = thrift.NewTFramedTransportFactoryConf(transportFactory, cfg)}if *server {if err := runServer(transportFactory, protocolFactory, *addr, *secure); err != nil {fmt.Println("error running server:", err)}} else {if err := runClient(transportFactory, protocolFactory, *addr, *secure, cfg); err != nil {fmt.Println("error running client:", err)}}
}
这里的代码因为是和server端是公用的,所以就不进行具体的分析,详情可以看上一篇。
runClient
还是先看一下具体的实现
func runClient(transportFactory thrift.TTransportFactory, protocolFactory thrift.TProtocolFactory, addr string, secure bool, cfg *thrift.TConfiguration) error {// 初始化transportvar transport thrift.TTransportif secure {transport = thrift.NewTSSLSocketConf(addr, cfg)} else {transport = thrift.NewTSocketConf(addr, cfg)}// 根据transportFactory获取transporttransport, err := transportFactory.GetTransport(transport)if err != nil {return err}defer transport.Close()// 根据地址获取连接if err := transport.Open(); err != nil {return err}// 获取inbound的protoiprot := protocolFactory.GetProtocol(transport)// 获取inbound的protooprot := protocolFactory.GetProtocol(transport)// 进行请求return handleClient(tutorial.NewCalculatorClient(thrift.NewTStandardClient(iprot, oprot)))
}
首先就是初始化client的transport,这里因为没有走https,所以这里走的是transport = thrift.NewTSocketConf(addr, cfg).然后和server一样,其实最终是初始化了TSocket这个结构体,如下
// NewTSocketFromAddrConf creates a TSocket from a net.Addr
func NewTSocketFromAddrConf(addr net.Addr, conf *TConfiguration) *TSocket {return &TSocket{addr: addr,cfg: conf,}
}
然后调用transportFactory.GetTransport。这里的GetTransport其实还是原封不动的返回了,所以这里就是TSocket的这个结构体,然后就是open方法去根据地址获取连接。
Open
// Connects the socket, creating a new socket object if necessary.
func (p *TSocket) Open() error {if p.conn.isValid() {return NewTTransportException(ALREADY_OPEN, "Socket already connected.")}if p.addr == nil {return NewTTransportException(NOT_OPEN, "Cannot open nil address.")}if len(p.addr.Network()) == 0 {return NewTTransportException(NOT_OPEN, "Cannot open bad network name.")}if len(p.addr.String()) == 0 {return NewTTransportException(NOT_OPEN, "Cannot open bad address.")}var err error// 根据地址获取连接if p.conn, err = createSocketConnFromReturn(net.DialTimeout(p.addr.Network(),p.addr.String(),p.cfg.GetConnectTimeout(),)); err != nil {return &tTransportException{typeId: NOT_OPEN,err: err,msg: err.Error(),}}p.addr = p.conn.RemoteAddr()return nil
}
这里的逻辑其实也是比较简单,通过官方的net.DialTimeout方法去获取一个链接。
protocolFactory.GetProtocol
这里的实现是和server端一样,根据compact的proto去返回一个TProtocol。
func (p *TCompactProtocolFactory) GetProtocol(trans TTransport) TProtocol {return NewTCompactProtocolConf(trans, p.cfg)
}
func NewTCompactProtocolConf(trans TTransport, conf *TConfiguration) *TCompactProtocol {PropagateTConfiguration(trans, conf)p := &TCompactProtocol{origTransport: trans,cfg: conf,}if et, ok := trans.(TRichTransport); ok {p.trans = et} else {p.trans = NewTRichTransport(trans)}return p
}
handleClient
注意这里首先是需要初始化client的方法也就是 tutorial.NewCalculatorClient(thrift.NewTStandardClient(iprot, oprot))。接下来进行逐步的解析
NewTStandardClient
// TStandardClient implements TClient, and uses the standard message format for Thrift.
// It is not safe for concurrent use.
func NewTStandardClient(inputProtocol, outputProtocol TProtocol) *TStandardClient {return &TStandardClient{iprot: inputProtocol,oprot: outputProtocol,}
}type TClient interface {Call(ctx context.Context, method string, args, result TStruct) (ResponseMeta, error)
}
首先需要注意的是这个TStandardClient结构体实现了TClient 这个interface,这个也是这个结构体核心的功能,在后面的很多地方也都有使用。然后看一下call这个方法
type TStruct interface {Write(ctx context.Context, p TProtocol) errorRead(ctx context.Context, p TProtocol) error
}
func (p *TStandardClient) Call(ctx context.Context, method string, args, result TStruct) (ResponseMeta, error) {p.seqId++seqId := p.seqIdif err := p.Send(ctx, p.oprot, seqId, method, args); err != nil {return ResponseMeta{}, err}// method is onewayif result == nil {return ResponseMeta{}, nil}err := p.Recv(ctx, p.iprot, seqId, method, result)var headers THeaderMapif hp, ok := p.iprot.(*THeaderProtocol); ok {headers = hp.transport.readHeaders}return ResponseMeta{Headers: headers,}, err
}
这里的seqId每次调用都会自增一次。
然后可以看出来这个方法的主要是由Send 和Recv这两个方法构成。分别看一下Send方法和Recv方法,
func (p *TStandardClient) Send(ctx context.Context, oprot TProtocol, seqId int32, method string, args TStruct) error {// Set headers from context object on THeaderProtocolif headerProt, ok := oprot.(*THeaderProtocol); ok {headerProt.ClearWriteHeaders()for _, key := range GetWriteHeaderList(ctx) {if value, ok := GetHeader(ctx, key); ok {headerProt.SetWriteHeader(key, value)}}}// 注意这里的oprot就是compact所生成的TProtocol。if err := oprot.WriteMessageBegin(ctx, method, CALL, seqId); err != nil {return err}// 因为这里的args是一个interface,所以后面根据具体的实现分析if err := args.Write(ctx, oprot); err != nil {return err}// 这里compact的WriteMessageEnd方法if err := oprot.WriteMessageEnd(ctx); err != nil {return err}// 调用compact的Flush方法return oprot.Flush(ctx)
}
这里没有多余的逻辑主要就是调用compact中的WriteMessageBegin,WriteMessageEnd方法,然后调用args的args方法。
然后看一下recv方法。
func (p *TStandardClient) Recv(ctx context.Context, iprot TProtocol, seqId int32, method string, result TStruct) error {// 读取compact的ReadMessageBegin方法 并且获取对应的参数rMethod, rTypeId, rSeqId, err := iprot.ReadMessageBegin(ctx)if err != nil {return err}// 判断是不是自己指定的方法if method != rMethod {return NewTApplicationException(WRONG_METHOD_NAME, fmt.Sprintf("%s: wrong method name", method))} else if seqId != rSeqId {return NewTApplicationException(BAD_SEQUENCE_ID, fmt.Sprintf("%s: out of order sequence response", method))} else if rTypeId == EXCEPTION {var exception tApplicationExceptionif err := exception.Read(ctx, iprot); err != nil {return err}if err := iprot.ReadMessageEnd(ctx); err != nil {return err}return &exception} else if rTypeId != REPLY {return NewTApplicationException(INVALID_MESSAGE_TYPE_EXCEPTION, fmt.Sprintf("%s: invalid message type", method))}// result读取结果if err := result.Read(ctx, iprot); err != nil {return err}// 调用compact的ReadMessageEnd方法return iprot.ReadMessageEnd(ctx)
}
看起来Recv方法其实和Send类似,读取的是compact的ReadMessageBegin和ReadMessageEnd方法,然后就是调用args的write和result.Read方法。
NewCalculatorClient
需要注意这个是通过thrift实现的方法,入参就是上面实现的TStandardClient结构体,然后实现了TClient这个interface也就是Call方法。
func NewCalculatorClient(c thrift.TClient) *CalculatorClient {return &CalculatorClient{SharedServiceClient: shared.NewSharedServiceClient(c),}
}
func NewSharedServiceClient(c thrift.TClient) *SharedServiceClient {return &SharedServiceClient{c: c,}
}
handleClient的具体实现
func handleClient(client *tutorial.CalculatorClient) (err error) {// 调用ping方法client.Ping(defaultCtx)fmt.Println("ping()")// 调用Add方法sum, _ := client.Add(defaultCtx, 1, 1)fmt.Print("1+1=", sum, "\n")// 调用Calculate方法work := tutorial.NewWork()work.Op = tutorial.Operation_DIVIDEwork.Num1 = 1work.Num2 = 0quotient, err := client.Calculate(defaultCtx, 1, work)if err != nil {switch v := err.(type) {case *tutorial.InvalidOperation:fmt.Println("Invalid operation:", v)default:fmt.Println("Error during operation:", err)}} else {fmt.Println("Whoa we can divide by 0 with new value:", quotient)}// 调用Calculate方法 work.Op = tutorial.Operation_SUBTRACTwork.Num1 = 15work.Num2 = 10diff, err := client.Calculate(defaultCtx, 1, work)if err != nil {switch v := err.(type) {case *tutorial.InvalidOperation:fmt.Println("Invalid operation:", v)default:fmt.Println("Error during operation:", err)}return err} else {fmt.Print("15-10=", diff, "\n")}// 调用GetStruct方法log, err := client.GetStruct(defaultCtx, 1)if err != nil {fmt.Println("Unable to get struct:", err)return err} else {fmt.Println("Check log:", log.Value)}return err
}
这个方法从使用上来看是好理解的,主要作用就是组装参数,调用client所实现的方法,接下来看一下这些方法在thrift中的实现。因为这里的实现都是大同小异的,所以这里看一下Add这个方法,代码如下
type CalculatorAddArgs struct {Num1 int32 `thrift:"num1,1" db:"num1" json:"num1"`Num2 int32 `thrift:"num2,2" db:"num2" json:"num2"`
}// Attributes:
// - Success
type CalculatorAddResult struct {Success *int32 `thrift:"success,0" db:"success" json:"success,omitempty"`
}// ResponseMeta represents the metadata attached to the response.
type ResponseMeta struct {// The headers in the response, if any.// If the underlying transport/protocol is not THeader, this will always be nil.Headers THeaderMap
}// Parameters:
// - Num1
// - Num2
func (p *CalculatorClient) Add(ctx context.Context, num1 int32, num2 int32) (_r int32, _err error) {var _args3 CalculatorAddArgs_args3.Num1 = num1_args3.Num2 = num2var _result5 CalculatorAddResultvar _meta4 thrift.ResponseMeta_meta4, _err = p.Client_().Call(ctx, "add", &_args3, &_result5)p.SetLastResponseMeta_(_meta4)if _err != nil {return}return _result5.GetSuccess(), nil
}
这里的CalculatorAddArgs,CalculatorAddResult和ResponseMeta都如上所示,然后也是调用的Call方法。之所以要传入add是因为在server那边根据方法名称去获取不同的的handler,所以method名称是add。然后就是获取结果,然后调用CalculatorAddResult 中的success的成员,当然这里就是int32。
到这里基本上就说完了,当然这还远远不够,因为接下来需要说一下thrift中不同的proto对于TProtocol的具体实现。
相关文章:
【go语言之thrift协议三之client端分析】
go语言之thrift协议之client端分析runClientOpenprotocolFactory.GetProtocolhandleClientNewTStandardClientNewCalculatorClienthandleClient的具体实现上一篇文章分析了thrift协议server端的实现,这边还是基于官方的示例去分析。 import ("crypto/tls"…...
Codeforces Round #855 (Div. 3) A-E
传送门 A. Is It a Cat? 题意 给你一个只有英文字母的字符串,问你这个字符串是否由连续的’m’, ‘e’, ‘o’,‘w’,(顺序不能改变)构成,并且不区分大小写。 如: “meow”, “mmmEeOWww”, “MeOooOw” 是符合要求…...

3/3操作系统作业
目录 1.前趋图和程序执行 (1)前驱图 (2)程序的顺序执行 (3)程序的并发执行 2.进程的描述 (1)进程的定义与特征 编辑编辑(2)进程控制块编辑 &…...

「C/C++」 标准文件操作大全
一、设备文件(运行程序时会默认打开这三个设备文件) stdin:标准输入,默认为当前终端(键盘),我们使用的scanf、getchar函数默认从此终端获得数据。stdout: 标准输出,默认…...
一款SAST工具需要支持多少种编译器呢?
除了Java语言,C#语言之外,C、C语言是编译器类型最多的编程语言,有几十种编译器,这些编译器方言为研发SAST工具带来了巨大的工作量,很多产品由于无法适配客户的编译器,导致无法检测。下面我们罗列一下国外和…...

jvm mat分析dump文件
jvm调优中,经常使用dump来分析是否存在大对象导致频繁full gc,以下为使用步骤: 一、获得服务进程 ps -ef | grep list-app | grep -v grep 二、生成dump文件 jmap -dump:formatb,filexxx.dump pid jmap -dump:filetest.hprof,formatb 3307…...

python16行代码获取原神全角色+全语音
前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 本来是不玩原神的,但是实在是经不住双重诱惑呀~ 毕竟谁能拒绝角色风景超级好看又可以爬树、炸鱼、壶里造房子、抓小动物、躲猫猫的游戏捏~ 今天点进官网~角色得配音让我沉陷其中,于是 我决定把他们爬…...

链接投票二维码制作制作投票链接视频选举投票制作
关于微信投票,我们现在用的最多的就是小程序投票,今天的网络投票,在这里会教大家如何用“活动星投票”小程序来进行投票。我们现在要以“信赖挚友”为主题进行一次投票活动,我们可以在在微信小程序搜索,“活动星投票”…...
HTTP 协议
HTTP(hypertext transport Protocol);超文本传输协议,是浏览器与万维网服务器之间通信的规则。 规定了客户端与服务端之间互相发送内容的格式,客户端发给服务端的叫 请求协议,服务端返回给客户端的为 响应…...

公司新招了个人,一副毛头小子的样儿,哪想到是新一代卷王····
内卷,是现在热度非常高的一个词汇,随着热度不断攀升,隐隐到了“万物皆可卷”的程度。 在程序员职场上,什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事,我们可以帮他。 是技术太强的人吗?也不是…...

TSDF学习记录
【唐宇迪】三维重建-TSDF通俗解读 人工智能入门教程 水泡动画模拟(Marching Cubes) - 算法小丑 - 博客园 (cnblogs.com) TSDF 流程分析 首先需要构建一大块空区域采用体素网格来存储该区域需要计算每个体素的TSDF值及其权重 原理简述 SDF值&#x…...

【Linux】孤儿进程
在Linux中,如果子进程运行时,父进程因为某些原因先行终止,就称该子进程为孤儿进程。 我们编写如下代码: 子进程一直在运行,父进程运行一段时间后自动终止。运行该程序观察现象: 最开始时,子进程…...

ChatGPT解答:python大批量读写ini文件时,性能很低,有什么解决方法吗,给出具体的思路和实例
ChatGPT解答: python大批量读写ini文件时,性能很低,有什么解决方法吗,给出具体的思路和实例 ChatGPTDemo Based on OpenAI API (gpt-3.5-turbo). python大批量读写ini文件时,性能很低,有什么解决方法吗&…...

MySql主键id不推荐使用UUID
前言 昨天在某个技术群中,有个老哥发送了一个技术视频:讲的是一个毕业生面试被问,前后端的交互ID是使用自增的吗?为什么不使用UUID?最后的解释是说性能问题,这个引起了我的兴趣,查了一下资料总…...

密码算法(SM1、SM2、SM3、SM4、同态加密、密态计算、隐私计算和安全多方计算)
文章目录SM1 对称密码SM2 椭圆曲线公钥密码算法SM3 杂凑算法SM4 对称算法同态加密密态计算和隐私计算安全多方计算技术安全多方计算的应用场景对称加密算法非对称加密算法(公钥加密)参考文章SM1、SM2、SM3和SM4 为了保障商用密码的安全性,国家…...
保险行业中【内容行政系统】模块功能的实现
以下是一个基本的保险行业中的内容行政系统功能模块,包括对保单、理赔等方面的处理: 创建保单表创建理赔表创建保单查询视图创建理赔查询视图创建新保单更新保单状态创建理赔更新理赔状态-- 创建保单表 CREATE TABLE policies ( policy_id NUMBER PRIM…...

C语言入门知识——(7)VS2022的C语言基础调试
1、什么是bug 这个故事很多人都知道 1947年9月9日:第一个“Bug”被发现的时候:“1949年9月9日,我们晚上调试机器的时候,开着的窗户没有纱窗,机器闪烁的亮光几乎吸引来了世界上所有的虫子。果然机器故障了,…...

数据库可视化开发工具内容介绍
在现代化办公环境中,数据管理的重要性不言而喻。对于企业来说,将企业内部的数据做好规划和管理,可以给企业提升办公协作效率,为企业高层做出正确的经营决策奠定基础。本文主要给大家介绍的是数据化可视化开发工具的内容࿰…...

坚如磐石:TiDB 基于时间点的恢复(PiTR)特性优化之路丨6.5 新特性解析
本文介绍了 TiDB 数据库的基于时间点的恢复(PiTR)特性,该特性允许用户将数据库恢复到特定时间点,从而避免丢失重要数据。文章首先介绍了 PiTR 技术的基本概念和工作原理,接着探讨了 TiDB 对 PiTR 的优化,包…...
【云原生】K8S中PV和PVC
前言 容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)
+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...

rm视觉学习1-自瞄部分
首先先感谢中南大学的开源,提供了很全面的思路,减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接:https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架: 代码框架结构:readme有…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
jieba实现和用RNN实现中文分词的区别
Jieba 分词和基于 RNN 的分词在技术路线、实现机制、性能特点上有显著差异,以下是核心对比: 1. 技术路线对比 维度Jieba 分词RNN 神经网络分词范式传统 NLP(规则 统计)深度学习(端到端学习)核心依赖词典…...

可视化图解算法48:有效括号序列
牛客网 面试笔试 TOP101 | LeetCode 20. 有效的括号 1. 题目 描述 给出一个仅包含字符(,),{,},[和],的字符串,判断给出的字符串是否是合法的括号序列 括号必须以正确的顺序关闭,"()"和"()[]{}"都是合法的括号序列&…...