远程过程调用 RPC
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC假定了某些协议的存在,例如TCP/UDP等,为通信程序之间携带信息数据。在OSI七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
为何使用grpc
对比http版本的不同来做解释
protobuf的使用及规范
protobuf介绍
protobuf是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。protobuf是 二进制数据格式,需要编码和解码。 数据本身不具有可读性,因此只能反序列化之后才能得到真正可读的数据。
- 优势
- 序列化后体积相比json和xml很小,适合网络传输
- 支持跨语言平台
- 消息格式升级和兼容性还不错
- 序列化和反序列化速度很快
字段规则
- required:消息体中的必填字段,不设置会导致编解码异常
- optional:消息题中可选字段
- repeated:消息体中可重复字段,重复的值的顺序会被保留,在go中重复的会被定义为切片
示例 golang
message HelloReply {
string name = 1;
string message = 2;
optional string ip = 3;
repeated string port = 4;
}
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"`
}
默认值
类型 | 默认值 |
---|
bool | false | 整型 | 0 | string | 空字符串 “” | 枚举 | 第一个枚举元素的值,因为protobuf3强制要求第一个枚举元素的值必须是0,所以枚举的默认值就是0 | message | 不是null,而是DEFAULT_INSTANCE |
标识号
标识号:在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是[0, 2^29-1]范围内的一个整数
示例
message HelloReply {
string name = 1;
string message = 2;
optional string ip = 3;
repeated string port = 4;
}
嵌套消息
message SubHello {
string code = 1;
string msg = 2;
}
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 []:
openssl rsa -in server.key -out server_no_password.key
openssl x509 -req -days 365 -in server.pem -signkey server.key -out server.crt
至此普通证书生成完成
生成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 []:
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 []:
注意:两次的域名一定要写成一致的
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() {
listen, err := net.Listen("tcp", ":8002")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("正在运行grpc服务...")
server := grpc.NewServer()
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";
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() {
grpcConn, err := grpc.Dial("127.0.0.1"+":8002", grpc.WithInsecure())
if err != nil {
fmt.Println(err)
return
}
client := authenticationpb.NewBlogServiceAuthClient(grpcConn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
res, err := client.UserLogin(ctx, &authenticationpb.LoginRequest{UserUid: "23eu89eqfuiqe", UserNickName: "高姿清高"})
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("接受到的值是 %v", res)
}
服务端流式rpc
一个服务端流式RPC,客户端放请求到服务器,拿到一个流去读取返回的消息序列。客户端读取返回的流,直到里面没有任何消息。
客户端流式rpc
客户端流式RPC,客户端卸乳一个消息序列并将其发送到服务器,同样也是使用流,一旦客户端完成写入消息,他等待服务器完成读取返回他的响应。
双向流式rpc
双向流式RPC是双方使用读写流去发送一个消息序列。两个流独立操作,因此客户端和服务器可以以任意喜欢的顺序读写:比如,服务器可以在写入响应前等待接受所有的客户端消息,或者可以交替的读取和写入消息,或者其他读写的组合。
使用GRPC-GATEWAY代理REST API请求
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() {
go startGRPCGateway()
listen, err := net.Listen("tcp", ":8002")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("正在运行grpc服务...")
server := grpc.NewServer()
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)
}
}
syntax = "proto3";
package authentication;
option go_package = ".;authenticationpb";
service BlogServiceAuth{
rpc UserLogin (LoginRequest) returns(LoginResponse) {}
}
message LoginRequest{
string UserUid = 1;
string UserNickName = 2;
}
message LoginResponse{
string Success = 1;
}
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
|