> ## 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/rpc-action-ep1-implement-a-simple-rpc-interface-in-go/
- Published: 2024-08-12T10:09:15.000Z
- Updated: 2026-09-06T11:40:23.000Z
- Description: Learn to implement a simple RPC interface in Go using the `net/rpc` package! 🚀 This article covers the basics and JSON encoding. Read more: https://dev.to/huizhou92/rpc-action-ep1-implement-a-simple-rpc-interface-in-go-1m96
- Author: huizhou92
- Tags: tech, go, #lang-en

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.

> This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on [Medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

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](https://pkg.go.dev/net/rpc/jsonrpc?ref=huizhou92.com#NewClientCodec). 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` is a unique call number maintained by the caller, 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:

```shell
 nc -l 1234

```

The `nc` command receives the following data:

```shell
 {"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:

```shell
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.