【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 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。…...
终极指南:3步解决Mac NTFS读写难题,Nigate免费工具完整教程
终极指南:3步解决Mac NTFS读写难题,Nigate免费工具完整教程 【免费下载链接】Free-NTFS-for-Mac Nigate: An open-source NTFS utility for Mac. It supports all Mac models (Intel and Apple Silicon), providing full read-write access, mounting, a…...
从选型到调试:MCP2517FD与ATA6563收发器搭配实战避坑指南
从选型到调试:MCP2517FD与ATA6563收发器搭配实战避坑指南 在工业控制和车载电子系统中,CAN FD总线技术正逐步取代传统CAN总线,成为高速数据传输的新标准。作为硬件工程师,我们常常面临这样的挑战:如何在有限的项目周期…...
Windows Cleaner:专业级Windows系统优化终极指南
Windows Cleaner:专业级Windows系统优化终极指南 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服! 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner Windows Cleaner是一款专为Windows系统设计的开源系统…...
3分钟快速解密QMC加密音乐:QMCDecoder完整使用指南
3分钟快速解密QMC加密音乐:QMCDecoder完整使用指南 【免费下载链接】qmc-decoder Fastest & best convert qmc 2 mp3 | flac tools 项目地址: https://gitcode.com/gh_mirrors/qm/qmc-decoder 你是否遇到过QQ音乐下载的歌曲只能在特定播放器里播放&#…...
跟着 MDN 学 HTML day_35:(深入解析 CharacterData 抽象接口)
在 DOM 的庞大体系中,并非所有节点都以可见的标签形式存在。当我们操作一段文本、一条注释甚至一条处理指令时,背后都有一个共同的基类在默默提供支持。这个基类就是 CharacterData。它是一个抽象接口,意味着你不会在代码中直接创建 Characte…...
大语言模型微调的内存优化:零阶方法与曲率引导技术
1. 大语言模型微调的内存困境与零阶优化机遇在自然语言处理领域,大语言模型(LLM)的微调通常采用基于反向传播的一阶优化方法。这种传统方法虽然效果显著,但随着模型规模指数级增长(如GPT-3的1750亿参数)&am…...
物联网LoRa系列-2:从感知到应用,详解LoRa在分层架构中的关键角色
1. LoRa技术入门:从无线通信到物联网连接 第一次接触LoRa是在五年前的一个智慧农业项目上。当时客户需要在2000亩的茶园部署环境监测系统,传统WiFi和4G网络要么覆盖不足,要么功耗太高。当我看到LoRa终端设备在单节电池供电下能工作3年时&…...
模型版本爆炸、依赖漂移、推理熵增——SITS 2026提出的“动态契约管理”如何让AI系统稳定性提升4.8倍?
更多请点击: https://intelliparadigm.com 第一章:AI原生模型管理:SITS 2026 MLOps完整解决方案 SITS 2026 是面向AI原生工作负载设计的下一代MLOps平台,深度集成模型生命周期治理、可观测性引擎与边缘协同推理能力。其核心突破在…...
企业内如何利用 Taotoken 构建统一的 AI 能力中台
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 企业内如何利用 Taotoken 构建统一的 AI 能力中台 在技术驱动的业务环境中,中型及大型企业内部的多个团队或产品线往往…...
为LLM注入联网能力:SuGPT-kexue项目的架构设计与工程实践
1. 项目概述与核心价值最近在开源社区里,一个名为“SuGPT-kexue”的项目引起了不少开发者和AI爱好者的注意。这个项目名本身就挺有意思,它指向了一个非常具体且实用的场景:如何让一个大型语言模型(LLM)具备科学上网的能…...
