golang thrift 源码分析,服务器和客户端究竟是如何工作的
首先编写thrift文件(rpcserver.thrift),运行thrift --gen go rpcserver.thrift,生成代码
namespace go rpc service RpcService {
string SayHi(: string name);
void SayHello(: string name);
}
搭建一个以二进制为传输协议的服务器如下:
type rpcService struct{
} func (this *rpcService)SayHi(name string)(r string, err error){
fmt.Println("Hi ", name)
r = "Hello "+name
err = nil
return
} func (this *rpcService)SayHello(name string)err error{
fmt.Println("Hello ", name)
err = nil
return
} func StartServer(){ serverTransport, err := thrift.NewTServerSocket("127.0.0.1:8808")
if err != nil {
fmt.Println("Error!", err)
return
}
handler := &rpcService{}
processor := NewRpcServiceProcessor(handler)
server := thrift.NewTSimpleServer2(processor, serverTransport)
fmt.Println("thrift server in localhost") server.Serve()
}
查看自动生成的代码recserver.go,我们发现 NewRpcServiceProcessor函数代码如下:
func NewRpcServiceProcessor(handler RpcService) *RpcServiceProcessor { self4 := &RpcServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)}
self4.processorMap["SayHi"] = &rpcServiceProcessorSayHi{handler: handler}
self4.processorMap["SayHello"] = &rpcServiceProcessorSayHello{handler: handler}
return self4
}
也就是说,thrift通过key-value保存了我们实际将要运行的函数,最终通过handler来执行。
这里就有点像我们使用golang系统中的http包中的ListenAndServer()函数时,提前通过Handfunc来设置好函数路由是一个意思。
再看看Serve()函数是如何实现的:
func (p *TSimpleServer) Serve() error {
err := p.Listen()
if err != nil {
return err
}
p.AcceptLoop()
return nil
} func (p *TSimpleServer) AcceptLoop() error {
for {
client, err := p.serverTransport.Accept()
if err != nil{
//.......被我删掉
}
if client != nil {
go func() {
if err := p.processRequests(client); err != nil {
log.Println("error processing request:", err)
}
}()
}
}
}
Serve()函数负责监听连接到服务器上的client,并且通过processRequests()函数来处理client。实际处理过程中,服务器会获取client的processor,然后进一步处理client的请求。这部分先暂停一下,我们来分析一下client端的工作原理,之后再回过头来看看会比较清晰一些
首先我们架设client端如下,并且通过client端来发送一个SayHi的操作:
transport, err := thrift.NewTSocket(net.JoinHostPort("127.0.0.1", "8808"))
if err != nil {
//...
} protocolFactory := thrift.NewTBinaryProtocolFactoryDefault() client := NewRpcServiceClientFactory(transport, protocolFactory)
if err := transport.Open(); err != nil {
//...
}
defer transport.Close()
res, _ := client.SayHi("wordl")
现在问题来了,这个SayHi是如何通知给服务器的呢?不急,看源码
在我们调用thrift --gen go XXX命令的时候,thrift已经给我们生成了SayHi过程的代码,如下:
func (p *RpcServiceClient) SayHi(name string) (r string, err error) {
if err = p.sendSayHi(name); err != nil {
return
}
return p.recvSayHi()
}
其中RpcServiceClient类型就是我们的client,可以看到先调用了一个sendSayHi,如果没有错误的话,又调用了一个recvSayHi。
其实sendSayHi就是我们通知服务器执行SayHi()函数的关键,而recvSayHi是接受服务器的执行结果的。
一起看下sendSayHi是如何实现的(代码被我精简,这保留了关键部分,完整代码可以自己通过thrift命令生成查看)
func (p *RpcServiceClient) sendSayHi(name string) (err error) {
oprot := p.OutputProtocol //获取传输协议 if err = oprot.WriteMessageBegin("SayHi", thrift.CALL, p.SeqId); err != nil { //发送SayHi字符创,告诉服务器将来执行的函数
return
}
args := RpcServiceSayHiArgs{ //构建参数
Name: name,
}
if err = args.Write(oprot); err != nil { //将参数发送给服务器
return
}
if err = oprot.WriteMessageEnd(); err != nil { //通知服务器发送完毕
return
}
return oprot.Flush()
}
通过这样的一系列数据传输,服务器通过路由解析,便可以正确的知道该执行哪个函数了。thrift的精髓也正在此,实现了rpc架构,客户端只需要简单的调用client.SayHi(),不必知道这是本地调用还是远程调用。
好了,既然请求发出了,我们现在当然看看服务器是如何响应的,在源码中,有一个函数是专门响应客户端请求的:
func (p *RpcServiceProcessor) Process(iprot, oprot thrift.TProtocol) (success bool, err thrift.TException)
前面讲解服务器端是如何创建的时候讲到过一个processRequests()函数,它在client连接上server的时候会被server调用。我们看看源码:
func (p *TSimpleServer) processRequests(client TTransport) error {
processor := p.processorFactory.GetProcessor(client)
//....
for {
ok, err := processor.Process(inputProtocol, outputProtocol)
if err{
//....
}
}
return nil
}
在去除无关代码之后我们看到,服务器首先获取客户端的processor,然后调用processor的Process函数,从而执行响应客户端的请求。
看看Process函数具体是如何实现的:
func (p *RpcServiceProcessor) Process(iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
name, _, seqId, err := iprot.ReadMessageBegin() //获取客户端请求执行的函数名称
if err != nil {
return false, err
}
if processor, ok := p.GetProcessorFunction(name); ok {
return processor.Process(seqId, iprot, oprot) //执行
}
//...
}
要注意的是,函数中用红色标注的Process是另外一个函数,这里可不是递归。两个Process函数的声明是不一样的:
func (p *RpcServiceProcessor) Process(iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) //RpcServiceProcessor是server的processor func (p *rpcServiceProcessorSayHi) Process(seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) //rpcServiceProcessorSayHi是具体的handler,
现在到了最关键的时候了,我们看看handler是如何执行process的:
func (p *rpcServiceProcessorSayHi) Process(seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) {
args := RpcServiceSayHiArgs{} //构建参数
if err = args.Read(iprot); err != nil { //读取客户端发来的参数
//处理err
} iprot.ReadMessageEnd() //读取客户端的结束消息
result := RpcServiceSayHiResult{}
var retval string
var err2 error
if retval, err2 = p.handler.SayHi(args.Name); err2 != nil { //执行函数
//..处理err
} else {
result.Success = &retval
}
//...将result发送给客户端,流程和client发送请求类似,client通过recvSayHi()函数接受result
return true, err
}
现在,服务器和客户端究竟是如何工作的。你明白了吗
转载请注明出处,谢谢
golang thrift 源码分析,服务器和客户端究竟是如何工作的的更多相关文章
- motan源码分析六:客户端与服务器的通信层分析
本章将分析motan的序列化和底层通信相关部分的代码. 1.在上一章中,有一个getrefers的操作,来获取所有服务器的引用,每个服务器的引用都是由DefaultRpcReferer来创建的 pub ...
- thrift源码分析
1 前言 学习thrift源码主要为了弄清楚几个问题 thrift客户端和服务端的通信流程是如何的 thrift的IDL中给属性加上编号的作用是什么 thrift中require.optional和默 ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(3) 前后台打通
4.2.1.2.4 PipelineDraweeControllerBuilder.obtainController()源码分析 续 上节中我们提到两个核心的步骤 obtainDataSourceSu ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题
4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...
- Fresco 源码分析(二) Fresco客户端与服务端交互(2) Fresco.initializeDrawee()分析 续
4.2.1.2 Fresco.initializeDrawee()的过程 续 继续上篇博客的分析Fresco.initializeDrawee() sDraweeControllerBuilderSu ...
- Thrift源码分析(一)-- 基本概念
我所在的公司使用Thrift作为基础通信组件,相当一部分的RPC服务基于Thrift框架.公司的日UV在千万级别,Thrift很好地支持了高并发访问,并且Thrift相对简单地编程模型也提高了服务地开 ...
- zookeeper源码分析之二客户端启动
ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API: create(path, data, flags): 创建一个ZNode, path是其 ...
- Thrift源码分析(二)-- 协议和编解码
协议和编解码是一个网络应用程序的核心问题之一,客户端和服务器通过约定的协议来传输消息(数据),通过特定的格式来编解码字节流,并转化成业务消息,提供给上层框架调用. Thrift的协议比较简单,它把协议 ...
- motan源码分析四:客户端调用服务
在第一章中,我们分析了服务的发布与注册,本章中将简单的分析一下客户端调用服务的代码及流程,本文将以spring加载的方式进行分析. 1.在DemoRpcClient类的main()方法中加载类: Ap ...
随机推荐
- Java虚拟机详解04----GC算法和种类【重要】
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- android studio 导入工程问题总结
github上下了几个开源项目,在导入android studio时出现各种问题, 在网上查询各种资料后一一得以解决,现对个问题点进行简单的总结: 1. gradle project sync fai ...
- js 日期2015/12/22/16/45替换2015-12-22 16:45格式
js 日期2015/12/22/16/45替换2015-12-22 16:45格式 利用正则分组: function toChange(date) { var reg = /(\d+)\/(\d+)\ ...
- p点到(a,b)点两所在直线的垂点坐标及p点是否在(a,b)两点所在直线上
/// <summary> /// p点到(a,b)点两所在直线的垂点坐标 /// </summary> /// <p ...
- PHP mcrypt加密扩展使用总结
在开发中,很多时候我们在前后端交互中需要对一些敏感数据进行一定的加密.PHP中有提供了mcrypt的这样一个加密扩展实现对数据的加密解密. 一.mcrypt扩展的安装 在低版本的PHP中需要在配置文件 ...
- jquery 现实多状态控件 (status & power(2,0)) = power(2,0)
数据库表设计的时候,会有很些多状态的需求,比如招聘职位需要同时发布到武汉,广州,上海 实现方法有很多种,我选择了在职位表中建一个 int 型字段保存多种状态,这个涉及到一些算法,我要查询武汉和广州的职 ...
- (转载)关于Apache 的两种工作模式
今天在查看服务器的时候,发现服务器http请求数 每天增长越来越多,在优化集群服务器的时候,查看到Apache 的工作模式是prefork,于是想到了worker 模式, 想暂时的把当前运行模式改成w ...
- 【.NET】传智播客第【19】期就业班视频(高清无加密)
[.NET]传智播客第[19]期就业班视频(高清无加密) 下载地址:http://fu83.cn/thread-85-1-1.html
- Linux及安全——程序破解
Linux及安全——程序破解 由于我的Ubuntu的vi有故障,所以用kaili做. 运行原程序 1.反汇编代码,查看 objdump -d login 2.修改代码 vi login 转换为16进制 ...
- Linux第七次实验笔记
#期中总结 习题总结与分析 填空:Linux Bash中,Ctrl+a快捷键的作用是(将光标移至输入行头,相当于Home键). [ctrl]+u 从游标处向前删除指令串 [ctrl]+k 从游标处向后 ...