
gRPC简单模式/长连接实战
1.GRPC环境配置
1.安装gRPC package
go get -u google.golang.org/grpc2.安装protobuf
linux安装protobuf
# 安装依赖 yum install autoconf automake libtool curl make g++ unzip libffi-dev glibc-headers gcc-c++ -y # 下载 git clone https://github.com/protocolbuffers/protobuf.git # 安装 unzip protobuf.zip cd protobuf ./autogen.sh ./configure make && make install ldconfig # 刷新共享库 # 测试 protoc --version
mac安装protobuf,推荐使用homebrew安装:
# 安装 brew install protobuf brew install automake brew install libtool # 测试 protoc --version
win安装protobuf
下载地址: https://github.com/google/protobuf/releases
下载win版本后,配置文件中的bin目录到Path环境变量下即可。protoc --version #查看protoc的版本 libprotoc 3.12.0
安装代码golang契约生成工具
//正确方式 go get -u github.com/golang/protobuf/protoc-gen-go //将protoc-gen-go安装到您的$GOPATH/bin go get google.golang.org/genproto/新版不能使用,继续沿用旧版 go get google.golang.org/protobuf/cmd/protoc-gen-go //不使用 go install google.golang.org/protobuf/cmd/protoc-gen-go //不使用//新版本添加声明 option go_package = ".;io_grpc_examples_hello";注意和gRPC版本保持同步
2.获得demo源代码
proto锲约示例:
syntax = "proto3"; //语法使用 protocol buffer proto3 option go_package = ".;hello"; package protoes; service Chat { rpc BidStream(stream Request) returns (stream Response){} } message Request{ string input = 1; } message Response { string output = 1; }
3.gRPC简单模式实战
说明:一定要使用mod管理,protoes.server,client都要生成mod管理
契约代码:
syntax = "proto3"; option go_package = ".;io_grpc_examples_hello"; package protoes; service HelloServer{ rpc SayHi(HelloRequest)returns(HelloReplay){} rpc GetMsg(HelloRequest)returns(HelloMessage){} } message HelloRequest{ string name = 1 ; } message HelloReplay{ string message = 1; } message HelloMessage{ string msg = 1; }生成Golang代码:
protoc --go_out=plugins=grpc:. *.proto考察生成的代码
type HelloServerClient interface { SayHi(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReplay, error) GetMsg(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloMessage, error) } type HelloServerServer interface { SayHi(context.Context, *HelloRequest) (*HelloReplay, error) GetMsg(context.Context, *HelloRequest) (*HelloMessage, error) }继续观察结构体HelloRequest
server代码示例:
package main import ( "fmt" pb "protoes" "net" "context" "google.golang.org/grpc" ) // 对象要和proto内定义的服务一致 type server struct { } // 实现rpc的 SayHi接口 func (s *server) SayHi(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReplay, error) { return &pb.HelloReplay{ Message: "Hi " + in.Name, }, nil } // 实现rpc的 GetMsg接口 func (s *server) GetMsg(ctx context.Context, in *pb.HelloRequest) (*pb.HelloMessage, error) { return &pb.HelloMessage{ Msg: "Server msg is coming...", }, nil } func main() { // 监听网络 ln, err := net.Listen("tcp", "127.0.0.1:3000") if err != nil { fmt.Println("网络异常:", err) return } // 创建grpc句柄 srv := grpc.NewServer() // 将server结构体注册到grpc服务中 pb.RegisterHelloServerServer(srv, &server{}) // 监听服务 err = srv.Serve(ln) //阻塞 if err != nil { fmt.Println("监听异常:", err) return } }run:
.\server.exe
client代码示例:
package main import ( "fmt" pb "protoes" "context" "google.golang.org/grpc" ) func main() { // 客户端连接服务器 conn, err := grpc.Dial("127.0.0.1:3000", grpc.WithInsecure()) if err != nil { fmt.Println("连接服务器失败", err) } // defer的好处 defer conn.Close() // 获得grpc句柄。连接是共享的 c := pb.NewHelloServerClient(conn) // 远程单调用 SayHi 接口。比较和gorpc的差别,编译访问和字符串访问 r1, err := c.SayHi( context.Background(), &pb.HelloRequest{ Name: "Kitty", }, ) if err != nil { fmt.Println("Can not get SayHi:", err) return } fmt.Println("SayHi 响应:", r1) // 远程单调用 GetMsg 接口 r2, err := c.GetMsg( context.Background(), &pb.HelloRequest{ Name: "Kitty", }, ) if err != nil { fmt.Println("Can not get GetMsg:", err) return } fmt.Println("GetMsg 响应:", r2) }run:
.\client.exe SayHi 响应: message:"Hi Kitty" GetMsg 响应: msg:"Server msg is coming..."
5.gRPC长连接实战
契约代码:
syntax = "proto3"; // 语法使用 protocol buffer proto3 option go_package = ".;io_grpc_examples_hello"; package protoes; /* 服务名: Chat, 其中只有 名为“BidStream”的一个RPC服务, 输入是 Request格式的数据流, 输出是 Response 格式的数据流 */ service Chat { rpc BidStream(stream Request) returns (stream Response) {} } // 请求数据 Request格式定义 message Request { string input = 1; } // 响应数据Response格式定义 message Response { string output = 1; }生成Golang代码
protoc --go_out=plugins=grpc:. *.proto生成的代码分析
// ChatServer is the server API for Chat service. type ChatServer interface { BidStream(Chat_BidStreamServer) error } type Chat_BidStreamServer interface { Send(*Response) error Recv() (*Request, error) grpc.ServerStream } type ChatClient interface { BidStream(ctx context.Context, opts ...grpc.CallOption) (Chat_BidStreamClient, error) } type Chat_BidStreamClient interface { Send(*Request) error Recv() (*Response, error) grpc.ClientStream }
server代码示例:
package main import ( "io" "log" "net" proto "protoes" "strconv" "google.golang.org/grpc" ) // Streamer 服务端 type Streamer struct{} // BidStream 实现了 ChatServer 接口中定义的 BidStream 方法 func (s *Streamer) BidStream(stream proto.Chat_BidStreamServer) error { ctx := stream.Context() for { select { case <-ctx.Done(): log.Println("收到客户端通过context发出的终止信号") return ctx.Err() default: // 接收从客户端发来的消息 输入, err := stream.Recv() if err == io.EOF { log.Println("客户端发送的数据流结束") return nil } if err != nil { log.Println("接收数据出错:", err) return err } // 如果接收正常,则根据接收到的 字符串 执行相应的指令 switch 输入.Input { case "结束对话": log.Println("收到'结束对话'指令") if err := stream.Send(&proto.Response{Output: "收到结束指令"}); err != nil { return err } // 收到结束指令时,通过 return nil 终止双向数据流 return nil case "返回数据流": log.Println("收到'返回数据流'指令") // 收到 收到'返回数据流'指令, 连续返回 10 条数据 for i := 0; i < 10; i++ { if err := stream.Send(&proto.Response{Output: "数据流 #" + strconv.Itoa(i)}); err != nil { return err } } default: // 缺省情况下, 返回 '服务端返回: ' + 输入信息 log.Printf("[收到消息]: %s", 输入.Input) if err := stream.Send(&proto.Response{Output: "服务端返回: " + 输入.Input}); err != nil { return err } } } } } func main() { log.Println("启动服务端...") server := grpc.NewServer() // 注册 ChatServer proto.RegisterChatServer(server, &Streamer{}) address, err := net.Listen("tcp", ":3000") if err != nil { panic(err) } if err := server.Serve(address); err != nil { panic(err) } }run
.\server.execlient代码示例:
package main import ( "bufio" "context" "io" "log" "os" proto "protoes" "google.golang.org/grpc" ) func main() { // 创建连接 conn, err := grpc.Dial("localhost:3000", grpc.WithInsecure()) if err != nil { log.Printf("连接失败: [%v] ", err) return } defer conn.Close() // 声明客户端 client := proto.NewChatClient(conn) // 声明 context ctx := context.Background() // 创建双向数据流 stream, err := client.BidStream(ctx) if err != nil { log.Printf("创建数据流失败: [%v] ", err) } // 启动一个 goroutine 接收命令行输入的指令 go func() { log.Println("请输入消息...") scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { // 向服务端发送 指令 if err := stream.Send(&proto.Request{Input: scanner.Text()}); err != nil { return } } }() for { // 接收从 服务端返回的数据流 响应, err := stream.Recv() if err == io.EOF { log.Println(" 收到服务端的结束信号") break //如果收到结束信号,则退出“接收循环”,结束客户端程序 } if err != nil { // TODO: 处理接收错误 log.Println("接收数据出错:", err) } // 没有错误的情况下,打印来自服务端的消息 log.Printf("[客户端收到]: %s", 响应.Output) } }run
.\client.exe 2020/04/18 21:50:46 请输入消息... hello 2020/04/18 21:50:49 [客户端收到]: 服务端返回: hello PS D:\mygo\src\grpcdemo-bistream\client> .\client.exe 2020/04/18 21:51:18 请输入消息... hello 2020/04/18 21:51:21 [客户端收到]: 服务端返回: hello 返回数据流 2020/04/18 21:51:27 [客户端收到]: 数据流 #0 2020/04/18 21:51:27 [客户端收到]: 数据流 #1 2020/04/18 21:51:27 [客户端收到]: 数据流 #2 2020/04/18 21:51:27 [客户端收到]: 数据流 #3 2020/04/18 21:51:27 [客户端收到]: 数据流 #4 2020/04/18 21:51:27 [客户端收到]: 数据流 #5 2020/04/18 21:51:27 [客户端收到]: 数据流 #6 2020/04/18 21:51:27 [客户端收到]: 数据流 #7 2020/04/18 21:51:27 [客户端收到]: 数据流 #8 2020/04/18 21:51:27 [客户端收到]: 数据流 #9 结束对话 2020/04/18 21:51:32 [客户端收到]: 收到结束指令 2020/04/18 21:51:32 收到服务端的结束信号连接多个客户端试试?
思考,有多少goroutine在等待用户输入?