什么是GRPC?
rpc框架一般基于tcp或者http协议实现。基于http的rpc框架有许多优点,HTTP/1.x协议简单明了,是目前最流行的应用层协议,有着非常成熟且完善的各种基础设施,如负载均衡、监控、代理等,适用性广泛,各个设备系统均有实现。但是缺点也很明显,就是HTTP/1.x采用的是文本协议,解析速度慢,带宽占用高。而且request/response的通信方法导致整体效率不高。gRPC基于HTTP2协议,HTTP2 使得grpc 能够更好的适用于移动客户端和服务端通信的使用场景,并且连接多路复用也保证了RPC 的效率。grpc 的协议设计上很好的使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。
图片来自网络,从上图和文档中我们可以了解到,用gRPC来进行远程服务调用就仅仅需要gRPC Stub(Client)用Proto Request向远方的gRPC Server发起服务调用,然后远方的gRPC Server通过Proto Response(s)将调用结果返回给gRPC Stub。
话不多说,在上代码之前,贴出项目最终的一个样子!
好,接下来我们还有一些东西需要提前准备,protoc.exe 以及protoc-gen-go.exe两个工具:
- protoc.exe直接下载就好了(传送门)
- protoc-gen-go这个需要安装依赖(
需要将其放到环境变量中
):go get github.com/golang/protobuf/protoc-gen-go
接下来就到了代码环节了。
创建proto文件:
syntax = "proto3";
package services;
option go_package = "./protos;protos";
// 定义发送请求信息
message UserRequest{
// 定义发送的参数
// 参数类型 参数名 标识号(不可重复) 1 表示确定顺序
string user_id = 1;
}
// 定义响应信息返回信息
message UserResponse{
string user_id = 1;
int32 score = 2;
int32 age = 3;
string user_name = 4;
}
service UserService{
rpc GetUserInfo (UserRequest) returns (UserResponse){};
}
Protobuf生成Go代码指南:https://juejin.cn/post/6844903944511029262
编译proto文件:
.\protoc.exe --go_out=plugins=grpc:. .\protos\user.proto
protoc.exe是在与main.go同一级目录下的,我这是没有放在环境变量中的情况,使用的时候需要注意相对路径。
编译完成后则会在protos文件夹下新增一个user.pb.go文件。里面有很多东西我们先不用管,只需要先实现我们定义的rpc方法。
服务端(main.go):
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
pb "grpcserver/protos"
"log"
"net"
)
const DefaultUserId = "11111"
var userMap = map[string]User{
DefaultUserId: {
UserId: DefaultUserId,
UserName: "user_name_1",
Score: 101,
Age: 10,
},
}
type UserInfoService struct{}
type User struct {
UserId string
UserName string
Score int32
Age int32
}
func (c *UserInfoService) GetUserInfo(ctx context.Context, in *pb.UserRequest) (*pb.UserResponse, error) {
response := new(pb.UserResponse)
userId := in.GetUserId()
fmt.Println("user_id: ", userId)
if userId != "" {
user, ok := userMap[userId]
if ok{
response.UserId = user.UserId
response.UserName = user.UserName
response.Age = user.Age
response.Score = user.Score
}
}
return response, nil
}
func main() {
// 监听本地端口// Address 监听地址// Network 网络通信协议
listener, err := net.Listen("tcp", ":8000")
if err != nil {
log.Fatalf("net.Listen err: %v", err)
}
server := grpc.NewServer()
// 在gRPC服务器注册我们的服务
pb.RegisterUserServiceServer(server, &UserInfoService{})
err = server.Serve(listener)
if err != nil {
log.Fatalf("server err: %v", err)
}
}
客户端(client.go):
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
pb "grpcserver/protos"
"log"
)
const (
Address = ":8000"
)
func main() {
// 连接服务器
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
log.Fatalf("grpc dail err: %v", err)
return
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// 创建发送结构体
req := pb.UserRequest{
UserId: "11111",
}
// 调用我们的服务(GetUserInfo方法)
// 同时传入了一个 context.Context ,在有需要时可以让我们改变GRPC的行为,比如超时/取消一个正在运行的RPC
res, err := client.GetUserInfo(context.Background(), &req)
if err != nil {
log.Fatalf("call getUserInfo err: %v", err)
return
}
fmt.Println(res)
}
先启动服务端,然后在启动客户端,最终结果如下: