What is RPC?

Remote Procedure Call (RPC) is a computer communication protocol. The protocol allows a program running on one computer to call a subroutine on another without the programmer having to program for this interaction. If the software involved uses object-oriented programming, remote procedure calls can also be called remote calls or remote method calls.

Remote procedure call is a simple and popular example of a Client/Server for distributed computing. A remote procedure call always involves a client making a request to the server to execute several procedures with parameters provided by the client. The execution result is returned to the client. Due to various variations and detail differences, various remote procedure call protocols are derived, and they are not compatible with each other. —————— from Wikipedia


What is JSON-RPC?

Json-rpc is a stateless and lightweight remote procedure call (RPC) transport protocol, which mainly transmits content through JSON. Instead of calling a remote server from a web address (such as GET /user), JSON-RPC defines the name of the function to be called directly in the content (such as {” method “: “GetUser”}), which also saves developers from having to use PUT or PATCH. See more JSON xml-rpc agreed: zh.wikipedia.org/wiki/JSON-R…

The problem

Server registration and invocation

Conventions such as NET/RPC:

  • The method’s type is exported.
  • the method is exported.
  • the method has two arguments, both exported (or builtin) types.
  • The method’s second argument is a pointer.
  • the method has return type error.
Func (t * t) MethodName(argType T1, replyType *T2) errorCopy the code

So here’s the question:

Question 1: How does the Server register 'T. thods'? Question 2: Does the Params in the JSON-RPC request parameter go to args?Copy the code

Server side type definition:

