zoukankan      html  css  js  c++  java
  • RPC和Protobuf(二)

    跨语言的RPC

      标准库的RPC默认采用Go语言特有的Gob编码,因此从其他语言调用Go语言实现RPC服务将比较困难,在互联网的为服务时代,每个RPC以及服务的使用都可能采用不同的编码语言,因此跨语言是互联网时代RPC的一个首要

    条件。Go语言的RPC框架有两个比较有特色的设计: 一个是RPC数据包可以通过插件实现自定义的编码和解码;另一个是RPC建立在抽象的io.ReadWriteCloser接口之上,我们可以将RPC架设在不同的通信之上,这里我们尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的RPC。

    service.go代码如下:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net"
    	"net/rpc"
    	"net/rpc/jsonrpc"
    )
    
    // 定义服务结构体
    type HelloService struct {
    }
    
    // 定义服务空间名,之后要与客户端一致绑定
    const HelloServiceName = "grpc.Server"
    
    // 服务端接口要求方法实现
    type HelloServiceInterface = interface {
    	Hellos(request string, reply *string) error
    }
    
    // 将结构体注册到rpc的命名空间下,注意这里的命名空间可以是一个路径片段,隐式约束实现接口中的方法后才能注册
    func RegisterHelloService(svc HelloServiceInterface) error {
    	// 将注册一这个名字为空间的,可以通过映射到这个结构体下,在客户端通过.调用对应的方法
    	return rpc.RegisterName(HelloServiceName, svc)
    }
    
    // 服务端实现方法,注意这个函数名要和client端的.名字保持一致
    func (p *HelloService) Hellos(request string, reply *string) error {
    	*reply = "hello :" + request
    	return nil
    }
    
    func main() {
    	// 将结构体注册到rpc中,前提式实现了方法
    	RegisterHelloService(new(HelloService))
    	listener, err := net.Listen("tcp", ":1234")
    	if err != nil {
    		log.Fatal("ListenTCP error: ", err)
    	}
    
    	for {
    		fmt.Println("listenning...")
    		// 监听服务
    		conn, err := listener.Accept()
    		if err != nil {
    			log.Fatal("Accept error:", err)
    		}
    		// 将监听到的服务挂在在rpc上,这不过这里使用rpc.ServeCodec(),传入参数是针对服务端的JSON编解码器
    		go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    	}
    }  

    client.go代码如下:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net"
    	"net/rpc"
    	"net/rpc/jsonrpc"
    )
    
    // 自定义命名空间,与服务注册的名字一致
    const HelloServiceName = "grpc.Server"
    
    // 使用一个接口来约束注册的结构体
    type HelloServiceInterface = interface {
    	Hello (request string, reply *string) error
    }
    
    // 将结构体注册到rpc的命名空间下,注意这里的命名空间可以是一个路径片段
    func RegisterHelloService(svc HelloServiceInterface) error {
    	return rpc.RegisterName(HelloServiceName, svc)
    }
    
    
    func main() {
    	// 首先通过net建立tcp拨号,而不是rpc创建客户端了
    	conn, err := net.Dial("tcp","localhost:1234")
    	if err != nil {
    		log.Fatal("dialing: ",err)
    	}
    
    	// 区别于之前,这里基于tcp连接,建立JSON的编解码器
    	client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    
    	var reply
    	// 通过client.Call()时,第一个参数是用点号连接的RPC服务名字和方法名字,第二个和第三个参数分别是定义rpc方法的两个参数
    	err = client.Call(HelloServiceName+".Hellos","hello",&reply)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	fmt.Println(reply)
    }

    有了以上代码,我们可以测试哈,分别运行服务端和客户端代码,可以看到相应的结果。那和之前有什么差别?我们停止服务端的运行,运行一个监听tcp端口的命令: nc -l 1234 ;之后我们再次调用客户端时会出现如下结果:

    {"method":"grpc.Server.Hellos","params":["hello"],"id":0}。 这里我们可以看到客户端通过json数据传输给服务端(也就是说只要能发送json就可以调用服务端的函数了),这里需要说明的是Json数据内部对应了两个结构体:客户端是clientRequest、服务端是serverRequest:

    type  clientRequest struct {
        Method string   `json:"method"`
        Params [1]interface `json:"params"`
        Id  uint64 `json:"id"`
    }
    
    // 服务端类似

    在获取到RPC调用的Json数据后,我们来核实下service是否可以根据json数据返回相应的结果,启动服务端,在终端中数据:echo -e '{"method":"grpc.Server.Hellos","params":["hello"],"id":0}' | nc localhost 1234, 返回的结果是{"id":0,"result":"hello :hello","error":null}。说明可以通过json来实现跨语言的交流了,这里忽略了服务端的返回结构体。


    之前的版本使用的是Gob编码,并比支持http协议,并没有完全实现跨语言调用,下面的版本可以实现:

    package main
    
    import (
    	"io"
    	"net/http"
    	"net/rpc"
    	"net/rpc/jsonrpc"
    )
    
    // 创建一个函数的承载体
    type HelloServer struct {
    }
    
    // 定义服务函数
    // rpc规则: 必须含有两个参数,一个请求、一个相应,返回值为error类型,且方法名必须大写
    func (p *HelloServer) Hello(request string, reply *string) error {
    	*reply = "hello" + request
    	return nil
    }
    
    func main() {
    	// 将承载体的所有满足rpc规则的方法注册到HelloServer服务空间中
    	rpc.RegisterName("HelloServer", new(HelloServer))
    
    	http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
    		var conn io.ReadWriteCloser = struct {
    			io.Writer
    			io.ReadCloser
    		}{
    			ReadCloser: r.Body,
    			Writer:     w,
    		}
    		rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
    	})
    
    	http.ListenAndServe(":1234", nil)
    }

    在终端数据如下命令: curl localhost:1234/jsonrpc -X POST --data '{"method":"HelloServer.Hello","params":["hello"],"id":0}' 就可以完成rpc调用

  • 相关阅读:
    手机自动化测试:appium源码分析之bootstrap九
    手机自动化测试:appium源码分析之bootstrap八
    手机自动化测试:appium源码分析之bootstrap七
    HashMap
    Java 泛型
    LinkedList
    ArrayList
    Synchronzied(内置锁)
    第十四章 构建自定义的同步工具
    第十三章 ReentrantLock 简介
  • 原文地址:https://www.cnblogs.com/double-W/p/12734621.html
Copyright © 2011-2022 走看看