golang thrift 源码分析,服务器和客户端究竟是如何工作的



首先编写thrift文件(rpcserver.thrift),运行thrift --gen go rpcserver.thrift,生成代码

namespace go rpc

service RpcService {
    string SayHi(1: 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( nil
    return
}

func StartServer(){

    serverTransport,err := thrift.NewTServerSocket(127.0.0.1:8808")
    if err != nil {
        fmt.Println(Error!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: 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 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(",thrift.CALL,p.SeqId); err != nil { 发送SayHi字符创,告诉服务器将来执行的函数
        return
    }
    args := RpcServiceSayHiArgs{ 构建参数
        Name: name,}
    if err = args.Write(oprot); err != nil {  将参数发送给服务器
        if err = oprot.WriteMessageEnd(); err != nil { 通知服务器发送完毕
        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
}

现在到了最关键的时候了,我们看看handler是如何执行process的:

func (p *rpcServiceProcessorSayHi) Process(seqId int32,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
    true,err
}

相关文章

程序目录结构 简单实现,用户登录后返回一个jwt的token,下次请求带上token请求用户信息接口并返回信息...
本篇博客的主要内容是用go写一个简单的Proof-of-Work共识机制,不涉及到网络通信环节,只是一个本地的简...
简介 默克尔树(MerkleTree)是一种典型的二叉树结构,其主要特点为: 最下面的叶节点包含存储数据或其...
接下来学习并发编程, 并发编程是go语言最有特色的地方, go对并发编程是原生支持. goroutine是go中最近本...
先普及一下, 什么是广度优先搜索 广度优先搜索类似于树的层次遍历。从图中的某一顶点出发,遍历每一个顶...
第一天: 接口的定义和实现 第二天: 一. go语言是面向接口编程. 在学习继承的时候说过, go语言只有封装,...