Backend Development 11 min read

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.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
Build a Simple Go RPC Framework in 300 Lines – Learn the Basics

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.

Backend DevelopmentrpcGoNetwork ProgrammingTLVgob serialization
360 Zhihui Cloud Developer
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.