相关链接: gRPC官网:https://grpc.io/
RPC框架一般用于后端服务数据通信比较大很频繁的场景。通信少时RPC和HTTP都可以用,怎么简单怎么来,满足需要就可。GRPC面向移动应用,当然它是通用的,后端也可以用,常用于分布式项目中,比较强大,支持双向通信。
先来个简单的rpc示例代码,再来个简单的grpc示例代码。
一、RPC
RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以在一台机器上,也可以在不同的机器上。程序员使用时,就像调用本地程序一样,无需关注内部的实现细节。
RPC 即远程过程调用,很简单的概念,就像调用本地服务(方法)一样调用服务器的服务(方法) 。
在Go中,标准库提供的net/rpc 包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。net/rpc 包允许 RPC客户端 程序通过网络或者其他IO连接调用一个远程对象的公开方法(该方法必须是外部可访问即首字母大写),在RPC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。
1、GO语言实现rpc服务端
package main
import (
"errors"
"fmt"
"log"
"net/http"
"net/rpc"
)
func main() {
fmt.Println("rpc服务端启动......")
arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP()
if err := http.ListenAndServe(":1234", nil); err != nil {
log.Fatal("serve error:", err)
}
}
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (t *Arith) Divide(args *Args, quo *Quotient) error {
if args.B == 0 {
return errors.New("divide by 0")
}
quo.Quo = args.A / args.B
quo.Rem = args.A % args.B
return nil
}
2、Go语言实现rpc客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
fmt.Println("rpc客户端启动......")
client, err := rpc.DialHTTP("tcp", ":1234")
if err != nil {
log.Fatal("dialing:", err)
}
args := &Args{7, 8}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("Multiply error:", err)
}
fmt.Printf("Multiply: %d*%d=%d\n", args.A, args.B, reply)
args = &Args{15, 6}
var quo Quotient
err = client.Call("Arith.Divide", args, &quo)
if err != nil {
log.Fatal("Divide error:", err)
}
fmt.Printf("Divide: %d/%d=%d...%d\n", args.A, args.B, quo.Quo, quo.Rem)
}
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
二、GRPC
gRPC是谷歌开源的一个高性能、开源、通用的RPC框架。基于HTTP/2协议标准设计开发,默认采用Protocol Buffers数据序列化协议Protocol Buffers基本语法,支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库,面向移动和 HTTP2 设计。【基于HTTP2】
在服务端,服务端实现这个接口并运行一个 gRPC 服务器来处理客户端调用。 在客户端,客户端有一个存根(在某些语言中仅称为客户端),它提供与服务器相同的方法。
gRPC 客户端和服务器可以在各种环境中运行和相互通信,并且可以用任何 gRPC 支持的语言编写【主流语言都支持】。
1、protobuf
ProtoBuf 【高效的二进制协议】具有强大的IDL(interface description language,接口描述语言)和相关工具集(主要是protoc)。用户写好.proto 描述文件后,protoc可以将其编译成众多语言的接口代码。
- 数据交互格式: protobuf
- 通信方式: 最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现
- 核心库: 在HTTP/2协议之上又构建了针对Go语言的gRPC核心库
- Stub: 应用程序Application 通过 gRPC插件 生产的 Stub代码 和 gRPC核心库 通信,也可以直接和gRPC核心库通信。
2、gRPC 支持 4 类服务方法
a、简单 RPC(Simple RPC)
客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。即标准RPC通信。
rpc SayHello(HelloRequest) returns (HelloResponse){}
b、服务端数据流式 RPC (Server-side streaming RPC)
客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
c、客户端数据流式 RPC(Client-side streaming RPC)
客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。典型的例子是物联网终端向服务器报送数据。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {}
d、双向数据流式 RPC(Bidirectional streaming RPC)
两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。典型的例子就是聊天应用。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){}
3、准备工作
a、安装go
下载安装包:https://golang.google.cn/dl/
解压安装包到安装目录,配置安装目录到环境变量【这一步如果不会,去百度】
验证:
C:\Users\DeskTop>go version
go version go1.17 windows/amd64
b、安装 protobuf
protobuf 是一个跨平台和跨语言的数据结构存储和传输的便利工具。
官方文档地址:https://developers.google.com/protocol-buffers/ GitHub 地址:https://github.com/protocolbuffers/protobuf Releases 下载地址:https://github.com/protocolbuffers/protobuf/releases
解压安装包到安装目录,配置安装目录到环境变量【这一步如果不会,去百度】
验证:
C:\Users\DeskTop>protoc --version
libprotoc 3.17.3
c、安装 Go protobuf 插件
go get -u github.com/golang/protobuf/proto
go get -u github.com/golang/protobuf/protoc-gen-go
d、安装 grpc-go
go get -u google.golang.org/grpc
- 编写服务端
.proto 文件 - 生成服务端
.pb.go 文件并同步给客户端 - 编写服务端提供接口的代码
- 编写客户端调用接口的代码
目录结构
├─ hello -- 代码根目录
│ ├─ go_client
│ ├── main.go
│ ├── proto
│ ├── hello
│ ├── hello.pb.go
│ ├─ go_server
│ ├── main.go
│ ├── controller
│ ├── hello_controller
│ ├── hello_server.go
│ ├── proto
│ ├── hello
│ ├── hello.pb.go
│ ├── hello.proto
这样创建目录是为了 go_client 和 go_server 后期可以拆成两个项目。
4、Go 实现 gRPC 的服务端
编写服务端 hello.proto 文件
syntax = "proto3";
package hello;
option go_package = "./;hello";
service Hello {
rpc SayHello(HelloRequest) returns (HelloResponse) {}
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){}
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
了解更多 Protobuf 语法,请查看:
https://developers.google.com/protocol-buffers/
生成服务端 .pb.go
protoc -I . --go_out=plugins=grpc:. ./hello.proto
同时将生成的 hello.pb.go 复制到客户端一份。
查看更多命令参数,执行 protoc,查看 OPTION。
package hello_controller
import (
"fmt"
"golang.org/x/net/context"
"hello/go_server/proto/hello"
)
type HelloController struct{}
func (h *HelloController) SayHello(ctx context.Context, in *hello.HelloRequest) (*hello.HelloResponse, error) {
return &hello.HelloResponse{Message : fmt.Sprintf("%s", in.Name)}, nil
}
func (h *HelloController) LotsOfReplies(in *hello.HelloRequest, stream hello.Hello_LotsOfRepliesServer) error {
for i := 0; i < 10; i++ {
stream.Send(&hello.HelloResponse{Message : fmt.Sprintf("%s %s %d", in.Name, "Reply", i)})
}
return nil
}
package main
import (
"log"
"net"
"hello/go_server/proto/hello"
"hello/go_server/controller/hello_controller"
"google.golang.org/grpc"
)
const (
Address = "0.0.0.0:9090"
)
func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
hello.RegisterHelloServer(s, &hello_controller.HelloController{})
log.Println("Listen on " + Address)
if err := s.Serve(listen); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
运行:
go run main.go
2019/07/28 17:51:20 Listen on 0.0.0.0:9090
5、Go 实现 gRPC 的客户端
package main
import (
"hello/go_client/proto/hello"
"io"
"log"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
Address = "0.0.0.0:9090"
)
func main() {
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
c := hello.NewHelloClient(conn)
res, err := c.SayHello(context.Background(), &hello.HelloRequest{Name: "Hello World"})
if err != nil {
log.Fatalln(err)
}
log.Println(res.Message)
stream, err := c.LotsOfReplies(context.Background(),&hello.HelloRequest{Name: "Hello World"})
if err != nil {
log.Fatalln(err)
}
for {
res, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Printf("stream.Recv: %v", err)
}
log.Printf("%s", res.Message)
}
}
运行:
go run main.go
2019/07/28 17:58:13 Hello World
2019/07/28 17:58:13 Hello World Reply 0
2019/07/28 17:58:13 Hello World Reply 1
2019/07/28 17:58:13 Hello World Reply 2
2019/07/28 17:58:13 Hello World Reply 3
2019/07/28 17:58:13 Hello World Reply 4
2019/07/28 17:58:13 Hello World Reply 5
2019/07/28 17:58:13 Hello World Reply 6
2019/07/28 17:58:13 Hello World Reply 7
2019/07/28 17:58:13 Hello World Reply 8
2019/07/28 17:58:13 Hello World Reply 9
|