网上的grpc教程都或多或少有些老或者有些问题,导致最后执行生成文件时会报很多错。这里给出个人实践出可执行的编译命令与碰到的报错与解决方法。(ps:本文代码按照煎鱼的教程编写:4.2 gRPC Client and Server - 跟煎鱼学 Go (gitbook.io))
最后看了官网,果然只有官网的教程不会太老导致不可用。
- 项目目录结构
grpc_test
├─client
| └─main
| └─client.go
└─proto
| |─grpc
| └─search.proto
└─server└─main└─server.go
-
下载protoc不做介绍请自行下载
下载后执行
protoc --version
输出类似以下信息则安装成功,否则卸载重新安装
libprotoc 25.1
-
下载go编译proto插件
下面的命令已废弃!不要使用
go get -u github.com/golang/protobuf/protoc-gen-go
请使用下面命令!
go get -u google.golang.org/grpc go install google.golang.org/protobuf/cmd/protoc-gen-go go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
观察GOPATH目录的bin下有两个文件
-
编写search.proto文件
syntax = "proto3";package proto;service SearchService {rpc Search(SearchRequest) returns (SearchResponse) {} }message SearchRequest {string request = 1; }message SearchResponse {string response = 1; }
-
执行编译命令
$ protoc --go_out=plugins=grpc:. *.proto
以上是煎鱼的grpc教程,可能由于版本或者环境导致现在并不能使用了,下面列出我遇到的报错以及如何解决
-
报错一:插件旧库已废弃,且安装新库命令由于本地go tool版本不匹配安装失败
执行下面命令报错该库已经废弃
go get -u github.com/golang/protobuf/protoc-gen-go
执行下面命令时报错tool的版本不对
go install google.golang.org/protobuf/cmd/protoc-gen-go
报错信息
compile: version "go1.20.13" does not match go tool version "go1.21.6"
该报错导致插件下载失败,网上说是升级版本时以前的库没删干净,但是我并没用找到以前的库。所以我全部重新安装了最近的1.22
-
首先把GOPATH下三个文件夹里文件全部删除
-
把GOROOT里文件全部删除
-
去官网重新下载Windows版本的1.22的zip文件解压到我的GOROOT里(ps:安装新版本后本地执行go version可能还是以前的版本,不用担心,这是本地环境的缓存,重启一下再go version就好,就像linux里修改环境文件都要sourse一下才能使用)
重新执行插件安装命令,执行成功!
-
-
报错二:执行编译命令失败
执行下面编译命令时报错
$ protoc --go_out=plugins=grpc:. *.proto
报错
'protoc-gen-go' 不是内部或外部命令,也不是可运行的程序 或批处理文件。 --go_out: protoc-gen-go: Plugin failed with status code 1.
原因:该命令不可用或者本地插件环境未配置好
下面把GOPATH的bin目录下新下载的文件安装到GOROOT下bin中解决!
我本地GOROOT的文件夹是go
-
报错三:输出参数不对
--proto_path passed empty directory name. (Use "." for current directory.)
这是proto3后来规定文件中必须要配置go_package参数,在文件中配置加上此参数以解决(protoc 3.10 版本后可能会报此错误)
syntax = "proto3";package proto;option go_package = "/grpc;proto";service SearchService {rpc Search(SearchRequest) returns (SearchResponse) {} }message SearchRequest {string request = 1; }message SearchResponse {string response = 1; }
option go_package = “/grpc;proto”;
分号前是输出的 .pb.go 文件的路径,路径不存在会自动创建
分号后是输入的 .pb.go 文件的包名
最后进入proto目录并执行下面命令,grpc目录下会自动生成编译文件(ps:grpc目录自己创建,不建议生成编译go文件在proto中,因为grpc的缺陷是后面修改proto文件都需要重新编译生成go文件,若proto文件写错了会覆盖原来好的文件。建议生成在别的目录下确认不误后再投入使用)
另外煎鱼的教程可能版本只能生成一个go文件,代码也不对了。请使用下面命令!
protoc --go_out=. --go-grpc_out=. search.proto
- 煎鱼的代码可能也是不能用了,我重写了一下,加上timeoutcontext的超时关闭逻辑,测试后可以正常运行
client.go
package mainimport ("context""fmt""log""time""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"pb "grpc_test/proto/grpc"proto "grpc_test/proto/grpc" )const address = "127.0.0.1:9001"func getGrpcConn(address string) (*grpc.ClientConn, *proto.SearchServiceClient, *context.CancelFunc, error) {ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)// timeoutcontex用于超时自动关闭// grpc.WithBlock()用于配置gRPC客户端连接阻塞并等待连接建立或失败后再返回// 在需要确保连接就绪后再继续执行后续代码的场景中很有用// grpc现在强制要求设置TLS,withinsurance已经废弃conn, err := grpc.DialContext(ctx, address, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {msg := fmt.Sprintf("did not connect to %v error %v", address, err)log.Println(msg)}client := proto.NewSearchServiceClient(conn)return conn, &client, &cancel, err }func main() {conn, client, _, _ := getGrpcConn(address)defer conn.Close()// Contact the server and print out its response.ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()// 使用服务端函数resp, err := (*client).Search(ctx, &pb.SearchRequest{Request: "gRPC"})if err != nil {log.Fatalf("client.Search err: %v", err)}log.Printf("resp: %s", resp.GetResponse()) }
server.go
package mainimport ("context""fmt""log""net""google.golang.org/grpc"pb "grpc_test/proto/grpc")type SearchService struct {pb.UnimplementedSearchServiceServer }func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {fmt.Println("sever serching")return &pb.SearchResponse{Response: r.GetRequest() + " Server"}, nil }const PORT = "9001"func main() {// 获取grpc服务端(被调用方)server := grpc.NewServer()// 服务端映射到结构体SearchService{}(注册)pb.RegisterSearchServiceServer(server, &SearchService{})// 建立tcp端口监听lis, err := net.Listen("tcp", ":"+PORT)if err != nil {log.Fatalf("net.Listen err: %v", err)}// 服务端监听tcp连接if err := server.Serve(lis); err != nil {log.Fatalf("net.Listen err: %v", err)} }
有兴趣的可以看看rpc的设计原理,其实大多应用在使用时就可以反映出大致原理,rpc的设计框架也是,从网络传输协议,序列化协议,消息编码协议三层设计,另外加上函数注册和参数注册的逻辑。利用tcp或者http建立连接,传输编码后的请求参数,将参数解码后在本地找到对应函数,本地执行对应函数并填充回包,再把回包编码通过网络连接返回。即在网络连接上实现双方无感知调用对方函数。
参考文件
记不得了,全网教程几乎都看了,参考太多了