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


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

    namespace go rpc
    
    service RpcService {
        string SayHi(1: string name);
        void SayHello(1: 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
    }

    现在,服务器和客户端究竟是如何工作的。你明白了吗

    转载请注明出处,谢谢

  • 相关阅读:
    ajax与302响应
    读过/在读/想读的英文原著
    从编译DotNetOpenAuth中学到的程序集强签名知识
    百度输入法引起的Mac远程桌面Ctrl+.快捷键不起作用
    MacBook鼠标指针乱窜/不受控制问题的解决方法
    IIS中User-mode caching引起的Cache-Control不为public的问题
    让IIS8支持WCF的最简单方法
    在ASP.NET Web Forms中用System.Web.Optimization取代SquishIt
    苹果官方发布,iPhone 6 & Plus 设计素材
    jQuery 特效:盒子破碎和移动动画效果
  • 原文地址:https://www.cnblogs.com/ka200812/p/5868172.html
Copyright © 2020-2023  润新知