当前位置: 首页 > 工具软件 > JSON-IO > 使用案例 >

Golang-RPC(二):golang中的rpc实现之json-rpc

端木桐
2023-12-01

因为内置net/rpc包接口设计的缺陷,通过追踪 rpc.HandleHTTP() 方法,找到 ServeHTTP 方法:

func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method != "CONNECT" {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.WriteHeader(http.StatusMethodNotAllowed)
		io.WriteString(w, "405 must CONNECT\n")
		return
	}
	conn, _, err := w.(http.Hijacker).Hijack()
	if err != nil {
		log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error())
		return
	}
	io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
	server.ServeConn(conn)
}

func (server *Server) ServeConn(conn io.ReadWriteCloser) {
	buf := bufio.NewWriter(conn)
	srv := &gobServerCodec{
		rwc:    conn,
		dec:    gob.NewDecoder(conn),
		enc:    gob.NewEncoder(buf),
		encBuf: buf,
	}
	server.ServeCodec(srv)
}

根本就没有设置codec的参数,直接写死了 gob ,所以依赖于 net/rpc 包的除了 gob 之外的编码方式都只能使用 tcp 协议。

我们无法使用jsonrpc等定制的编码作为rpc.DialHTTP的底层协议,如果需要让jsonrpc支持rpc.DialHTTP函数,需要调整rpc的接口。

除了传输协议,还有可以指定一个RPC编码协议,用于编码/解码RPC调用的函数参数和返回值,RPC调用不指定编码协议时,默认采用Go语言特有的gob编码协议。

因为,, 其他语言一般都不支持Go语言的gob协议,因此如果需要跨语言RPC调用就需要采用通用的编码协议。

Go的标准库还提供了一个"net/rpc/jsonrpc"包,用于提供基于JSON编码的RPC支持。

JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。

定义服务

package repo

import (
	"errors"
)

type Order struct {}

type OrderInfo struct {
	Id string
	Price float64
	Status int
}

func (o *Order) GetOne(orderId string, orderInfo *OrderInfo) error {
	if orderId == "" {
		return errors.New("orderId is invalid")
	}

	*orderInfo = OrderInfo{
		Id: orderId,
		Price: 100.00,
		Status: 1,
	}

	return nil
}

服务端

package main

import (
	"log"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"

	"demo1/go-rpc/repo"
)

func main() {
	err := rpc.Register(new(repo.Order))
	if err != nil {
		log.Fatal(err)
	}

	l, err := net.Listen("tcp", ":8100")
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, e := l.Accept()
		if e != nil {
			continue
		}
		go jsonrpc.ServeConn(conn)
	}
}

客户端

package main

import (
	"fmt"
	"log"
	"net/rpc/jsonrpc"

	"demo1/go-rpc/repo"
)

func main() {
	client, err := jsonrpc.Dial("tcp", "127.0.0.1:8100")
	if err != nil {
		log.Fatal("dialing:", err)
	}

	orderId := "asddddddd"
	var orderInfo repo.OrderInfo

	err = client.Call("Order.GetOne", orderId, &orderInfo)
	if err != nil {
		log.Fatal("Order error:", err)
	}
	fmt.Println(orderInfo)
}

jsonrpc 中的协议说明

使用tcp工具连接测试,比如nc

1、请求参数列表

id		int 	编号,默认是0
method	string	要调用的方法
params			参数,数组或者是map,如果参数是单个的值应转换成数组。

例如

{"method":"Order.GetOne","params":["asddddddd"],"id":0}
{"method":"Order.GetOne","params":{"A":9,"B":2},"id":0}

2、响应参数

id		int		编号,默认是0
result			结果
error	string	错误信息

如果成功,返回结果:

{"id":0,"result":{"Id":"asddddddd","Price":100,"Status":1},"error":null}

如果失败,返回结果:

{"id":0,"result":null,"error":"rpc: can't find service Arith.Multiply"}
{"id":0,"result":null,"error":"json: cannot unmarshal string into Go value of type [1]interface {}"}

以上是采用 TCP 来调用的 RPC,就是将传输的格式改成了 JSON 格式,基于此可以实现一个 HTTP+JSON+RPC。我们在 HTTP+GOB+RPC 的基础上,将设置 Handdler 修改一下,设置 Codec 为 JSON 即可。

func main() {
	err := rpc.Register(new(repo.Order))
	if err != nil {
		log.Fatal(err)
	}

	http.HandleFunc(rpc.DefaultRPCPath, func(writer http.ResponseWriter, request *http.Request) {
		var conn io.ReadWriteCloser = struct {
			io.Writer
			io.ReadCloser
		}{
			Writer:     writer,
			ReadCloser: request.Body,
		}
		rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
	})

	http.ListenAndServe(":8100", nil)
}

在postman发起请求

GET: http://localhost:8100/_goRPC_
body raw: {"method":"Order.GetOne","params":["asddddddd"],"id":10}

response: {"id":10,"result":{"Id":"asddddddd","Price":100,"Status":1},"error":null}

说明服务端启动成功了。

 类似资料: