> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# RPC Action EP1: Implement a simple RPC interface in Go
- URL: https://huizhou92.com/implementing-a-simple-rpc-interface-in-go-a-step-by-step-guide/
- Published: 2024-08-14T11:19:50.000Z
- Updated: 2026-09-08T02:30:44.000Z
- Description: RPC Action EP1: Implement a simple RPC interface in Go. RPC(Remote Procedure Call)is a widely used communication method between different nodes in distribu。
- Author: huizhou92
- Tags: #Migrated-1788833207488, #Import 2026-09-08 02:07

RPC(Remote Procedure Call)is a widely used communication method between different nodes in distributed systems and a foundational technology of the Internet era. Go’s standard library provides a simple implementation of RPC under the `net/rpc` package. This article aims to help you understand RPC by walking you through implementing a simple RPC interface using the `net/rpc` package.

To enable a function to be remotely called in `net/rpc`, it must meet the following [five conditions](https://go.googlesource.com/go/blob/9177e12ccc2115e44de824ae7247ace88617c29a/src/net/rpc/server.go?ref=huizhou92.com#L13):

- The method’s type is exported.
- The method is exported.
- The method has two arguments, both of which are exported (or built-in) types.
- The method’s second argument is a pointer.
- The method has a return type of error.

In other words, the function signature must be:

```go
func (t *T) MethodName(argType T1, replyType *T2) error
```

### Creating a Simple RPC Request

Based on these five conditions, we can construct a simple RPC interface:

```go
type HelloService struct{}   
 func (p *HelloService) Hello(request string, reply *string) error {   
     log.Println("HelloService Hello")   
     *reply = "hello:" + request   
     return nil   
 }
```

Next, you can register an object of the `HelloService` type as an RPC service:

```go
func main() { 
   _ = rpc.RegisterName("HelloService", new(HelloService))   
   listener, err := net.Listen("tcp", ":1234")   
   if err != nil {   
       log.Fatal("ListenTCP error:", err)   
   }   
   for {   
       conn, err := listener.Accept()   
       if err != nil {   
          log.Fatal("Accept error:", err)   
       }   
       go rpc.ServeConn(conn)   
   } 
 }
```

The client-side implementation is as follows:

```go
func main() { 
   conn, err := net.Dial("tcp", ":1234") 
   if err != nil { 
     log.Fatal("net.Dial:", err) 
   } 
   client := rpc.NewClient(conn) 
   var reply string 
   err = client.Call("HelloService.Hello", "hello", &reply) 
   if err != nil { 
     log.Fatal(err) 
   } 
   fmt.Println(reply) 
 }
```

First, the client dials the RPC service using `rpc.Dial`, then invokes a specific RPC method via `client.Call()`. The first parameter is the RPC service name and method name combined with a dot, the second is the input, and the third is the return value, which is a pointer. This example demonstrates how easy it is to use RPC.

In both the server and client code, we need to remember the RPC service name `HelloService` and the method name `Hello`. This can easily lead to errors during development, so we can wrap the code slightly by abstracting the common parts. The complete code is as follows:

```go
// server.go 
 const ServerName = "HelloService"   
    
 type HelloServiceInterface = interface {   
     Hello(request string, reply *string) error   
 }   
    
 func RegisterHelloService(srv HelloServiceInterface) error {   
     return rpc.RegisterName(ServerName, srv)   
 }   
    
 type HelloService struct{}   
    
 func (p *HelloService) Hello(request string, reply *string) error {   
     log.Println("HelloService Hello")   
     *reply = "hello:" + request   
     return nil   
 } 
 ​ 
 func main() {   
     _ = RegisterHelloService(new(HelloService))   
     listener, err := net.Listen("tcp", ":1234")   
     if err != nil {   
        log.Fatal("ListenTCP error:", err)   
     }   
     for {   
        conn, err := listener.Accept()   
        if err != nil {   
           log.Fatal("Accept error:", err)   
        }   
        go rpc.ServeConn(conn)   
     }   
 }
```

```go
// client.go 
 ​ 
 type HelloServiceClient struct {   
     *rpc.Client   
 }   
    
 var _ HelloServiceInterface = (*HelloServiceClient)(nil)   
 ​ 
 const ServerName = "HelloService"  
 ​ 
 func DialHelloService(network, address string) (*HelloServiceClient, error) {   
     conn, err := net.Dial(network, address)   
     client := rpc.NewClient(conn)   
     if err != nil {   
        return nil, err   
     }   
     return &HelloServiceClient{Client: client}, nil   
 } 
 ​ 
 func (p *HelloServiceClient) Hello(request string, reply *string) error {   
     return p.Client.Call(ServerName+".Hello", request, reply)   
 } 
 func main() { 
   client, err := DialHelloService("tcp", "localhost:1234")   
   if err != nil {   
       log.Fatal("net.Dial:", err)   
   }   
   var reply string   
   err = client.Hello("hello", &reply)   
   if err != nil {   
       log.Fatal(err)   
   }   
   fmt.Println(reply) 
 }
```

Does it look familiar?

### Implementing JSON Codec with Go’s `net/rpc` Package

By default, Go’s standard RPC library uses Go’s proprietary Gob encoding. However, it’s straightforward to implement other encodings, such as `Protobuf` or `JSON`, on top of it. The standard library already supports `jsonrpc` encoding, and we can implement JSON encoding by making minor changes to the server and client code.

```go
// server.go 
 func main() {   
     _ = rpc.RegisterName("HelloService", new(HelloService))   
     listener, err := net.Listen("tcp", ":1234")   
     if err != nil {   
        log.Fatal("ListenTCP error:", err)   
     }   
     for {   
        conn, err := listener.Accept()   
        if err != nil {   
           log.Fatal("Accept error:", err)   
        }   
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))   
        //go rpc.ServeConn(conn)   
     }   
 } 
 ​ 
 //client.go 
 func DialHelloService(network, address string) (*HelloServiceClient, error) {   
     conn, err := net.Dial(network, address)   
     //client := rpc.NewClient(conn)   
     client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))   
     if err != nil {   
        return nil, err   
     }   
     return &HelloServiceClient{Client: client}, nil   
 }
```

The JSON request data object internally corresponds to two structures: on the client side, it’s [clientRequest](https://go.googlesource.com/go/blob/9177e12ccc2115e44de824ae7247ace88617c29a/src/net/rpc/jsonrpc/client.go?ref=huizhou92.com#L46), and on the server side, it’s [serverRequest](https://go.googlesource.com/go/blob/9177e12ccc2115e44de824ae7247ace88617c29a/src/net/rpc/jsonrpc/server.go?ref=huizhou92.com#L46). The content of `clientRequest` and `serverRequest` structures is essentially the same:

```go
type clientRequest struct {   
     Method string `json:"method"`   
     Params [1]any `json:"params"`   
     Id     uint64 `json:"id"`   
 } 
 type serverRequest struct {   
     Method string           `json:"method"`   
     Params *json.RawMessage `json:"params"`   
     Id     *json.RawMessage `json:"id"`   
 }
```

Here, `Method` represents the service name composed of `serviceName` and `Method`. The first element of `Params` is the parameter, and `Id` does the caller maintain a unique call number, used to distinguish requests in concurrent scenarios.

We can use `nc` to simulate the server and then run the client code to see what information the JSON-encoded client sends to the server:

```yaml
nc -l 1234
```

The `nc` command receives the following data:

```json
{"method":"HelloService.Hello","params":["hello"],"id":0}
```

This is consistent with [serverRequest](https://go.googlesource.com/go/blob/9177e12ccc2115e44de824ae7247ace88617c29a/src/net/rpc/jsonrpc/server.go?ref=huizhou92.com#L46).

We can also run the server code and use `nc` to send a request:

```makefile
echo -e '{"method":"HelloService.Hello","params":["Hello"],"Id":1}' | nc localhost 1234  
 ---  
 {"id":1,"result":"hello:Hello","error":null}
```

### Conclusion

This article introduced the `rpc` package from Go's standard library, highlighting its simplicity and powerful performance. Many third-party `rpc` libraries are built on top of the `rpc` package. This article serves as the first installment in a series on RPC research. In the next article, we will combine `protobuf` with RPC and eventually implement our own RPC framework.

[文章索引](/article-index/)