注意:阅读开始前,请确保有grpc相关基础,新手向,使用各种实战方式来学习grpc
syntax = "proto3";
// 服务端流模式
option go_package = "./;streamprotodemo";
service Greeter {
rpc Single(StreamReqData) returns (StreamResData); //单向调用
rpc GetStream(StreamReqData) returns (stream StreamResData); // 服务端流模式
rpc PutStream(stream StreamReqData) returns (StreamResData); // 客户端流模式
rpc AllStream(stream StreamReqData) returns (stream StreamResData); // 双向流模式
}
message StreamReqData {
string data =1 ;
}
message StreamResData {
string data =1 ;
}
命令如下:
protoc -I . --go_out=. stream.proto --go-grpc_out=. stream.proto
首先我们初始化一个client对象,以及请求体对象
//初始化一个client对象,conn是用grpc.Dial创建的
client := streamprotodemo.NewGreeterClient(conn)
StreamReqData := streamprotodemo.StreamReqData{
Data: "chenteng",
}
func (s * server)Single(ctx context.Context,req *streamprotodemo.StreamReqData) (*streamprotodemo.StreamResData,error){
fmt.Println("server Single 被调用")
return &streamprotodemo.StreamResData{
Data: req.Data +"from server",
},nil
}
func Single(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.StreamReqData){
StreamReqData.Data = "client single func "
res ,err := client.Single(context.Background(),StreamReqData)
if err != nil {
log.Fatalln("single call fail ",err)
}
fmt.Println("Single call res :",res)
}
func (s *server) PutStream(clientdata streamprotodemo.Greeter_PutStreamServer) error {
fmt.Println("server PutStream 被调用")
for {
a, err := clientdata.Recv()
if err != nil {
log.Println(err)
break
}
fmt.Println(a)
}
return nil
}
func PutStream(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.StreamReqData){
putres ,_ := client.PutStream(context.Background())
for i:=0;i<10;i++ {
StreamReqData.Data = fmt.Sprintf("NO:%d, 客户端流模式",i)
fmt.Println("向服务端发送",StreamReqData.Data)
err := putres.Send(StreamReqData)
if err != nil {
log.Fatalln("向服务端发送数据失败",err)
}
}
}
通过for循环向客户端发送时间戳消息,模拟服务端流
func (s *server) GetStream(req *streamprotodemo.StreamReqData, res streamprotodemo.Greeter_GetStreamServer) error {
fmt.Println("server GetStream 被调用")
for i:=0;i<10;i++{
err := res.Send(&streamprotodemo.StreamResData{
Data: fmt.Sprintf("%v", time.Now().Unix()),
})
if err != nil {
log.Fatalln("send err", err)
}
time.Sleep(time.Second)
}
return nil
}
func GetStream(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.StreamReqData){
//服务端流模式
res,_ := client.GetStream(context.TODO(),StreamReqData)
for {
data ,err := res.Recv()
if err != nil {
if err == io.EOF {
fmt.Println("服务端传输结束")
break
}
log.Fatalln(err)
}
fmt.Println(data)
}
}
客户端、服务端同时发送消息,所以要使用两个gorouting并发去执行发收功能,这里使用sync下面的Waitgroup来等待gorouting执行完毕
func (s *server) AllStream(alldata streamprotodemo.Greeter_AllStreamServer) error {
fmt.Println("server AllStream 被调用")
wg := sync.WaitGroup{}
wg.Add(2)
defer wg.Wait()
go func() {
defer wg.Done()
for i:=0;i<10;i++ {
data,_ := alldata.Recv()
fmt.Println("AllStream收到客户端消息",data)
}
}()
go func() {
defer wg.Done()
for i:=0;i<10;i++ {
_ = alldata.Send(&streamprotodemo.StreamResData{
Data: "我是服务器",
})
time.Sleep(time.Second)
}
}()
return nil
}
func AllStream(client streamprotodemo.GreeterClient,StreamReqData *streamprotodemo.StreamReqData){
//双向流模式
allstream , _ := client.AllStream(context.Background())
wg := sync.WaitGroup{}
wg.Add(2)
defer wg.Wait()
go func() {
defer wg.Done()
for i:=0;i<10;i++ {
StreamResData,_ := allstream.Recv()
fmt.Println("AllStream收到服务端消息",StreamResData.Data)
}
}()
go func() {
defer wg.Done()
StreamReqData.Data = "AllStream:我是客户端"
for i:=0;i<10;i++ {
_ = allstream.Send(StreamReqData)
time.Sleep(time.Second)
}
}()
}
gRPC让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中,都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串数组类型。metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用。
客户端
func main(){
//md := metadata.Pairs("timestamp",time.Now().Format(timeformat))
lis ,_ := grpc.Dial("127.0.0.1:8081",grpc.WithInsecure())
client := metadatademo.NewHelloServiceClient(lis)
req := metadatademo.HelloRequest{Name: "chenteng"}
md := metadata.New(map[string]string{
"name":"chenteng",
})
ctx := metadata.NewOutgoingContext(context.Background(),md)
res,_ := client.SayHello(ctx,&req)
fmt.Println(res.Message)
}
服务端
func (h *HelloServer)SayHello(ctx context.Context,req *metadatademo.HelloRequest) (*metadatademo.HelloReply,error){
md , ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Println("获取md失败")
}
if name,ok := md["name"];ok {
fmt.Println(name[0])
}
//for key,val := range md {
// fmt.Println(key,val)
//}
return &metadatademo.HelloReply{
Message: "Hello " + req.Name,
},nil
}
在Grpc中可使用拦截器来进行服务鉴权,在收到请求之前就对metadata中进行校验,斌不会侵入我们的业务代码
客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"mygrpc/protodemo"
)
type customCredential struct {}
func (c *customCredential)GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error){
return map[string]string{
"name":"chenteng",
},nil
}
func (c *customCredential)RequireTransportSecurity() bool{
//是否启用安全模式
return false
}
func main() {
//使用更简单的方式传递md
op := grpc.WithPerRPCCredentials(&customCredential{})
conn, _ := grpc.Dial("127.0.0.1:7789", grpc.WithInsecure(), op)
client := protodemo.NewHelloServiceClient(conn)
req := protodemo.HelloRequest{Name: "chenteng"}
res, _ := client.SayHello(context.Background(), &req)
fmt.Println(res.Message)
}
服务端
package main
import (
"context"
"errors"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"log"
"mygrpc/protodemo"
"net"
"time"
)
type HelloServer struct {
protodemo.UnimplementedHelloServiceServer
}
func (h *HelloServer) SayHello(ctx context.Context, req *protodemo.HelloRequest) (*protodemo.HelloReply, error) {
time.Sleep(time.Second)
return &protodemo.HelloReply{
Message: "Hello " + req.Name,
}, nil
}
func main() {
Interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if md, ok := metadata.FromIncomingContext(ctx); !ok {
log.Println("获取认证信息失败")
return nil, errors.New("获取认证信息失败")
} else {
if value, ok := md["name"]; !ok {
log.Println("认证信息中未存放name相关信息")
return nil, errors.New("认证信息中未存放name相关信息")
} else {
log.Println("客户端访问,name = ", value[0])
}
}
starttime := time.Now()
res, err := handler(ctx, req)
fmt.Println("请求已完成,耗时:", time.Since(starttime))
return res, err
}
md := grpc.UnaryInterceptor(Interceptor)
server := grpc.NewServer(md)
protodemo.RegisterHelloServiceServer(server, &HelloServer{})
lis, err := net.Listen("tcp", ":7789")
if err != nil {
log.Fatal(err)
return
}
_ = server.Serve(lis)
}
在开发过程中,我们可以使用grpc下面的status包来返回类似于http中的404,500之类的错误状态码,可以方便我们排查问题,光有状态码是不够的,需要携带一下报错信息,下面是client与server的具体实现
客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
"mygrpc/protodemo"
)
func main() {
lis, _ := grpc.Dial("127.0.0.1:7789", grpc.WithInsecure())
client := protodemo.NewHelloServiceClient(lis)
req := protodemo.HelloRequest{Name: "chenteng"}
res, err := client.SayHello(context.Background(), &req)
if err != nil {
if statusinfo,ok := status.FromError(err);ok {
fmt.Println(statusinfo.Code())
fmt.Println(statusinfo.Message())
}
}
fmt.Println(res)
}
服务端
package main
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"log"
"mygrpc/protodemo"
"net"
)
type HelloServer struct {
protodemo.UnimplementedHelloServiceServer
}
func (h *HelloServer) SayHello(ctx context.Context, req *protodemo.HelloRequest) (*protodemo.HelloReply, error) {
return nil,status.Error(codes.Internal,"内部错误")
//return &protodemo.HelloReply{
// Message: "Hello " + req.Name,
//}, nil
}
func main() {
server := grpc.NewServer()
protodemo.RegisterHelloServiceServer(server, &HelloServer{})
lis, err := net.Listen("tcp", ":7790")
defer lis.Close()
if err != nil {
log.Fatal(err)
return
}
_ = server.Serve(lis)
}