Build a Simple Go RPC Framework in 300 Lines – Learn the Basics
This article walks through creating a lightweight RPC framework in Go, covering the definition of RPC, TLV data encoding, gob serialization, transport handling, server and client implementation, and a complete test example to illustrate core remote‑call concepts.
Recently the author studied RPC principles and implemented a simple RPC framework in Go using about 300 lines of code to illustrate core concepts.
1. What is RPC
RPC allows a service A to call a function on service B when they run in different memory spaces, requiring a way to express the call and transmit it over the network.
2. Network data format
The framework uses a TLV (type‑length‑value) encoding scheme for TCP transmission. The data structure is defined as:
<code>type RPCdata struct {
Name string // function name
Args []interface{} // request or response body (excluding error)
Err string // error message from remote server
}
</code>Serialization is performed with Go's built‑in gob encoder:
<code>func Encode(data RPCdata) ([]byte, error) {
var buf bytes.Buffer
encoder := gob.NewEncoder(&buf)
if err := encoder.Encode(data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func Decode(b []byte) (RPCdata, error) {
buf := bytes.NewBuffer(b)
decoder := gob.NewDecoder(buf)
var data RPCdata
if err := decoder.Decode(&data); err != nil {
return RPCdata{}, err
}
return data, nil
}
</code>3. Transport layer
A simple TLV transport reads and writes a 4‑byte length header followed by the payload:
<code>type Transport struct {
conn net.Conn
}
func (t *Transport) Send(data []byte) error {
buf := make([]byte, 4+len(data))
binary.BigEndian.PutUint32(buf[:4], uint32(len(data)))
copy(buf[4:], data)
_, err := t.conn.Write(buf)
return err
}
func (t *Transport) Read() ([]byte, error) {
header := make([]byte, 4)
if _, err := io.ReadFull(t.conn, header); err != nil {
return nil, err
}
dataLen := binary.BigEndian.Uint32(header)
data := make([]byte, dataLen)
_, err := io.ReadFull(t.conn, data)
return data, err
}
</code>4. RPC server
The server registers functions by name and executes them when a request arrives:
<code>type RPCServer struct {
addr string
funcs map[string]reflect.Value
}
func (s *RPCServer) Register(name string, fn interface{}) {
s.funcs[name] = reflect.ValueOf(fn)
}
func (s *RPCServer) Execute(req RPCdata) RPCdata {
f, ok := s.funcs[req.Name]
if !ok {
return RPCdata{Name: req.Name, Args: nil, Err: fmt.Sprintf("func %s not Registered", req.Name)}
}
// unpack arguments, call function, pack results and error
// ... (reflection logic omitted for brevity)
return RPCdata{Name: req.Name, Args: nil, Err: ""}
}
</code>5. RPC client
The client builds a request, encodes it, sends it via the transport, receives the response, decodes it, and maps the returned values back to the caller’s function signature.
<code>func (c *Client) callRPC(rpcName string, fPtr interface{}) {
// reflect magic to bind the remote call to a local function variable
// encode request, send, read response, decode, handle errors
}
</code>6. Test the framework
A complete example registers a QueryUser function, starts the server, creates a client, and invokes the remote function:
<code>func QueryUser(id int) (User, error) { /* ... */ }
func main() {
gob.Register(User{})
srv := NewServer("localhost:3212")
srv.Register("QueryUser", QueryUser)
go srv.Run()
// client side
conn, _ := net.Dial("tcp", "localhost:3212")
cli := NewClient(conn)
var Query func(int) (User, error)
cli.callRPC("QueryUser", &Query)
u, _ := Query(1)
fmt.Println(u)
}
</code>The program prints the queried users and demonstrates a working RPC call.
In summary, this lightweight RPC framework shows the essential steps of defining a protocol, serializing data, transporting it over TCP, and invoking remote functions, helping readers grasp RPC fundamentals.
360 Zhihui Cloud Developer
360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.