SoFunction
Updated on 2025-03-05

How to implement a simple RPC framework in Go

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.