This is a creation in Article, where the information may have evolved or changed.
This article was also posted on my blog Yeqown.github.io
What is RPC?
Remote Procedure Calls (English: remote Procedure Call, abbreviated RPC) is a computer communication protocol. The protocol allows programs running on one computer to invoke subroutines from another computer, and programmers do not need to program this interaction for additional purposes. If the software involved is object-oriented, the remote procedure call can also be called a remote call or a remote method call.
The remote procedure call is an example of a distributed computing client-server (Client/server), which is simple and widely popular. A remote procedure call is always made by the client to a server that executes several process requests and provides parameters to the client. The execution results are returned to the client. Due to a variety of variations and detail differences, various remote procedure call protocols are derived, and they are not compatible with each other.
What is JSON-RPC again?
Json-rpc is a stateless and lightweight remote Procedure call (RPC) Transfer protocol that delivers content that is primarily JSON-based. By invoking a remote server with a URL (such as Get/user), JSON-RPC directly defines the name of the function to invoke in the content (for example {"method": "GetUser"}), which makes the developer not trapped in the problem of using PUT or PATCH.
More JSON-RPC conventions See also: Https://zh.wikipedia.org/wiki/JSON-RPC
Problem
Service-side registration and invocation
Conventions such net/rpc
as:
- The method ' s type is exported.
- The method is exported.
- The method has both arguments, both exported (or builtin) types.
- The method ' s second argument is a pointer.
- The method has a return type error.
// 这就是约定func (t *T) MethodName(argType T1, replyType *T2) error
So here's the question:
问题1: Server怎么来注册`t.Methods`? 问题2: JSON-RPC请求参数里面的Params给到args?
Server-side type definition:
type methodType struct { method reflect.Method // 用于调用 ArgType reflect.Type ReplyType reflect.Type}type service struct { name string // 服务的名字, 一般为`T` rcvr reflect.Value // 方法的接受者, 即约定中的 `t` typ reflect.Type // 注册的类型, 即约定中的`T` method map[string]*methodType // 注册的方法, 即约定中的`MethodName`的集合}// Server represents an RPC Server.type Server struct { serviceMap sync.Map // map[string]*service}
To resolve issue 1, refer to the net/rpc
registration call in. The main use of reflect
this package. The code is as follows:
The //resolves the incoming type and the corresponding exportable method, storing the RCVR type,methods information in SERVER.M. If type is not exportable, it will error func (S *server) Register (RCVR interface{}) Error {_service: = new (Service) _service.typ = REFL Ect. TypeOf (RCVR) _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}//about Suitablemethods, also using reflect,//to get all the available _service.typ The relevant parameters of the method and method are saved to *methodtype
Suitablemethods code goes: https://github.com/yeqown/rpc/blob/master/server.go#l105
To solve issue 2, to resolve issue 2, and first see how to call method, the code is as follows:
// 约定: func (t *T) MethodName(argType T1, replyType *T2) error// s.rcvr: 即约定中的 t// argv: 即约定中的 argType// replyv: 即约定中的 replyTypefunc (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)}
See how to call, plus the JSON-RPC convention, know that to the server is a JSON, and Params
it is a JSON-formatted data. Then it becomes: interface{}
-req. Params to reflect.Value
-argv. So how do you convert it? Look at the code:
Func (S *server) call (req *request) *response {//....//According to Req. method to Query method//Req. Method format such as: "Servicename.methodname"//Mtype *methodtype Mtype: = Svc.method[methodname]//According to the Mtype at the time of registration. Argtype to generate a reflect.value argisvalue: = False/If true, need to indirect before. 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 for the argv parameter. Value, but argv is still 0 values so far. So how to put req. The Params is copied to argv? I tried, argv = reflect. Value (req. Params), but at the time of the call, it is called "map[string]interface{} as Main.*args",//This means that the value of the parameter is not correctly assigned to argv. That's the back. Convert function://BS, _: = json. Marshal (req. Params)//JSON. Unmarshal (BS, argv. Interface ())//So there are some restrictions ~, not much to say convert (req. Params, argv. Interface ())//Note: The Replytype in the Convention is a pointer type that is convenient for assigning values. According to the Mtype at the time of registration. ReplyType to generate a reflect.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)}
Support for HTTP calls
Having finished the above section, it is very easy to talk about support for HTTP. Implement the http.Handler
interface on the line ~. As follows:
// 支持之POST & jsonfunc (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 } // 解析请求参数到[]*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[0]) } return}
Using the example
Service-side use
// test_server.gopackage mainimport ( // "fmt" "net/http" "github.com/yeqown/rpc")type Int inttype 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)}
Client uses
// test_client.gopackage mainimport ( "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)}
Run
Realize
The above only picked up the more important part of me, said the implementation, more like the client support, JSON-RPC request response definition, can be consulted in the project. JSON-RPC is currently implemented based on TCP and HTTP, project address: Github.com/yeqown/rpc
Defects
Only JSON-RPC is supported, and the JSON-RPC Convention is not fully implemented. For example, in a batch call:
if the bulk-call RPC operation itself is not a valid JSON or an array that contains at least one value, the server returns a single Response object instead of an array. If a bulk call does not require a response object to be returned, the server does not need to return any results and must not return an empty array to the client.
Read the two RPC in the reference and discover the ways in which both are used codec
to provide extensions. Therefore, you can consider using this method to extend later.
Reference