分享
  1. 首页
  2. 文章

Golang中的RPC和gRPC

xiaodulala · · 16102 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、RPC编程

RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。RPC协议构建于TCP或UDP,或者是HTTP上。允许开发者直接调用另一台服务器上的程序,而开发者无需另外的为这个调用过程编写网络通信相关代码,使得开发网络分布式程序在内的应用程序更加容易

RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。当执行一个远程过程调用时,客户端程序首先先发送一个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到客户端的调用信息到达。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息。然后等待下一个调用。

  • Go语言中的RPC支持与处理
在Go中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。这使得用Go语言开发的多个进程之间的通信变得非常简单
net/rpc包允许PRC客户端程序通过网络或者其他IO连接调用一个远程对象的公开方法(该方法必须是外部可访问即首字母大写)。在PRC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。一个RPC服务端可以注册多个不通类型的对象,但<font color="Brown">不允许注册同一类型的多个对象。</font>
  • 一个对象中只有满足如下条件的方法,才能被PRC服务端设置为可供远程访问
    1.<font color="Brown">必须是在对象外部可公开调用的方法(首字母大写)</font>
    2.<font color="Brown">必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go內建支持的类型</font>
    3.<font color="Brown">第二个参数必须是一个指针</font>
    4.<font color="Brown">方法必须返回一个error类型的值</font>
    用代码表示

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

    在上面这行代码中,类型 T T1 T2 默认会使用Go内置的encoding/gob包进行编码和解码
    改方法的第一个参数表示由PRC客户端传入的参数,第二个参数表示要返回给PRC客户端的结果。改方法最后返回一个error类型

  • RPC客户端和服务器端的使用
RPC服务端可以通过调用 ```rpc.ServerConn```处理单个连接请求。多数情况下,通过tcp或是http在某个网络地址上监听然后再创建该服务是个不错的选择 
在RPC客户端,Go的net/rpc包提供了便利的```rpc.Dial()```和```rpc.DialHTTP()```方法来与指定的RPC服务建立连接。在建立连接之后,Go的net/rpc包允许我们使用通过或者异步的方式接受RPC服务端的结果。调用RPC客户端的```Call()```方法则进行同步处理。这个时候客户端程序按照顺序执行。当调用RPC客户端的```Go()```方法时,则进行异步处理。客户端无需等待服务端的结果即可执行后面的程序,当接收到服务端响应时,再对其进行相应的处理。 无论是哪个方法,都必须要指定要调用的服务及其方法名称,以及一个客户端传入参数的引用,还有一个用于接收处理结果参数的指针 
如果没有指定RPC传输过程中使用何种编码解码器,默认使用Go标准库提供的eccoding/gob包进行数据传输 
  • 代码示例
服务器端代码
package main
import (
 "errors"
 "log"
 "net"
 "net/http"
 "net/rpc"
 "os"
 "time"
)
type Args struct {
 A, B int
}
type Quotient struct {
 Quo, Rem int
}
type Arith int
//计算乘积
func (t *Arith) Multiply(args *Args, reply *int) error {
 time.Sleep(time.Second * 3) //睡三秒,同步调用会等待,异步会先往下执行
 *reply = args.A * args.B
 return nil
}
//计算商和余数
func (t *Arith) Divide(args *Args, quo *Quotient) error {
 time.Sleep(time.Second * 3)
 if args.B == 0 {
 return errors.New("divide by zero")
 }
 quo.Quo = args.A / args.B
 quo.Rem = args.A % args.B
 return nil
}
func main() {
 //创建对象
 arith := new(Arith)
 //rpc服务注册了一个arith对象 公开方法供客户端调用
 rpc.Register(arith)
 //指定rpc的传输协议 这里采用http协议作为rpc调用的载体 也可以用rpc.ServeConn处理单个连接请求
 rpc.HandleHTTP()
 l, e := net.Listen("tcp", ":1234")
 if e != nil {
 log.Fatal("listen error", e)
 }
 go http.Serve(l, nil)
 os.Stdin.Read(make([]byte, 1))
}
客户端代码
package main
import (
 "fmt"
 "log"
 "net/rpc"
 "time"
)
type Args struct {
 A, B int
}
type Quotient struct {
 Quo, Rem int
}
func main() {
 //调用rpc服务端提供的方法之前,先与rpc服务端建立连接
 client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
 if err != nil {
 log.Fatal("dialHttp error", err)
 return
 }
 //同步调用服务端提供的方法
 args := &Args{7, 8}
 var reply int
 //可以查看源码 其实Call同步调用是用异步调用实现的。后续再详细学习
 err = client.Call("Arith.Multiply", args, &reply) //这里会阻塞三秒
 if err != nil {
 log.Fatal("call arith.Multiply error", err)
 }
 fmt.Printf("Arith:%d*%d=%d\n", args.A, args.B, reply)
 //异步调用
 quo := Quotient{}
 divCall := client.Go("Arith.Divide", args, &quo, nil)
 //使用select模型监听通道有数据时执行,否则执行后续程序
 for {
 select {
 case <-divCall.Done:
 fmt.Printf("商是%d,余数是%d\n", quo.Quo, quo.Rem)
 default:
 fmt.Println("继续向下执行....")
 time.Sleep(time.Second * 1)
 }
 }
}

说明

//Go函数的原型,注意其最后一个参数是一个channel 也就是调用结果存到了这个channel里 里面的类型数据是*Call类型
//如果你不传递这个channel这个参数,Go函数内部会默认创建一个*Call类型的10个长度的channel来缓存结果数据。channel的名字叫做
//Done也就是返回值*Calll俩面的最后一个值 
//好吧,其实我就是想说,cient.Go的返回值包含了最后一个参数(channel),想获取调用结果,可以从参数管道中直接获取,也可以从返回值
//Done中获取
// Call represents an active RPC.
type Call struct {
 ServiceMethod string // The name of the service and method to call.
 Args interface{} // The argument to the function (*struct).
 Reply interface{} // The reply from the function (*struct).
 Error error // After completion, the error status.
 Done chan *Call // Strobes when call is complete.
}
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {

二 gRpc

  • gRPC是什么
gRPC中文文档

gRPC是一个高性能、开源、通用的RPC框架。基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议([Protocol Buffers基本语法]()),支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库

  • gRPC应用场景
在gRPC客户端可以直接调用不通服务器上的远程程序,就想调用本地程序一样,很容易构建分布式应用和服务。和很多RPC系统一样,服务负责实现定义好的接口并处理客户端请求,客户端根据接口描述直接调用需要的服务。客户端和服务器可以分别使用gRPC支持的不同语言实现

图片

  • 安装

protobuf Golang插件

执行完成后会在GOPATH/bin目录下生成 protoc-gen-go工具,在编译.proto文件时,protoc命令需要用到此插件

[关于protobuf语法和基本使用]()

go get -u github.com/golang/protobuf/{proto,protoc-gen-go}

grpc-go golang第三方库下载(需要翻墙 用ss配置http代理完成终端翻墙))

go get -u google.golang.org/grpc
  • gPRC列子 hello gRPC

流程

1.编写.proto描述文件 
2.编译生成.pb.go文件 
3.客户端实现约定的接口并提供服务 
4.客户端按照约定调用方法请求服务 

目录结构


$GOPATH/src/go_demo/
23gPRC/
|—— hello/
 |—— client/
 |—— main.go // 客户端
 |—— server/
 |—— main.go // 服务端
|—— proto/
 |—— hello.proto // proto描述文件
 |—— hello.pb.go // proto编译后文件
  • 代码示例
proto rpc服务描述文件
syntax = "proto3"; //指定proto版本
package proto;
//定义请求结构
message HelloRequest{
 string name=1;
}
//定义响应结构
message HelloReply{
 string message=1;
}
//定义Hello服务
service Hello{
 //定义服务中的方法
 rpc SayHello(HelloRequest)returns (HelloReply){}
}
protoc -I . --go_out=plugins=grpc:. ./hello.proto
hello.proto文件中定义了一个Hello Service 该服务包含了一个SayHello方法 同时声明了HelloRequest和HelloReply消息结构
用于请求和响应。客户端使用HelloRequest参数调用SayHello方法请求服务端 服务端响应HelloReply消息
根据hello.proto文件编译生成Golang源文件 hello.pb.go
源文件中包含消息传递的请求和响应结构。服务端注册对象的方法 创建客户端 以及调用服务端方法
服务器端代码

package main
import (
 "fmt"
 pb "go_demo/23gRPC/proto"
 "net"
 "golang.org/x/net/context"
 "google.golang.org/grpc"
)
const (
 //gRPC服务地址
 Address = "127.0.0.1:50052"
)
//定义一个helloServer并实现约定的接口
type helloService struct{}
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
 resp := new(pb.HelloReply)
 resp.Message = "hello" + in.Name + "."
 return resp, nil
}
var HelloServer = helloService{}
func main() {
 listen, err := net.Listen("tcp", Address)
 if err != nil {
 fmt.Printf("failed to listen:%v", err)
 }
 //实现gRPC Server
 s := grpc.NewServer()
 //注册helloServer为客户端提供服务
 pb.RegisterHelloServer(s, HelloServer) //内部调用了s.RegisterServer()
 fmt.Println("Listen on" + Address)
 s.Serve(listen)
}
go run main.go
Listen on127.0.0.1:50052
客户端代码

package main
import (
 "fmt"
 pb "go_demo/23gRPC/proto"
 "golang.org/x/net/context"
 "google.golang.org/grpc"
)
const (
 Address = "127.0.0.1:50052"
)
func main() {
 //连接gRPC服务器
 conn, err := grpc.Dial(Address, grpc.WithInsecure())
 if err != nil {
 fmt.Println(err)
 }
 defer conn.Close()
 //初始化客户端
 c := pb.NewHelloClient(conn)
 //调用方法
 reqBody := new(pb.HelloRequest)
 reqBody.Name = "gRPC"
 r, err := c.SayHello(context.Background(), reqBody)
 if err != nil {
 fmt.Println(err)
 }
 fmt.Println(r.Message)
}
go run main.go
hello gRpc

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:xiaodulala

查看原文:Golang中的RPC和gRPC

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
16102 次点击 ∙ 1 赞
被以下专栏收入,发现更多相似内容
3 回复 | 直到 2021年05月06日 16:09:04
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