Golang grpc 入门示例

grpc支持多种语言, 也支持Golang
安装: 由于无法直接获取对应的包, 因此安装需要费点功夫折腾下

go get -u github.com/golang/protobuf/{proto,protoc-gen-go}# 下载grpc-go git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genprotogit clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text# 安装 cd $GOPATH/src/ go install google.golang.org/grpc

备注:
1.github.com/golang/protobuf/{proto,protoc-gen-go} 可以直接下载访问github.com/golang/protobuf 下载对应文件
2.net/text包可以不用下载,默认应该有,若没有,则下载,可不用放入golang.org/x/ 下, 可以引用即可
【Golang grpc 入门示例】3.安装过程若执行不成功, 建议手动clone github文件,并将其放入到$GOPATH/src/golang.org 对应的目录下
4.proto 需加入path,方便进行命令执行
net/context 放入$GOPATH/src/golang.org/x proto,protoc-gen-go放入$GOPATH/src/google.golang.org/genproto grpc放入$GOPATH/src/google.golang.org/grpc

定义proto文件:
syntax = "proto3"; option java_package = "io.grpc.examples"; package helloworld; // The greeter service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} }// The request message containing the user's name. message HelloRequest { string name = 1; }// The response message containing the greetings message HelloReply { string message = 1; }

转换go文件:
protoc --go_out=plugins=grpc:. helloworld.proto

