golang实现RPC的几种方式

举报
lxw1844912514 发表于 2022/03/26 23:02:22 2022/03/26
【摘要】 什么是RPC 远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。 用通俗易懂的语言描述就是:RPC允许跨机...

什么是RPC

远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。如果涉及的软件采用面向对象编程,那么远程过程调用亦可称作远程调用或远程方法调用。

用通俗易懂的语言描述就是:RPC允许跨机器、跨语言调用计算机程序方法。打个比方,我用go语言写了个获取用户信息的方法getUserInfo,并把go程序部署在阿里云服务器上面,现在我有一个部署在腾讯云上面的php项目,需要调用golang的getUserInfo方法获取用户信息,php跨机器调用go方法的过程就是RPC调用。

golang中如何实现RPC

在golang中实现RPC非常简单,有封装好的官方库和一些第三方库提供支持。Go RPC可以利用tcp或http来传递数据,可以对要传递的数据使用多种类型的编解码方式。golang官方的net/rpc库使用encoding/gob进行编解码,支持tcphttp数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc库实现的RPC方法没办法进行跨语言调用。

golang官方还提供了net/rpc/jsonrpc库实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。

除了golang官方提供的rpc库,还有许多第三方库为在golang中实现RPC提供支持,大部分第三方rpc库的实现都是使用protobuf进行数据编解码,根据protobuf声明文件自动生成rpc方法定义与服务注册代码,在golang中可以很方便的进行rpc服务调用。

net/rpc库

下面的例子演示一下如何使用golang官方的net/rpc库实现RPC方法,使用http作为RPC的载体,通过net/http包监听客户端连接请求。

$GOPATH/src/test/rpc/rpc_server.go


   
  1. package main
  2. import (
  3.     "errors"
  4.     "fmt"
  5.     "log"
  6.     "net"
  7.     "net/http"
  8.     "net/rpc"
  9.     "os"
  10. )
  11. // 算数运算结构体
  12. type Arith struct {
  13. }
  14. // 算数运算请求结构体
  15. type ArithRequest struct {
  16.     A int
  17.     B int
  18. }
  19. // 算数运算响应结构体
  20. type ArithResponse struct {
  21.     Pro int // 乘积
  22.     Quo int // 商
  23.     Rem int // 余数
  24. }
  25. // 乘法运算方法
  26. func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
  27.     res.Pro = req.A * req.B
  28.     return nil
  29. }
  30. // 除法运算方法
  31. func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {
  32.     if req.B == 0 {
  33.         return errors.New("divide by zero")
  34.     }
  35.     res.Quo = req.A / req.B
  36.     res.Rem = req.A % req.B
  37.     return nil
  38. }
  39. func main() {
  40.     rpc.Register(new(Arith)) // 注册rpc服务
  41.     rpc.HandleHTTP()         // 采用http协议作为rpc载体
  42.     lis, err := net.Listen("tcp""127.0.0.1:8095")
  43.     if err != nil {
  44.         log.Fatalln("fatal error: ", err)
  45.     }
  46.     fmt.Fprintf(os.Stdout, "%s""start connection")
  47.     http.Serve(lis, nil)
  48. }

上述服务端程序运行后,将会监听本地的8095端口,我们可以实现一个客户端程序,连接服务端并实现RPC方法调用。

$GOPATH/src/test/rpc/rpc_client.go


   
  1. package main
  2. import (
  3.     "fmt"
  4.     "log"
  5.     "net/rpc"
  6. )
  7. // 算数运算请求结构体
  8. type ArithRequest struct {
  9.     A int
  10.     B int
  11. }
  12. // 算数运算响应结构体
  13. type ArithResponse struct {
  14.     Pro int // 乘积
  15.     Quo int // 商
  16.     Rem int // 余数
  17. }
  18. func main() {
  19.     conn, err := rpc.DialHTTP("tcp""127.0.0.1:8095")
  20.     if err != nil {
  21.         log.Fatalln("dailing error: ", err)
  22.     }
  23.     req := ArithRequest{92}
  24.     var res ArithResponse
  25.     err = conn.Call("Arith.Multiply", req, &res) // 乘法运算
  26.     if err != nil {
  27.         log.Fatalln("arith error: ", err)
  28.     }
  29.     fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
  30.     err = conn.Call("Arith.Divide", req, &res)
  31.     if err != nil {
  32.         log.Fatalln("arith error: ", err)
  33.     }
  34.     fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
  35. }


net/rpc/jsonrpc库

上面的例子我们演示了使用net/rpc实现RPC的过程,但是没办法在其他语言中调用上面例子实现的RPC方法。所以接下来的例子我们演示一下使用net/rpc/jsonrpc库实现RPC方法,此方式实现的RPC方法支持跨语言调用。

