grpc使用及意义

远程过程调用 RPC

RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC假定了某些协议的存在,例如TCP/UDP等,为通信程序之间携带信息数据。在OSI七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。

为何使用grpc

对比http版本的不同来做解释

protobuf的使用及规范

protobuf介绍

protobuf是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。protobuf是 二进制数据格式,需要编码和解码。 数据本身不具有可读性,因此只能反序列化之后才能得到真正可读的数据。

  • 优势
    • 序列化后体积相比json和xml很小,适合网络传输
    • 支持跨语言平台
    • 消息格式升级和兼容性还不错
    • 序列化和反序列化速度很快

字段规则

  • required:消息体中的必填字段,不设置会导致编解码异常
  • optional:消息题中可选字段
  • repeated:消息体中可重复字段,重复的值的顺序会被保留,在go中重复的会被定义为切片

示例 golang

//HelloReply协议内容
message HelloReply {
  string name = 1;
  string message = 2;
  optional string ip = 3;
  repeated string port = 4;
}
//HelloReply协议内容
type HelloReply struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name    string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	Message string   `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
	Ip      *string  `protobuf:"bytes,3,opt,name=ip,proto3,oneof" json:"ip,omitempty"`
	Port    []string `protobuf:"bytes,4,rep,name=port,proto3" json:"port,omitempty"`
}

默认值

类型默认值
boolfalse
整型0
string空字符串 “”
枚举第一个枚举元素的值,因为protobuf3强制要求第一个枚举元素的值必须是0,所以枚举的默认值就是0
message不是null,而是DEFAULT_INSTANCE

标识号

标识号:在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[0, 2^29-1]范围内的一个整数

示例

//HelloReply协议内容
message HelloReply {
  string name = 1; // 1即标识号
  string message = 2;
  optional string ip = 3;
  repeated string port = 4;
}

嵌套消息

message SubHello {
  string code = 1;
  string msg = 2;
}

//HelloReply协议内容
message HelloReply {
  string name = 1;
  string message = 2;
  optional string ip = 3;
  repeated string port = 4;
  repeated SubHello http = 5;
}
type HelloReply struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Name    string      `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	Message string      `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
	Ip      *string     `protobuf:"bytes,3,opt,name=ip,proto3,oneof" json:"ip,omitempty"`
	Port    []string    `protobuf:"bytes,4,rep,name=port,proto3" json:"port,omitempty"`
	Http    []*SubHello `protobuf:"bytes,5,rep,name=http,proto3" json:"http,omitempty"`
}

openssl生成签名证书

生成普通证书

  • 生成服务器私钥
 openssl genrsa -des3 -out server.key 4096
  • 生成证书请求文件csr(windows)或者pem(linux)
 openssl req -new -key server.key -out server.pem

Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:cn # 国家
State or Province Name (full name) []:beijing # 城市
Locality Name (eg, city) []:beijing # 城市
Organization Name (eg, company) []:huidev
Organizational Unit Name (eg, section) []:ihuidev
Common Name (eg, fully qualified host name) []:devhui.org # 服务器域名
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
  • 删除key的密码生成一个新的密钥
openssl rsa -in server.key -out server_no_password.key
  • 生成crt证书
openssl x509 -req -days 365 -in server.pem -signkey server.key -out server.crt

至此普通证书生成完成

生成CA签名证书

  • 生成ca私钥
 openssl genrsa -des3 -out ca.key 4096
  • 生成证书请求文件csr(windows)或者pem(linux)
 openssl req -new -x509 -days 3650 -key ca.key -out ca.pem

Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:cn # 国家
State or Province Name (full name) []:beijing # 城市
Locality Name (eg, city) []:beijing # 城市
Organization Name (eg, company) []:huidev
Organizational Unit Name (eg, section) []:ihuidev
Common Name (eg, fully qualified host name) []:devhui.org # 服务器域名
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
  • 生成server密钥
openssl genrsa -des3 -out server.key 4096
  • 生成证书请求文件csr(windows)或者pem(linux)
 openssl req -new -x509 -days 3650 -key server.key -out server.pem

Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:cn # 国家
State or Province Name (full name) []:beijing # 城市
Locality Name (eg, city) []:beijing # 城市
Organization Name (eg, company) []:huidev
Organizational Unit Name (eg, section) []:ihuidev
Common Name (eg, fully qualified host name) []:devhui.org # 服务器域名
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

注意:两次的域名一定要写成一致的

  • 生成crt证书
openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in server.pem -out server.crt

至此普通证书生成完成

grpc的四大模式

gRPC中文文档
gRPC官方文档

生成proto的go文件命令

protoc --go_out=plugins=grpc,paths=source_relative:. authentication.proto

这个命令只会生成xxx.pb.go的go文件

官方为我们提供的是

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative authentication.proto

会生成grpc的框架的go文件 如:xxx_grpc.pb.go

问题:

新版本的protoc-gen-go不支持grpc服务的生成,需要借助protoc-gen-go-grpc生成grpc服务接口,但会出现一个问题
missing xxxpb.mustEmbedUnimplementedxxxServer method

解决方法

1、使用命令行的时候protoc --go-grpc_out=require_unimplemented_servers=false
2、或者server结构体中匿名嵌入xxxpb.UnimplementedxxxServer

说明:第一种方案不推荐,官方的解释是为了保持前后兼容
具体可看官方文档
protoc-gen-go-grpc官方readme

普通rpc

一个简单的RPC,客户端使用存根发送请求到服务器并等待响应返回,就像平常的函数调用一样

示例:

server.go

package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"net"
)

type UserAuth struct {
	authenticationpb.UnimplementedBlogServiceAuthServer
}

func (user *UserAuth) UserLogin(context.Context, *authenticationpb.LoginRequest) (*authenticationpb.LoginResponse, error) {
	return &authenticationpb.LoginResponse{Success: "ok"}, nil
}

func main() {
	//协议类型以及ip,port
	listen, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("正在运行grpc服务...")
	//定义一个rpc的server
	server := grpc.NewServer()
	//注册服务,相当与注册SayHello接口
	authenticationpb.RegisterBlogServiceAuthServer(server, &UserAuth{})
	//进行映射绑定
	reflection.Register(server)

	//启动服务
	err = server.Serve(listen)
	if err != nil {
		fmt.Println(err)
	}
}

authentication.proto

syntax = "proto3";

package authentication;
option go_package = ".;authenticationpb";  // authenticationpb代表包名。强烈建议包名的写法是name+pb的形式

service BlogServiceAuth{
  rpc UserLogin (LoginRequest) returns(LoginResponse) {}
}


message LoginRequest{
  string UserUid = 1;
  string UserNickName = 2;
}

message LoginResponse{
  string Success = 1;
}

client.go

package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"google.golang.org/grpc"
	"time"
)

func main() {
	//创建一个grpc的连接
	grpcConn, err := grpc.Dial("127.0.0.1"+":8002", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		return
	}

	//创建grpc的client
	client := authenticationpb.NewBlogServiceAuthClient(grpcConn)
	//设置超时时间
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	//调用rpc方法,获得流接口
	res, err := client.UserLogin(ctx, &authenticationpb.LoginRequest{UserUid: "23eu89eqfuiqe", UserNickName: "高姿清高"})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Printf("接受到的值是 %v", res)

}

服务端流式rpc

一个服务端流式RPC,客户端放请求到服务器,拿到一个流去读取返回的消息序列。客户端读取返回的流,直到里面没有任何消息。

server.go

package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"log"
	"net"
	"net/http"
)

type UserAuth struct {
	authenticationpb.UnimplementedBlogServiceAuthServer
}

func (user *UserAuth) UserLogin(c context.Context, auth *authenticationpb.LoginRequest) (*authenticationpb.LoginResponse, error) {
	fmt.Printf("request %+v \n", auth)
	return &authenticationpb.LoginResponse{Success: auth.UserNickName}, nil
}

func (user *UserAuth) UserLoginStream(in *authenticationpb.RPCUsersLoginRequest, stream authenticationpb.BlogServiceAuth_UserLoginStreamServer) (error) {
	for i := 0; i < 10; i++ {
		if i%2 == 0 {
			err := stream.Send(&authenticationpb.RPCUsersLoginResponse{
				Username: "dopowd",
				Address:  "doit",
				Age:      10,
			})
			if err != nil {
				return nil
			}
		}
	}
	return nil
}

func main() {
	//协议类型以及ip,port
	listen, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("正在运行grpc服务...")
	//定义一个rpc的server
	server := grpc.NewServer()
	//注册服务,相当与注册SayHello接口
	authenticationpb.RegisterBlogServiceAuthServer(server, &UserAuth{})
	//进行映射绑定
	reflection.Register(server)

	//启动服务
	err = server.Serve(listen)
	if err != nil {
		fmt.Println(err)
	}

}

authentication.proto

syntax = "proto3";

package authentication;
option go_package = ".;authenticationpb"; // authenticationpb代表包名。强烈建议包名的写法是name+pb的形式

service BlogServiceAuth{
  rpc UserLogin (LoginRequest) returns(LoginResponse) {}
  rpc UserLoginStream(RPCUsersLoginRequest) returns(stream RPCUsersLoginResponse){}
}


message LoginRequest{
  string UserUid = 1;
  string UserNickName = 2;
}

message LoginResponse{
  string Success = 1;
}

message RPCUsersLoginRequest{
  string username = 1;
  int32 age = 2;
  string address = 3;
}

message RPCUsersLoginResponse{
  string username = 1;
  int32 age = 2;
  string address = 3;
}

client

package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"google.golang.org/grpc"
	"io"
	"log"
	"time"
)

func main() {
	conn, err := grpc.Dial(":8002", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		return
	}

	client := authenticationpb.NewBlogServiceAuthClient(conn)

	ctx, cancle := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancle()

	stream, err := client.UserLoginStream(ctx, &authenticationpb.RPCUsersLoginRequest{
		Age: 10,
		Username: "asdas",
		Address: "2ef4dad",
	})
	if err != nil {
		log.Fatal(err)
	}
	users := make([]*authenticationpb.RPCUsersLoginResponse, 0)
	for true {
		res, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		users = append(users, res)
	}

	fmt.Printf("获取到的用户列表是 %+v", users)
}

客户端流式rpc

客户端流式RPC,客户端卸乳一个消息序列并将其发送到服务器,同样也是使用流,一旦客户端完成写入消息,他等待服务器完成读取返回他的响应。

server.go

package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"io"
	"log"
	"net"
	"net/http"
)

type UserAuth struct {
	authenticationpb.UnimplementedBlogServiceAuthServer
}

func (user *UserAuth) UserLogin(c context.Context, auth *authenticationpb.LoginRequest) (*authenticationpb.LoginResponse, error) {
	fmt.Printf("request %+v \n", auth)
	return &authenticationpb.LoginResponse{Success: auth.UserNickName}, nil
}

func (user *UserAuth) UserLoginStream(in *authenticationpb.RPCUsersLoginRequest, stream authenticationpb.BlogServiceAuth_UserLoginStreamServer) error {
	for i := 0; i < 10; i++ {
		if i%2 == 0 {
			err := stream.Send(&authenticationpb.RPCUsersLoginResponse{
				Username: "dopowd",
				Address:  "doit",
				Age:      10,
			})
			if err != nil {
				return nil
			}
		}
	}
	return nil
}

func (user *UserAuth) ClientUserLoginStream(stream authenticationpb.BlogServiceAuth_ClientUserLoginStreamServer) error {
	users := make([]*authenticationpb.RPCUsersLoginRequest, 0)
	for true {
		request, err := stream.Recv()
		if err == io.EOF {
			reponse := &authenticationpb.RPCUsersLoginResponse{
				Username: "高姿清高",
				Age:      20,
				Address:  "北京朝颜群众",
			}

			return stream.SendAndClose(reponse)
		}

		if err != nil {
			return err
		}
		users = append(users, request)
	}
	return nil
}

func main() {
	//协议类型以及ip,port
	listen, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("正在运行grpc服务...")
	//定义一个rpc的server
	server := grpc.NewServer()
	//注册服务,相当与注册SayHello接口
	authenticationpb.RegisterBlogServiceAuthServer(server, &UserAuth{})
	//进行映射绑定
	reflection.Register(server)

	//启动服务
	err = server.Serve(listen)
	if err != nil {
		fmt.Println(err)
	}
}

authentication.proto

syntax = "proto3";

package authentication;
option go_package = ".;authenticationpb"; // authenticationpb代表包名。强烈建议包名的写法是name+pb的形式

service BlogServiceAuth{
  rpc UserLogin (LoginRequest) returns(LoginResponse) {}
  rpc UserLoginStream(RPCUsersLoginRequest) returns(stream RPCUsersLoginResponse){}
  rpc ClientUserLoginStream(stream RPCUsersLoginRequest) returns (RPCUsersLoginResponse){}
}


message LoginRequest{
  string UserUid = 1;
  string UserNickName = 2;
}

message LoginResponse{
  string Success = 1;
}

message RPCUsersLoginRequest{
  string username = 1;
  int32 age = 2;
  string address = 3;
}

message RPCUsersLoginResponse{
  string username = 1;
  int32 age = 2;
  string address = 3;
}

client.go

package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"google.golang.org/grpc"
	"io"
	"log"
	"time"
)

func main() {
	conn, err := grpc.Dial(":8002", grpc.WithInsecure())
	if err != nil {
		fmt.Println(err)
		return
	}

	client := authenticationpb.NewBlogServiceAuthClient(conn)

	ctx, cancle := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancle()

	stream, err := client.ClientUserLoginStream(ctx)
	if err != nil {
		log.Fatal(err)
	}

	for i := 1; i < 10; i++ {
		err = stream.Send(&authenticationpb.RPCUsersLoginRequest{
			Username: "猪八戒背媳妇",
			Address: "高老庄",
			Age: int32(i),
		})
		if err != nil{
			fmt.Println(err)
		}
	}

	response, err := stream.CloseAndRecv()
	if err != nil{
		fmt.Println(err)
	}


	fmt.Printf("服务器完成接受并返回消息 %+v", response)
}


双向流式rpc

双向流式RPC是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如,服务器可以在写入响应前等待接受所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。

使用GRPC-GATEWAY代理REST API请求

  • server.go
package main

import (
	"context"
	"fmt"
	authenticationpb "gitee.com/wlzhazhahui/gin-blog/blog_grpc/grpc_proto"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
	"log"
	"net"
	"net/http"
)

type UserAuth struct {
	authenticationpb.UnimplementedBlogServiceAuthServer
}

func (user *UserAuth) UserLogin(c context.Context, auth *authenticationpb.LoginRequest) (*authenticationpb.LoginResponse, error) {
	return &authenticationpb.LoginResponse{Success: auth.UserNickName}, nil
}

func main() {
	//协议类型以及ip,port
	go startGRPCGateway()
	listen, err := net.Listen("tcp", ":8002")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("正在运行grpc服务...")
	//定义一个rpc的server
	server := grpc.NewServer()
	//注册服务,相当与注册SayHello接口
	authenticationpb.RegisterBlogServiceAuthServer(server, &UserAuth{})
	//进行映射绑定
	reflection.Register(server)

	//启动服务
	err = server.Serve(listen)
	if err != nil {
		fmt.Println(err)
	}
}

func startGRPCGateway() {
	c := context.Background()
	c, cancle := context.WithCancel(c)
	defer cancle()
	Mux := runtime.NewServeMux()
	err := authenticationpb.RegisterBlogServiceAuthHandlerFromEndpoint(c, Mux, ":8002", []grpc.DialOption{grpc.WithInsecure()})
	if err != nil {
		log.Fatalf("cannot start grpc gateway: %v", err)
	}
	err = http.ListenAndServe(":8080", Mux)
	if err != nil {
		log.Fatalf("cannot start grpc gateway: %v", err)
	}

}

  • authentication.proto
syntax = "proto3";

package authentication;
option go_package = ".;authenticationpb"; // authenticationpb代表包名。强烈建议包名的写法是name+pb的形式

service BlogServiceAuth{
  rpc UserLogin (LoginRequest) returns(LoginResponse) {}
}


message LoginRequest{
  string UserUid = 1;
  string UserNickName = 2;
}

message LoginResponse{
  string Success = 1;
}

  • 生成xx.gw.go文件

gen.sh

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative authentication.proto
protoc --grpc-gateway_out=paths=source_relative,grpc_api_configuration=authentication.yaml:. authentication.proto
  • 编写grpc-gateway的yaml文件 authentication.yaml

注意:

yaml文件的格式要求很严格

比如:缩进要求

** 运行上面脚本生成gw.go文件时,可能会出 mapping values are not allowed in this context,这个问题是因为我的selector这行和post没有对齐导致**

type: google.api.Service
config_version: 3

http:
    rules:
    - selector: authentication.BlogServiceAuth.UserLogin 
      post: /auth //缩进和上行保持一致
      body: "*"

这个服务启动,我们就可以通过RESTAPI访问RPC

如果您觉得文章对您有所帮助,可以请囊中羞涩的博主吃个鸡腿饭,万分感谢。愿每一个来到这里的人生活幸福美满。

微信赞赏
在这里插入图片描述

支付宝赞赏
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值