RPC 入门(一)
在学习 gRPC
之前,先学习 protobufu
协议,简单的来理解,我们可以使用他来定义 消息
和 服务
。然后你只需要实现服务即可,剩下的东西,gRPC
会帮你自动完成。
RPC 协议
使用一个 go
语言只带的 net/rpc
包,自定义实现一个 rpc
调用,代码目录如下
复制├── server
│ ├── go.mod
│ ├── main.go
│ └── models
│ └── add.go
└── client
├── go.mod
├── go.work
└── main.go
创建一个 rpc 的服务端
package models
type Args struct {
X, Y int
}
type ServiceA struct{}
func (s *ServiceA) Add(args *Args, reply *int) error {
*reply = args.X + args.Y
return nil
}
package main
import (
"d1/models"
"log"
"net"
"net/http"
"net/rpc"
)
func main() {
service := new(models.ServiceA)
rpc.Register(service) //注册 rcp 协议
rpc.HandleHTTP() // 使用 http 协议发送
listen, err := net.Listen("tcp", ":5001")
if err != nil {
log.Fatalf("rpc listen err: %v", err)
}
log.Println("开始启动 rpc 服务端.......")
http.Serve(listen, nil)
}
创建一个服务端
package main
import (
"d1/models"
"log"
"net/rpc"
)
func main() {
rpcClient, err := rpc.DialHTTP("tcp", "127.0.0.1:5001")
if err != nil {
log.Fatalf("rcp client dial fail: %v", err)
}
args := &models.Args{X: 20, Y: 30}
var reply int
if err = rpcClient.Call("ServiceA.Add", args, &reply); err != nil {
log.Fatalf("rpc 远程调用客户端失败: %v", err)
}
log.Printf("rpc 代码同步调用成功: %d + %d = %d", args.X, args.Y, reply)
// 测试异步调用
args.X, args.Y = 70, 50
asyncCall := rpcClient.Go("ServiceA.Add", args, &reply, nil)
replyCall := <-asyncCall.Done
if err = replyCall.Error; err != nil {
log.Printf("rpc 异步调用错误: %v", err)
}
log.Printf("rpc 异步调用成功 %d + %d = %d", args.X, args.Y, reply)
}
结果如下:
解释
- 上面的
rpc
调用是依赖于http
协议传输的,当然也可使用tcp
协议实现;现在应用最广泛的grpc
就是使用http2
实现的 - 在
server
服务中modeles
单独拎出来,是方便使用go work
机制,在另一个服务直接引用,而无需重新定义 rpc
服务调用分为同步调用
和异步调用
两种
protobufu 协议
protobuf
协议,可以适用于十几种开发语言,并且允许你使用同一种框架,每秒支持百万级以上的 RPC 调用
Linux 安装 protoc 工具
prootc 工具,核心是使用 c++
写的,常见的安装方式,有三种
- 使用
ubuntu
系统自带的包管理工具 apt 安装:sudo apt install protobuf-compiler
- 下载
c++
源码编译安装 - 下载官网编译好的二进制文件安装
https://github.com/protocolbuffers/protobuf/releases
以上三种方法,第一种最简单,但是安装的往往不是最新版本,第二种过于复杂,本文推荐使用第三种方式安装部署
使用 wget https://github.com/protocolbuffers/protobuf/releases/download/v28.3/protoc-28.3-linux-x86_64.zip 下载安装包并解压,将 /bin/protoc 放到 /usr/local/bin 下,将 include 下的 google 文件,放到 /usr/local/include 目录下
mac 中安装 gRPC 需要的环境
方法和 linux
中安装 gRPC
基本一样
cd ~/software/protobuf #创建一个 software 文件,当时你也可以按照自己的习惯,放到 usr 文件夹中。
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protoc-3.12.0-osx-x86_64.zip # 在下载 grpc 的安装包
unzip protoc-3.12.0-osx-x86_64.zip # 解压安装包
mv bin/protoc /usr/local/protoc # 通过将 bin 文件放到 protoc 中的方法,将 protoc 命令添加到 path 中
查看 protoc
的版本信息
protoc --version
# 输出 libprotoc 3.7.1
上面安装步骤完成以后,只是安装了 protobuf
的基础功能包。我们想要使用 go
语言中的 gprc
功能,还需要安装 grpc
的包。
在任意的有mod 的文件下,执行如下命令
go get google.golang.org/grpc
如果想对此包做更多理解,可以查询 gPRC 的中文官方文档: http://doc.oschina.net/grpc?t=60133
编辑器
我一直比较喜欢使用 vscore
。使用 vscore 编写 protobuf
文件,需要安装两个插件支持,用于格式化 protobuf
文件
- vscode-proto3
- Clang-Format
一个小 demo
通过 grpc
协议,定义一个客户端,实现以下两个功能,并使用 grpc 的 client 端来调用
-
添加产品
-
根据产品 id 来查询产品信息
定义一个能添加产品和查询产品的 protobuf 文件
syntax = "proto3";
package ecommerce;
option go_package="./econFileName"; // 文件名和包名,此两个文件保持一致
service ProductInfo {
rpc addProduct(Product) returns (ProductID);
rpc getProduct(ProductID) returns (Product);
}
message Product {
string id = 1;
string name = 2;
string description = 3;
float price = 4;
}
message ProductID {
string value = 1;
}
在声明 protobuf 的文件夹下,执行如下命令,会自动生成对应的 go
文件,切记,此文件只能查看,不可更改
protoc --go_out=plugins=grpc:. *.proto
服务端
1. 找到自动生成的 go 文件中 grpc 服务端的接口
打开自动生成的 go 文件,可以看到 server
接口。注意和客户端的接口区分,此处是 Server 结尾的
2. 重载此接口
根据 go 语言非入侵的接口实现方式,声明一个结构体,只要实现了某接口的所有方法,那么他就实现了这个接口
type server struct {
products []*pb.Product
}
func (s *server) AddProduct(ctx context.Context, product *pb.Product) (*pb.ProductID, error) {
product.Id = uuid.New().String()
s.products = append(s.products, product)
return &pb.ProductID{
Value: product.Id,
}, status.New(codes.OK, "").Err()
}
func (s *server) GetProduct(ctx context.Context, proId *pb.ProductID) (*pb.Product, error) {
for _, prod := range s.products {
if prod.Id == proId.Value {
return prod, status.New(codes.OK, "").Err()
}
}
return nil, status.Errorf(codes.NotFound, "product not exist", proId.Value)
}
3.启动服务,监听 5001 端口
package main
import (
"context"
"fmt"
"net"
pb "zhao/grpc/pb/econFileName"
"github.com/google/uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
port = ":5001"
)
type server struct {
products []*pb.Product
}
func (s *server) AddProduct(ctx context.Context, product *pb.Product) (*pb.ProductID, error) {
product.Id = uuid.New().String()
s.products = append(s.products, product)
return &pb.ProductID{
Value: product.Id,
}, status.New(codes.OK, "").Err()
}
func (s *server) GetProduct(ctx context.Context, proId *pb.ProductID) (*pb.Product, error) {
for _, prod := range s.products {
if prod.Id == proId.Value {
return prod, status.New(codes.OK, "").Err()
}
}
return nil, status.Errorf(codes.NotFound, "product not exist", proId.Value)
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
panic(err)
}
fmt.Printf("net service is starting at :%s\n", port)
s := grpc.NewServer()
pb.RegisterProductInfoServer(s, &server{})
if err = s.Serve(lis); err != nil {
panic(fmt.Sprintf("tpc web service port:【%s】 launch fail %v", port, err))
}
}
-
注意,grpc 方法的传入和传出,都是结构体,即使是
productId
这种简单的 string 类型的形参,也应该使用自动生成的proto.go
文件中定义好的结构体 -
grpc 的方法调用中,也会有状态码,使用
google.golang.org/grpc/codes
包引用
完整代码如下
package main
import (
"context"
"fmt"
"time"
pb "zhao/grpc/pb/econFileName"
"google.golang.org/grpc"
)
const (
address = "localhost:5001"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
fmt.Printf("connect grpc service fail at %s", address)
}
defer conn.Close()
client := pb.NewProductInfoClient(conn)
// 要添加的产品
product1 := pb.Product{
Name: "Apple iPhone 11",
Description: "Meet Apple iPhone 11. All-new dual-camera system with Ultra Wide and Night mode.",
Price: float32(699.00),
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 添加产品
id, err := client.AddProduct(ctx, &product1)
if err != nil {
fmt.Printf("client productid [%s] add product fail err: %v\n", product1.Id, err)
}
fmt.Printf("grpc req response product id [%s]\n", id.Value)
// 查询产品
req_id := pb.ProductID{Value: id.Value}
res, err := client.GetProduct(ctx, &req_id)
if err != nil {
fmt.Printf("client get product fail id is %s\n", req_id.Value)
}
fmt.Printf("get product successful %s\n", res.String())
}
客户端
1. 声明连接使用的字符串
conn, err := grpc.Dial("localhost:5001", grpc.WithInsecure())
2.使用自动生成的 go 文件中的方法,创建 productInfo 对应的是实例
client := pb.NewProductInfoClient(conn)
3. 此时,你就可以拿着这个客户端实例,像本地方法调用一样使用远端方法了
res, err := client.GetProduct(ctx, &req_id)
完整代码如下
package main
import (
"context"
"fmt"
"time"
pb "zhao/grpc/pb/econFileName"
"google.golang.org/grpc"
)
const (
address = "localhost:5001"
)
func main() {
conn, err := grpc.Dial("localhost:5001", grpc.WithInsecure())
if err != nil {
fmt.Printf("connect grpc service fail at %s", address)
}
defer conn.Close()
client := pb.NewProductInfoClient(conn)
product1 := pb.Product{
Name: "Apple iPhone 11",
Description: "Meet Apple iPhone 11. All-new dual-camera system with Ultra Wide and Night mode.",
Price: float32(699.00),
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// 添加产品
id, err := client.AddProduct(ctx, &product1)
if err != nil {
fmt.Printf("client productid [%s] add product fail err: %v\n", product1.Id, err)
}
fmt.Printf("grpc req response product id [%s]\n", id.Value)
// 查询产品
req_id := pb.ProductID{Value: id.Value}
res, err := client.GetProduct(ctx, &req_id)
if err != nil {
fmt.Printf("client get product fail id is %s\n", req_id.Value)
}
fmt.Printf("get product successful %s\n", res.String())
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?