$GOPATH/src/test/rpc/jsonrpc_server.go


   
  1. package main
  2. import (
  3.     "errors"
  4.     "fmt"
  5.     "log"
  6.     "net"
  7.     "net/rpc"
  8.     "net/rpc/jsonrpc"
  9.     "os"
  10. )
  11. // 算数运算结构体
  12. type Arith struct {
  13. }
  14. // 算数运算请求结构体
  15. type ArithRequest struct {
  16.     A int
  17.     B int
  18. }
  19. // 算数运算响应结构体
  20. type ArithResponse struct {
  21.     Pro int // 乘积
  22.     Quo int // 商
  23.     Rem int // 余数
  24. }
  25. // 乘法运算方法
  26. func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error {
  27.     res.Pro = req.A * req.B
  28.     return nil
  29. }
  30. // 除法运算方法
  31. func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error {
  32.     if req.B == 0 {
  33.         return errors.New("divide by zero")
  34.     }
  35.     res.Quo = req.A / req.B
  36.     res.Rem = req.A % req.B
  37.     return nil
  38. }
  39. func main() {
  40.     rpc.Register(new(Arith)) // 注册rpc服务
  41.     lis, err := net.Listen("tcp""127.0.0.1:8096")
  42.     if err != nil {
  43.         log.Fatalln("fatal error: ", err)
  44.     }
  45.     fmt.Fprintf(os.Stdout, "%s""start connection")
  46.     for {
  47.         conn, err := lis.Accept() // 接收客户端连接请求
  48.         if err != nil {
  49.             continue
  50.         }
  51.         go func(conn net.Conn) { // 并发处理客户端请求
  52.             fmt.Fprintf(os.Stdout, "%s""new client in coming\n")
  53.             jsonrpc.ServeConn(conn)
  54.         }(conn)
  55.     }
  56. }

上述服务端程序启动后,将会监听本地的8096端口,并处理客户端的tcp连接请求。我们可以用golang实现一个客户端程序连接上述服务端并进行RPC调用。

$GOPATH/src/test/rpc/jsonrpc_client.go


   
  1. package main
  2. import (
  3.     "fmt"
  4.     "log"
  5.     "net/rpc/jsonrpc"
  6. )
  7. // 算数运算请求结构体
  8. type ArithRequest struct {
  9.     A int
  10.     B int
  11. }
  12. // 算数运算响应结构体
  13. type ArithResponse struct {
  14.     Pro int // 乘积
  15.     Quo int // 商
  16.     Rem int // 余数
  17. }
  18. func main() {
  19.     conn, err := jsonrpc.Dial("tcp""127.0.0.1:8096")
  20.     if err != nil {
  21.         log.Fatalln("dailing error: ", err)
  22.     }
  23.     req := ArithRequest{92}
  24.     var res ArithResponse
  25.     err = conn.Call("Arith.Multiply", req, &res) // 乘法运算
  26.     if err != nil {
  27.         log.Fatalln("arith error: ", err)
  28.     }
  29.     fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
  30.     err = conn.Call("Arith.Divide", req, &res)
  31.     if err != nil {
  32.         log.Fatalln("arith error: ", err)
  33.     }
  34.     fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
  35. }


protorpc库

为了实现跨语言调用,在golang中实现RPC方法的时候我们应该选择一种跨语言的数据编解码方式,比如JSON,上述的jsonrpc可以满足此要求,但是也存在一些缺点,比如不支持http传输,数据编解码性能不高等。于是呢,一些第三方rpc库都选择采用protobuf进行数据编解码,并提供一些服务注册代码自动生成功能。下面的例子我们使用protobuf来定义RPC方法及其请求响应参数,并使用第三方的protorpc库来生成RPC服务注册代码。

c767f2f2fe25e2227a5919eae8a819b9.png

首先,需要安装protobufprotoc可执行命令,可以参考此篇文章:protobuf快速上手指南

然后,我们编写一个proto文件,定义要实现的RPC方法及其相关参数。

$GOPATH/src/test/rpc/pb/arith.proto


   
  1. syntax = "proto3";package pb;  // 算术运算请求结构
  2. message ArithRequest {    int32 a = 1;    int32 b = 2;}// 算术运算响应结构
  3. message ArithResponse {    
  4.     int32 pro = 1;  // 乘积
  5.     int32 quo = 2;  // 商
  6.     int32 rem = 3;  // 余数
  7. }// rpc方法
  8. service ArithService {    
  9. rpc multiply (ArithRequest) returns (ArithResponse);  // 乘法运算方法
  10. rpc divide (ArithRequest) returns (ArithResponse);   // 除法运算方法
  11. }

接下来我们需要根据上述定义的arith.proto文件生成RPC服务代码。要先安装protorpc库:go get github.com/chai2010/protorpc 然后使用protoc工具生成代码:protoc --go_out=plugin=protorpc=. arith.proto 执行protoc命令后,在与arith.proto文件同级的目录下生成了一个arith.pb.go文件,里面包含了RPC方法定义和服务注册的代码。

基于生成的arith.pb.go代码我们来实现一个rpc服务端

$GOPATH/src/test/rpc/protorpc_server.go


   
  1. package main
  2. import (
  3.     "errors"
  4.     "test/rpc/pb"
  5. )
  6. // 算术运算结构体
  7. type Arith struct {
  8. }
  9. // 乘法运算方法
  10. func (this *Arith) Multiply(req *pb.ArithRequest, res *pb.ArithResponse) error {
  11.     res.Pro = req.GetA() * req.GetB()
  12.     return nil
  13. }
  14. // 除法运算方法
  15. func (this *Arith) Divide(req *pb.ArithRequest, res *pb.ArithResponse) error {
  16.     if req.GetB() == 0 {
  17.         return errors.New("divide by zero")
  18.     }
  19.     res.Quo = req.GetA() / req.GetB()
  20.     res.Rem = req.GetA() % req.GetB()
  21.     return nil
  22. }
  23. func main() {
  24.     pb.ListenAndServeArithService("tcp""127.0.0.1:8097"new(Arith))
  25. }

运行上述程序,将会监听本地的8097端口并接收客户端的tcp连接。

基于ariti.pb.go再来实现一个客户端程序。

$GOPATH/src/test/protorpc_client.go


   
  1. package main
  2. import (
  3.     "fmt"
  4.     "log"
  5.     "test/rpc/pb"
  6. )
  7. func main() {
  8.     conn, err := pb.DialArithService("tcp""127.0.0.1:8097")
  9.     if err != nil {
  10.         log.Fatalln("dailing error: ", err)
  11.     }
  12.     defer conn.Close()
  13.     req := &pb.ArithRequest{92}
  14.     res, err := conn.Multiply(req)
  15.     if err != nil {
  16.         log.Fatalln("arith error: ", err)
  17.     }
  18.     fmt.Printf("%d * %d = %d\n", req.GetA(), req.GetB(), res.GetPro())
  19.     res, err = conn.Divide(req)
  20.     if err != nil {
  21.         log.Fatalln("arith error ", err)
  22.     }
  23.     fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem)
  24. }


如何跨语言调用golang的RPC方法

上面的三个例子,我们分别使用net/rpcnet/rpc/jsonrpcprotorpc实现了golang中的RPC服务端,并给出了对应的golang客户端RPC调用示例,因为JSON和protobuf是支持多语言的,所以使用jsonrpcprotorpc实现的RPC方法我们是可以在其他语言中进行调用的。下面给出一个php客户端程序,通过socket连接调用jsonrpc实现的服务端RPC方法。

$PHPROOT/jsonrpc.php


   
  1. <?php
  2. class JsonRPC {
  3.     private $conn;
  4.     function __construct($host, $port) {
  5.         $this->conn = fsockopen($host, $port, $errno, $errstr, 3);
  6.         if (!$this->conn) {
  7.             return false;
  8.         }
  9.     }
  10.     public function Call($method, $params) {
  11.         if (!$this->conn) {
  12.             return false;
  13.         }
  14.         $err = fwrite($this->conn, json_encode(array(
  15.                 'method' => $method,
  16.                 'params' => array($params),
  17.                 'id'     => 0,
  18.             ))."\n");
  19.         if ($err === false) {
  20.             return false;
  21.         }
  22.         stream_set_timeout($this->conn, 03000);
  23.         $line = fgets($this->conn);
  24.         if ($line === false) {
  25.             return NULL;
  26.         }
  27.         return json_decode($line,true);
  28.     }
  29. }
  30. $client = new JsonRPC("127.0.0.1"8096);
  31. $args = array('A'=>9'B'=>2);
  32. $r = $client->Call("Arith.Multiply", $args);
  33. printf("%d * %d = %d\n", $args['A'], $args['B'], $r['result']['Pro']);
  34. $r = $client->Call("Arith.Divide", array('A'=>9'B'=>2));
  35. printf("%d / %d, Quo is %d, Rem is %d\n", $args['A'], $args['B'], $r['result']['Quo'], $r['result']['Rem']);


其他RPC库

除了上面提到的三种在golang实现RPC的方式外,还有一些其他的rpc库提供了类似的功能,比较出名的有google开源的grpc,但是grpc的初次安装比较麻烦,这里就不做进一步介绍了,有兴趣的可以自己了解。

3ccb0260635466d7022a6bf5040b8266.png

文章来源: blog.csdn.net,作者:lxw1844912514,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/lxw1844912514/article/details/122505293

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。