首页  >  文章  >  后端开发  >  RPC Action EP在Go中实现一个简单的RPC接口

RPC Action EP在Go中实现一个简单的RPC接口

PHPz
PHPz原创
2024-09-09 18:30:11976浏览

RPC Action EPImplement a simple RPC interface in Go

RPC(Remote procedure Call)是分布式系统中不同节点之间广泛使用的通信方式,是互联网时代的基础技术。 Go的标准库在net/rpc包下提供了RPC的简单实现。本文旨在通过引导您使用 net/rpc 包实现一个简单的 RPC 接口来帮助您了解 RPC。

本文首发于Medium MPP计划。如果您是 Medium 用户,请在 Medium 上关注我。非常感谢。

要使net/rpc中的函数能够被远程调用,必须满足以下五个条件:

  • 方法的类型已导出。
  • 方法已导出。
  • 该方法有两个参数,两者都是导出(或内置)类型。
  • 该方法的第二个参数是一个指针。
  • 该方法的返回类型为错误。

换句话说,函数签名必须是:

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

创建简单的 RPC 请求

基于这五个条件,我们可以构造一个简单的RPC接口:

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

接下来,您可以将HelloService类型的对象注册为RPC服务:

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)  
    }
}

客户端实现如下:

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)
}

首先,客户端使用 rpc.Dial 拨打 RPC 服务,然后通过 client.Call() 调用特定的 RPC 方法。第一个参数是RPC服务名和方法名用点组合起来,第二个是输入,第三个是返回值,是一个指针。这个例子展示了使用 RPC 是多么容易。

在服务器和客户端代码中,我们都需要记住 RPC 服务名称 HelloService 和方法名称 Hello。这很容易导致开发过程中出现错误,因此我们可以通过抽象公共部分来稍微包装代码。完整代码如下:

// 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)  
    }  
}
// 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)
}

是不是很眼熟?

使用 Go 的 net/rpc 包实现 JSON 编解码器

默认情况下,Go 的标准 RPC 库使用 Go 专有的 Gob 编码。但是,在其之上实现其他编码(例如 Protobuf 或 JSON)非常简单。标准库已经支持jsonrpc编码,我们可以通过对服务端和客户端代码进行小改动来实现JSON编码。

// 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  
}

JSON请求数据对象内部对应两种结构:在客户端,是clientRequest,在服务器端,是serverRequest。 clientRequest 和 serverRequest 结构体的内容本质上是相同的:

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"`  
}

这里的Method表示由serviceName和Method组成的服务名称。 Params第一个元素是参数,Id是调用者维护的唯一调用号,用于区分并发场景下的请求。

我们可以使用 nc 来模拟服务器,然后运行客户端代码来查看 JSON 编码的客户端向服务器发送了哪些信息:

 nc -l 1234

nc 命令接收以下数据:

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

这与serverRequest一致。

我们还可以运行服务器代码并使用 nc 发送请求:

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

结论

本文介绍了Go标准库中的rpc包,强调了它的简单性和强大的性能。许多第三方 rpc 库都是构建在 rpc 包之上的。本文是 RPC 研究系列的第一部分。在下一篇文章中,我们将把protobuf与RPC结合起来,最终实现我们自己的RPC框架。

以上是RPC Action EP在Go中实现一个简单的RPC接口的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn