对RPC的理解
什么是RPC
RPC是Remote Procedure Call的缩写,中文译为远程过程调用,通俗一点来说就是通过网络调用部署在远程服务器上的函数或方法,如下图所示:

RPC与HTTP
谈到RPC,往往会让人联想到另一种调用远程服务的方式:HTTP。
那为什么有了HTTP,还需要RPC呢?
其实使用RPC在不同主机进程间通讯的时间要早于HTTP出现的时间。
因此,应该这么问:既然有了RPC为何还需要HTTP呢?
我们知道,任何网络应用程序之间通讯都是基于TCP协议(当然可以是UDP)。
TCP是传输层协议,其作用之一便是将从网络层接收的数据传给应用层。
HTTP是一个应用层协议,HTTP协议规定一条HTTP报文由请求行、请求头和消息体组成,因此每条HTTP报文都有固定的格式。
使用RPC的方式进行通讯时,传输层依然是TCP协议,但是应用层则需要通讯双方约定好数据格式,相当于自定义一个应用层协议,因此RPC有各种不同的实现,并没有统一的规范。
gRPC框架的使用
gRPC是一个高性能开源的RPC框架,支持Go,C++,Java,PHP,Ruby,Python等不同编程语言。

图片来自于grpc官网
Protocol Buffers
Protocol Buffers是Google开发的一种与语言、平台无关的数据序列化机制,这种机制由几个部分组成:
protoc编译器,用于编译.proto文件。- 以
.proto为后缀的IDL声明文件,用于定义一个RPC服务。 - 底层支持通讯并进行编码与解码的库。
Protocal Buffers有以下几个特征:
Protocal Buffers和JSON类似,用于序列化数据,不过与比于JSON其体积更小,因此传输也更快。- 支持多种编程语言。
- 可以非常快速传输与解析。
.proto文件
.proto文件用于声明使用gRPC进行通讯服务名称、请求数据类型、顺序与响应数据类型、顺序等信息。
.proto文件的第一行必须是:
syntax = "proto3";
如果没有声明为proto3,编译器会以proto2的语法解析.proto文件。
message关键字用于定义一个消息类型:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}同一个.proto文件里可以定义多个消息类型:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
//...
}service关键字用于声明一个服务,格式如下:
service Search {
rpc Search (SearchRequest) returns (SearchReply) {}
}编写好的.proto文件,要使用protoc编译进行编译,生成目标语言的代码。
开发工具安装
当然在开发之前,除了安装Go语言环境外,还需要安装以下几个工具:
- protoc
- protoc-gen-go
- protoc-gen-go-grpc
protoc
protoc是.proto文件的编译器,其作用是将.proto文件中声明的信息转为目标语言的代码。
protoc可以从以下地址下载:https://github.com/protocolbuffers/protobuf/releases
下载后,将其配置到PATH路径下即可。
protoc-gen-go与protoc-gen-go-grpc
如果要protoc编译器可以生成go代码,还需要安装protoc-gen-go和protoc-gen-go-grpc插件。
protoc-gen-go与protoc-gen-go-grpc插件用于生成go以及grpc代码,这两个插件由protoc命令调用。
使用go install将两个命令安装到GOPATH/bin目录下:
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest $ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
GOPATH/bin目录要配置到PATH目录下
案例实战
安装了相关工具以及了解了Protocol Buffers与.proto文件,下面我们通过一个实际案例来了解RPC应用的开发。
创建项目
首先执行以下命令创建一个Go项目:
$ mkdir test $ cd test $ go mod init test
定义.proto文件
用gRPC开发RPC应用的第一件事就是定义.proto文件,在这个项目中,我们在user目录下创建user.proto文件:
$ mkdir user $ touch user.proto
在user.proto文件中输入以下代码:
syntax = "proto3";
option go_package = "test/user";
//定义两个服务
service User {
rpc GetUser (UserId) returns (UserInfoReply) {}
rpc AddUser (AddUserRequest) returns(UserId){}
}
message UserId {
int32 id = 1;
}
message UserInfoReply {
int32 id = 1;
string name = 2;
string email = 3;
}
message AddUserRequest {
string name = 1;
string email = 2;
}编译.proto文件:
.proto文件编写完成后,执行protoc命令编译该文件,生成目标语言代码:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ user/user.proto
编译成功后会在user目录生成user.pb.go和user_grpc.pb.go两个文件。
编写服务端代码
下面是gRPC应用的服务端代码:
package main
import (
"context"
"log"
"net"
"test/user"
"google.golang.org/grpc"
)
type userServer struct {
user.UnimplementedUserServer
}
//[1]
func (s *userServer) GetUser(ctx context.Context, in *user.UserId) (*user.UserInfoReply, error) {
log.Printf("请求用户id为: %d", in.GetId())
return &user.UserInfoReply{Id: 1, Name: "程序员读书", Email: "test@163.com"}, nil
}
//[2]
func (s *userServer) AddUser(ctx context.Context, in *user.AddUserRequest) (*user.UserId, error) {
log.Printf("你要添加的用户名称为: %s,邮箱为:%s", in.GetName(), in.GetEmail())
return &user.UserId{Id: 2}, nil
}
func main() {
listen, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
user.RegisterUserServer(s, &userServer{})
log.Printf("server listening at %v", listen.Addr())
if err := s.Serve(listen); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}在上面的代码中,主要完成以下几件事:
- 创建一个监听器监听
50051端口 - 通过
grpc.NewServer()创建RPC服务器,将服务对象userServer绑定服务器 - 将监听器传给
RPC服务器以启动服务。
编写客户端代码
package main
import (
"context"
"log"
"os"
"time"
"test/user"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
u := user.NewUserClient(conn)
userInfo, err := u.GetUser(ctx, &user.UserId{Id: 1})
if err != nil {
log.Fatalf("user nto found: %v", err)
}
userId,err := u.AddUser(ctx,&user.AddUserRequest{Name:"test",Email:"test@test.com"})
}小结
本文主要介绍了使用gRPC与Go语言进行RPC应用的开发,总结起来就是以下几点:
- 通过网络调用远程主机的函数,称为
RPC。 gRPC是一个实现RPC的框架,支持多种编程语言。gRpc使用.proto文件描述一个RPC服务,并用protoc命令生成目标语言的代码。
到此这篇关于重学Go语言之如何开发RPC应用的文章就介绍到这了,更多相关Go RPC内容请搜索好代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持好代码网!




