This article aims to describe several core problems and solutions in RPC framework design, and build a simple RPC framework based on Golang reflection technology.
Project address:Tiny-RPC
RPC
RPC(Remote Procedure Call), that is, remote procedure call, can be understood as if Service A wants to call a function of Service B that is not in the same memory space, it cannot be called directly because it is not in the same memory space. It needs to express the semantics of the call and convey the call data through the network.
Server side
2 problems need to be solved on the RPC server:
- Since the client transmits the RPC function name, how does the server maintain the mapping between the function name and the function entity
- How to implement the call of the corresponding function entity based on the function name
Core process
- Maintain the mapping of function names to functions
- After receiving the function name and parameter list from the client, parse the parameter list as reflected value and execute the corresponding function
- Encode the function execution result and return it to the client
Method Registration
The server needs to maintain the mapping of RPC function names to RPC function entities, we can usemap
Data structures to maintain mapping relationships.
type Server struct { addr string funcs map[string] } // Register a method via name func (s *Server) Register(name string, f interface{}) { if _, ok := [name]; ok { return } [name] = (f) }
Execute the call
Generally speaking, when the client calls RPC, it will send the function name and parameter list as request data to the server.
Since we used map[string]
To maintain the mapping between function names and function entities, we can use()
To call the function corresponding to the function name.
package main import ( "fmt" "reflect" ) func main() { // Register methods funcs := make(map[string]) funcs["add"] = (add) // When receives client's request req := []{(1), (2)} vals := funcs["add"].Call(req) var rsp []interface{} for _, val := range vals { rsp = append(rsp, ()) } (rsp) } func add(a, b int) (int, error) { return a + b, nil }
Specific implementation
Due to space limitations, no specific code for the server implementation is posted here. Please check the details.Project gallery。
Client
1 problem needs to be solved by the RPC client:
- Since the specific implementation of a function is on the server side, the client only has the function prototype. How can the client call its function entity through the function prototype?
Core process
- Encode the function parameters passed by the caller and transmit them to the server
- Decode the server response data and return it to the caller
Generate a call
We can bind a function entity to the specified function prototype.
package main import ( "fmt" "reflect" ) func main() { add := func(args []) [] { result := args[0].Interface().(int) + args[1].Interface().(int) return []{(result)} } var addptr func(int, int) int container := (&addptr).Elem() v := ((), add) (v) (addptr(1, 2)) }
Specific implementation
Due to space limitations, no specific code for the client implementation is posted here. Please check the details.Project gallery。
Data transfer format
We need to define the data format for the server to interact with the client.
type Data struct { Name string // service name Args []interface{} // request's or response's body except error Err string // remote server error }
The encoding and decoding functions corresponding to the interactive data.
func encode(data Data) ([]byte, error) { var buf encoder := (&buf) if err := (data); err != nil { return nil, err } return (), nil } func decode(b []byte) (Data, error) { buf := (b) decoder := (buf) var data Data if err := (&data); err != nil { return Data{}, err } return data, nil }
At the same time, we need to define simpleTLVProtocol (fixed length message header + variable length message body) regulates data transmission.
// Transport struct type Transport struct { conn } // NewTransport creates a transport func NewTransport(conn ) *Transport { return &Transport{conn} } // Send data func (t *Transport) Send(req Data) error { b, err := encode(req) // Encode req into bytes if err != nil { return err } buf := make([]byte, 4+len(b)) .PutUint32(buf[:4], uint32(len(b))) // Set Header field copy(buf[4:], b) // Set Data field _, err = (buf) return err } // Receive data func (t *Transport) Receive() (Data, error) { header := make([]byte, 4) _, err := (, header) if err != nil { return Data{}, err } dataLen := .Uint32(header) // Read Header filed data := make([]byte, dataLen) // Read Data Field _, err = (, data) if err != nil { return Data{}, err } rsp, err := decode(data) // Decode rsp from bytes return rsp, err }
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.