go gprc 使用 教程
技術(shù)棧
grpc
go
protobuff
1.環(huán)境
1.1. 安裝protoc
項(xiàng)目地址 https://github.com/protocolbuffers/protobuf
下載protobuff, 有條件的github上直接下載,github下載地址锐膜,或者可以從maven倉庫下載
在maven倉庫中找到對(duì)應(yīng)的版本,進(jìn)行下載瑟慈,linux ,windows 都有
這里我下載 windows 64 位的這個(gè)
下載下來的是可執(zhí)行文件,直接放到環(huán)境變量里面且改,這個(gè)文件名太長(zhǎng)了浦辨,把后綴都去掉
我放的路徑 %GOPATH%\bin\protoc.exe
1.2. 安裝 protoc-gen-go
直接go get -u github.com/golang/protobuf/protoc-gen-go
go get 的比較慢的話可以用代理。需要配置下代理伊诵。
看下 %GOPATH%\bin\ 有沒有protoc-gen-go.exe ,沒有的話需要找到下載的包進(jìn)行安裝回官。
下載目錄在:%GOPATH%\pkg\mod\github.com\golang\protobuf@xxx\protoc-gen-go
進(jìn)入目錄然后 go install 曹宴,然后再去看bin 目錄就會(huì)生成protoc-gen-go.exe
2. 建項(xiàng)目實(shí)踐
1. 建目錄 grpcprj
go mod init grpcprj
這個(gè)是我創(chuàng)建后的目錄
| go.mod #工程文件
| go.sum #工程文件
+---client #客戶端代碼存放目錄
| main.go
+---proto #proto 文件存放目錄,包括通過protoc 編譯后的文件
| \---hello
| hello.pb.go #protoc 根據(jù)proto文件生成的go項(xiàng)目文件
| hello.proto #proto 文件
\---server #服務(wù)端代碼
| main.go
\---handler #服務(wù)端接口實(shí)現(xiàn)
hello.go
2. 編寫proto文件
syntax="proto3"; //版本
package go.rpc.srv.hello; //作用域
service Hello{ //service 表示服務(wù)歉提,在這里面定義接口
//定義了一個(gè)接口SayHello 接收了一個(gè)Say消息笛坦,返回一個(gè)Say消息
rpc SayHello(Say) returns (Say);
}
// 定義一個(gè)結(jié)構(gòu)體,Say
message Say {
string name = 1; // 定義格式如下 :<修飾符> 類型 字段名 = 唯一編號(hào)
//由于一些歷史原因苔巨,基本數(shù)值類型的repeated的字段并沒有被盡可能地高效編碼版扩。
//在新的代碼中,用戶應(yīng)該使用特殊選項(xiàng)[packed=true]來保證更高效的編碼侄泽。
//注意[packed=true]只能用在 repeated修飾的數(shù)字類型中
//repeated用來定義數(shù)組
repeated int32 list =2; //repeated
//定義map
map<string, string> maps = 3;
//有時(shí)候你需要保留一些你以后要用到的編號(hào)或者變量名资厉,使用reserved關(guān)鍵字
reserved 3, 15, 9 to 11; //保留 3,15蔬顾,9到11
reserved "foo", "bar";
//string var2 = 3;//編譯會(huì)報(bào)錯(cuò),因?yàn)?被保留了
//string var3 = 10;//編譯會(huì)報(bào)錯(cuò)湘捎,因?yàn)?0被保留了
//string foo = 12;//編譯會(huì)報(bào)錯(cuò)诀豁,因?yàn)閒oo被保留了
}
消息體可以嵌套,也可以引入其他的proto文件
import "google/protobuf/any.proto"; //引入其他文件
//消息嵌套
message ParentBean{
ChildA child = 1;
message ChildB{
string name = 1;
}
ChildB child2 = 2 ;
}
message ChildA{
string name = 1;
}
字段類型對(duì)比
.proto Type | Go Type | 說明 |
---|---|---|
double | float64 | |
float | float32 | |
int32 | int32 | 使用變長(zhǎng)編碼窥妇,對(duì)于負(fù)值的效率很低舷胜,如果你的域有可能有負(fù)值,請(qǐng)使用sint64替代 |
uint32 | uint32 | 使用變長(zhǎng)編碼 |
uint64 | uint64 | 使用變長(zhǎng)編碼 |
sint32 | int32 | 使用變長(zhǎng)編碼,這些編碼在負(fù)值時(shí)比int32高效的多 |
sint64 | int64 | 使用變長(zhǎng)編碼烹骨,有符號(hào)的整型值翻伺。編碼時(shí)比通常的int64高效。 |
fixed32 | uint32 | 總是4個(gè)字節(jié)沮焕,如果數(shù)值總是比總是比228大的話吨岭,這個(gè)類型會(huì)比uint32高效。 |
fixed64 | uint64 | 總是8個(gè)字節(jié)峦树,如果數(shù)值總是比總是比256大的話辣辫,這個(gè)類型會(huì)比uint64高效。 |
sfixed32 | int32 | 總是4個(gè)字節(jié) |
sfixed64 | int64 | 總是8個(gè)字節(jié) |
bool | bool | 默認(rèn) false |
string | string | 一個(gè)字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本魁巩,默認(rèn)值為空急灭。 |
bytes | []byte | 默認(rèn)是空數(shù)組 |
3. 編譯proto文件
? proto 文件編寫完畢以后,使用protoc 進(jìn)行編譯谷遂,編譯成成 xxx.pb.go葬馋,編譯是干什么呢?
protoc --go_out=plugins=grpc:. proto/hello/hello.proto
? 運(yùn)行命令后會(huì)在 proto 文件同目錄生成 xxx.pb.go,對(duì)應(yīng)的service 會(huì)成生成一個(gè)空的實(shí)現(xiàn)(指的是只做了實(shí)現(xiàn)肾扰,并沒有具體業(yè)務(wù)功能)畴嘶,這里需要注意的是,默認(rèn)go環(huán)境變量已經(jīng)配置正確白对,gopath\bin 在環(huán)境變量里面掠廓。
hello.proto(最簡(jiǎn)的)
syntax="proto3";
service Hello{
rpc SyaHello(Say) returns (Say);
}
message Say {
string name = 1;
}
編譯后的文件
...省略了其他代碼
// HelloServer is the server API for Hello service.
// helloServer 接口
type HelloServer interface {
SyaHello(context.Context, *Say) (*Say, error)
}
// UnimplementedHelloServer can be embedded to have forward compatible implementations.
type UnimplementedHelloServer struct {
}
// 以下是一個(gè)空的實(shí)現(xiàn),沒有實(shí)現(xiàn)任何業(yè)務(wù)邏輯
func (*UnimplementedHelloServer) SyaHello(context.Context, *Say) (*Say, error) {
return nil, status.Errorf(codes.Unimplemented, "method SyaHello not implemented")
}
4. 實(shí)現(xiàn)服務(wù)端接口邏輯
? 服務(wù)端可以實(shí)現(xiàn)甩恼,也可以不實(shí)現(xiàn)蟀瞧,也可以改下上面的默認(rèn)生成的這個(gè)實(shí)現(xiàn)類,這里重新實(shí)現(xiàn)了下条摸,里面寫自己的業(yè)務(wù)邏輯悦污。
server/handler/hello.go(最簡(jiǎn)實(shí)現(xiàn))
package handler
import (
"context"
"fmt"
h "grpcprj/proto/hello"
)
type HelloImpl struct {}
func (hi *HelloImpl)SyaHello(ctx context.Context,in *h.Say) (*h.Say, error){
msg:=fmt.Sprintf("echo say:%s",in.GetName())
return &h.Say{Name:msg},nil
}
5. 服務(wù)端代碼
package main
import (
"context"
"google.golang.org/grpc"
h "grpcprj/proto/hello"
"log"
"time"
)
var(
addr ="localhost:8081"
defmsg = "hello boger"
)
func main() {
var(
conn *grpc.ClientConn
ctx context.Context
cancel context.CancelFunc
err error
r *h.Say
)
if conn,err=grpc.Dial(addr,grpc.WithInsecure(),grpc.WithBlock());err!=nil{
log.Fatalf("conn server err :%v",err)
}
defer conn.Close()
c := h.NewHelloClient(conn)
ctx,cancel=context.WithTimeout(context.Background(),time.Second)
defer cancel()
if r,err=c.SyaHello(ctx,&h.Say{Name:defmsg});err!=nil{
log.Fatalf("get error :%v",err)
}
log.Printf("get msg :%s",r.Name)
}
6. 客戶端代碼
? protoc 生成的go 代碼里面包括服務(wù)端的定義,和客戶端的定義(協(xié)議的打包钉蒲,解包都包括了)
? grpcprj/client/hello.go
package main
import (
"context"
"google.golang.org/grpc"
h "grpcprj/proto/hello"
"log"
"time"
)
var(
addr ="localhost:8081"
defmsg = "hello boger"
)
func main() {
var(
conn *grpc.ClientConn
ctx context.Context
cancel context.CancelFunc
err error
r *h.Say
)
if conn,err=grpc.Dial(addr,grpc.WithInsecure(),grpc.WithBlock());err!=nil{
log.Fatalf("conn server err :%v",err)
}
defer conn.Close()
c := h.NewHelloClient(conn)
ctx,cancel=context.WithTimeout(context.Background(),time.Second)
defer cancel()
if r,err=c.SyaHello(ctx,&h.Say{Name:defmsg});err!=nil{
log.Fatalf("get error :%v",err)
}
log.Printf("get msg :%s",r.Name)
}