helloword入门
服务端server.go:
package mainimport ( "log" "net" "golang.org/x/net/context" "google.golang.org/grpc" pb "sr/helloworld" )const ( port = ":50051" )type server struct {}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { return &pb.HelloReply{Message: "Hello " + in.Name}, nil }func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatal("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) }

客服端client.go:
package main//client.goimport ( "log" "os" "golang.org/x/net/context" "google.golang.org/grpc" pb "sr/helloworld" )const ( address= "localhost:50051" defaultName = "world" )func main() { conn, err := grpc.Dial(address, grpc.WithInsecure()) if err != nil { log.Fatal("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) name := defaultName if len(os.Args) >1 { name = os.Args[1] } r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) if err != nil { log.Fatal("could not greet: %v", err) } log.Printf("Greeting: %s", r.Message) }

运行server.go 和client.go
客户端输出:
2019/08/19 21:46:04 Greeting: Hello world

远程调用计算函数:
服务端:
package mainimport ( "errors" "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc" "os" )// 算数运算结构体 type Arith struct { }// 算数运算请求结构体 type ArithRequest struct { A int B int }// 算数运算响应结构体 type ArithResponse struct { Pro int // 乘积 Quo int // 商 Rem int // 余数 }// 乘法运算方法 func (this *Arith) Multiply(req ArithRequest, res *ArithResponse) error { res.Pro = req.A * req.B return nil }// 除法运算方法 func (this *Arith) Divide(req ArithRequest, res *ArithResponse) error { if req.B == 0 { return errors.New("divide by zero") } res.Quo = req.A / req.B res.Rem = req.A % req.B return nil }func resp(conn net.Conn) { fmt.Fprintf(os.Stdout, "%s", "new client in coming\n") //jsonrpc.ServeConn(conn) newcode := jsonrpc.NewServerCodec(conn) //newcode.ReadRequestHeader(n) rpc.ServeCodec(newcode) }func main() { rpc.Register(new(Arith)) // 注册rpc服务 rpc.HandleHTTP() lis, err := net.Listen("tcp", "127.0.0.1:8096") if err != nil { log.Fatalln("fatal error: ", err) } fmt.Fprintf(os.Stdout, "%s", "start connection") for {conn, err := lis.Accept() // 接收客户端连接请求 fmt.Println(conn.RemoteAddr().String()) if err != nil { continue }//rpc.ServeConn(conn) go resp(conn) //go func(conn net.Conn) { // 并发处理客户端请求 // fmt.Fprintf(os.Stdout, "%s", "new client in coming\n") // jsonrpc.ServeConn(conn) //}(conn) //go jsonrpc.ServeConn(conn) } }

客户端:
package mainimport ( "fmt" "log" "net/rpc/jsonrpc" )// 算数运算请求结构体 type ArithRequest struct { A int B int }// 算数运算响应结构体 type ArithResponse struct { Pro int // 乘积 Quo int // 商 Rem int // 余数 }func main() { conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8096") if err != nil { log.Fatalln("dailing error: ", err) } req := ArithRequest{9, 2} var res ArithResponse err = conn.Call("Arith.Multiply", req, &res) // 乘法运算 if err != nil { log.Fatalln("arith error: ", err) } fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro) err = conn.Call("Arith.Divide", req, &res) if err != nil { log.Fatalln("arith error: ", err) } fmt.Printf("%d / %d, quo is %d, rem is %d\n", req.A, req.B, res.Quo, res.Rem) }

输出:
服务端:
start connection127.0.0.1:49582 new client in coming 127.0.0.1:49583 new client in coming

客户端:
9 * 2 = 18 9 / 2, quo is 4, rem is 1

支持加密通信
SSL/TSL 加密: 服务端:
package main// server.goimport ( "fmt" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "google.golang.org/grpc/peer" "log" "net" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" pb "sr/helloworld" )const ( port = ":50051" )type server struct {}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { fmt.Println("replay ... ") pr, ok := peer.FromContext(ctx) fmt.Println(pr.Addr.String()) md, ok := metadata.FromIncomingContext(ctx) fmt.Println(md) if !ok { return nil, grpc.Errorf(codes.Unauthenticated, "无Token认证信息") } var ( appidstring appkey string ) if val, ok := md["appid"]; ok { appid = val[0] } if val, ok := md["appkey"]; ok { appkey = val[0] } if appid != "101010" || appkey != "i am key" { return nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey) } return &pb.HelloReply{Message: "Hello " + in.Name}, nil }func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatal("failed to listen: %v", err) } creds, err := credentials.NewServerTLSFromFile("./key/svr.pem", "./key/svr.key") if err != nil { grpclog.Fatalf("Failed to generate credentials %v", err) } s := grpc.NewServer(grpc.Creds(creds)) pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) }

客户端:
package main//client.goimport ( "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" "log" "os" "golang.org/x/net/context" "google.golang.org/grpc" pb "cl/helloworld" )const ( address= "localhost:50051" defaultName = "world" OpenTLS = true )// customCredential 自定义认证 type customCredential struct{}func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "appid":"101010", "appkey": "i am key", }, nil }func (c customCredential) RequireTransportSecurity() bool { if OpenTLS { return true } return false }func main() { var opts []grpc.DialOption if OpenTLS { creds, err := credentials.NewClientTLSFromFile("./key/svr.pem", "test.aaa.com") if err != nil { grpclog.Fatalf("Failed to create TLS credentials %v", err) } opts = append(opts, grpc.WithTransportCredentials(creds)) }else { opts = append(opts, grpc.WithInsecure()) } opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential))) //creds, err := credentials.NewClientTLSFromFile("./key/svr.pem", "test.aaa.com") //if err != nil { // grpclog.Fatalf("Failed to create TLS credentials %v", err) //} //conn, err := grpc.Dial(address, grpc.WithInsecure()) //conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds)) conn, err := grpc.Dial(address, opts...) if err != nil { log.Fatal("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) name := defaultName if len(os.Args) >1 { name = os.Args[1] } r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name}) if err != nil { log.Fatal("could not greet: %v", err) } log.Printf("Greeting: %s", r.Message) }

输出:
服务端:
replay ... [::1]:49638 map[:authority:[test.aaa.com] content-type:[application/grpc] user-agent:[grpc-go/1.20.0-dev] appkey:[i am key] appid:[101010]]

客户端:
2019/08/19 22:01:42 Greeting: Hello world

如果证书异常或者证书过期, 提示如下错误:
2019/08/19 20:03:48 could not greet: %vrpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: x509: certificate has expired or is not yet valid"
则需要更新证书

    推荐阅读