Type methodType struct {method reflect. method // Call ArgType reflect. type ReplyType reflect. type} type service struct {name string // The name of the service, usually 'T' RCVR reflect.Value // the recipient of the method, i.e. 'T' typ reflect.Type // the registered Type, T 'method map[string]*methodType Type Server struct {serviceMap sync.map // map[string]*service }Copy the code

To solve problem 1, refer to registration calls in NET/RPC. The reflect package is primarily used. The code is as follows:

// Parse the incoming type and the corresponding exportable method, and store the RCVR type and Methods information in server.m. // If type is not exportable, Func (s *Server) Register(RCVR interface{}) error {_service := new(service) _service.typ = reflect.TypeOf(RCVR) func (s *Server) Register(RCVR interface{}) error {_service := new(service) _service.typ = reflect. _service.rcvr = reflect.ValueOf(rcvr) sname := reflect.Indirect(_service.rcvr).Type().Name() if sname == "" { err_s := "rpc.Register: no service name for type " + _service.typ.String() log.Print(err_s) return errors.New(err_s) } if ! isExported(sname) { err_s := "rpc.Register: type " + sname + " is not exported" log.Print(err_s) return errors.New(err_s) } _service.name = sname _service.method = suitableMethods(_service.typ, true) // sync.Map.LoadOrStore if _, dup := s.m.LoadOrStore(sname, _service); dup { return errors.New("rpc: service already defined: "+ sname)} return nil} // For suitableMethods, also use reflect, // to get all exportable methods in _service.typ and their related parameters, save as *methodTypeCopy the code

SuitableMethods code from this: HTTPS: / / github.com/yeqown/rpc/blob/master/server.go#L105

Method = Method (); Method = Method ();

// protocol: func (t * t) MethodName(argType T1, replyType *T2) error Func (s *service) Call (mType *methodType, req *Request, argv, replyv reflect.Value) *Response { function := mtype.method.Func returnValues := function.Call([]reflect.Value{s.rcvr, argv, replyv}) errIter := returnValues[0].Interface() errmsg := "" if errIter ! = nil { errmsg = errIter.(error).Error() return NewResponse(req.ID, nil, NewJsonrpcErr(InternalErr, errmsg, nil)) } return NewResponse(req.ID, replyv.Interface(), nil) }Copy the code

See how to call, plus jSON-RPC convention, know that the server is a JSON, and the Params is a JSON format data. Interface {} -req. Params to reflect. value-argv. So how do you convert? Look at the code:

func (s *Server) call(req *Request) *Response { // .... Req.method = req.method; "ServiceName.MethodName" // mtype *methodType mType := svc.method[MethodName] // Generates a reflect.value based on the registered mtype.ArgType argIsValue := false // if true, need to indirect before calling. var argv reflect.Value if mtype.ArgType.Kind() == reflect.Ptr { argv = reflect.New(mtype.ArgType.Elem()) } else { argv = reflect.New(mtype.ArgType) argIsValue = true } if argIsValue { argv = Argv.elem ()} // generates a reflect.Value for the argv argument, but argv is still 0 so far. // How to copy req.params to argv? // I tried, argv = reflect.Value(req.params), but it said: "Map [string]interface{} as main.*Args", that is, the value of the argument is not properly assigned to argv. // Add the convert function: // bs, _ := json.Marshal(req.params) // json.Unmarshal(bs, argv.interface ())) // So there are some limitations ~, Convert (req.params, argv.interface ()) // Note: The convention ReplyType is a pointer type for easy assignment. Value Replyv := reflect.new (mtype.replytype.elem ()) switch mtype.ReplyType.Elem().Kind() { case reflect.Map: replyv.Elem().Set(reflect.MakeMap(mtype.ReplyType.Elem())) case reflect.Slice: replyv.Elem().Set(reflect.MakeSlice(mtype.ReplyType.Elem(), 0, 0)) } return svc.call(mtype, req, argv, replyv) }Copy the code

Support for HTTP calls

Now that you’ve done that, it’s easy to support HTTP. Implement HTTP.Handler interface. As follows:

// Support for POST & json func (s *Server) ServeHTTP(w http.responsewriter, r *http.Request) { var resp *Response w.Header().Set("Content-Type", "application/json") if r.Method ! = http.MethodPost { resp = NewResponse("", nil, NewJsonrpcErr( http.StatusMethodNotAllowed, "HTTP request method must be POST", nil),) response(w, resp) return} // Parse request parameters to []*rpc. request reqs, err := getRequestFromBody(r) if err ! = nil {resp = NewResponse("", nil, NewJsonrpcErr(InternalErr, err.error (), nil)) response(w, resp) return} Resps = s.handlewithRequests (reqs) if len(resps) > 1 {response(w, resps)} else {response(w, resps) resps[0]) } return }Copy the code

Use the sample

Server usage

// test_server.go
package main

import (
	// "fmt"
	"net/http"
	"github.com/yeqown/rpc"
)

type Int int

type Args struct {
	A int `json:"a"`
	B int `json:"b"`
}

func (i *Int) Sum(args *Args, reply *int) error {
	*reply = args.A + args.B
	return nil
}

type MultyArgs struct {
	A *Args `json:"aa"`
	B *Args `json:"bb"`
}

type MultyReply struct {
	A int `json:"aa"`
	B int `json:"bb"`
}

func (i *Int) Multy(args *MultyArgs, reply *MultyReply) error {
	reply.A = (args.A.A * args.A.B)
	reply.B = (args.B.A * args.B.B)
	return nil
}

func main() {
	s := rpc.NewServer()
	mine_int := new(Int)
	s.Register(mine_int)
	go s.HandleTCP("127.0.0.1:9999")

	// 开启http
	http.ListenAndServe(":9998", s)
}
Copy the code

Client use

// test_client.go package main import ( "github.com/yeqown/rpc" ) type Args struct { A int `json:"a"` B int `json:"b"` }  type MultyArgs struct { A *Args `json:"aa"` B *Args `json:"bb"` } type MultyReply struct { A int `json:"aa"` B int 'json:"bb"'} func main() {c := rpc.newClient () c.dialTCP ("127.0.0.1:9999") var sum int c.call ("1", "int.sum ", &Args{A: 1, B: 2}, &sum) println(sum) c.dialTCP ("127.0.0.1:9999") var reply MultyReply c.Call("2", "int.multy ", &multyargs {A: &Args{1, 2}, B: &Args{3, 4}}, &reply) println(reply.A, reply.B) }Copy the code

Run a screenshot

server

client

http-support


implementation

I only picked out the most important parts above, talking about the implementation, more such as client support, JSON-RPC request response definition, can be found in the project. At present, jSON-RPC is implemented based on TCP and HTTP, and the project address is github.com/yeqown/rpc

defects

Only JSON-RPC is supported, and jSON-RPC conventions are not fully implemented. For example, in a batch call:

If the batch-called RPC operation is not itself a valid JSON or an array containing at least one value, the server will return only a response object rather than an array. If a batch call has no response object to return, the server does not need to return any results and must not return an empty array to the client.

Read the two RPCS in the reference and see that both use the coDEC approach to provide extensions. So consider using this approach for future extensions.


reference