• 这篇文章里我们要实现一个基于GoLang编程语言的gRPC的客户端与服务端通信的HelloWorld案例

编写hello_world.proto文件,如下代码:

syntax proto3

package proto

//接口请求入参
message HelloRequest{
    string request = 1;
}
//接口返回出参
message HelloResponse{
    string response = 1;
}
//定义接口
service HelloService{
    //一个简单的rpc
    rpc HelloWorld(HelloRequest) returns (HelloResponse){}
    //一个服务器端流式rpc
    rpc HelloWorldServerStream(HelloRequest) returns (stream HelloResponse){}
    //一个客户端流式rpc
    rpc HelloWorldClientStream(stream HelloRequest) returns (HelloResponse){}
    //一个客户端和服务器端双向流式rpc
    rpc HelloWorldClientAndServerStream(stream HelloRequest) 
                       returns (stream HelloResponse){}
}
  • 如上代码,通过protobuffer的service定义一个接口HelloService,接口中有四个方法都以HelloWorld开头,入参是HelloRequest,出参是HelloResponse,通过最前面的rpc关键字标识为这是一个rpc接口

编译hello_world.proto文件生成对应的go文件

  • 笔者在go_common项目里新建了grpc项目结构如下代码
  • 进入项目之后可以先阅读README.md,了解各个包的功能
go_common
    grpc
        helloworld_new
            client //存放客户端代码
            proto 存放proto文件和生成的go文件
            server存放server文件
  • cd go_common/grpc/helloworld_new/proto,进入proto文件夹下
  • 执行命令:protoc --go_out=plugins=grpc:. hello_world.proto
  • 最后生成hello_world.pb.go文件,主要有以下几部分组成:
    1. 方法出入参结构体以及序列化和反序列方法
    2. 注册出入参结构体的init方法
    3. 客户端存根结构体和接口以及实现
    4. 服务端结构体和接口以及一个空实现
    5. stream的send和recv结构体和接口以及实现
    6. 服务的一些描述
  • 代码太多,这里粘一些核心代码,并且简化了一些出入参,完成代码参考github上的源码
//包名
package proto
//接口请求入参
type HelloRequest struct {
    Request    string 
}
//接口返回出参
type HelloResponse struct {
    Response     string   
}
//生成的helloServce的客户端接口,就跟java的api是的,让别人引用
type HelloServiceClient interface {
    //一个简单的
    rpc HelloWorld(in *HelloRequest) (*HelloResponse, error)
    //一个服务器端流式
    rpc HelloWorldServerStream(in *HelloRequest) (ServerStream,error)
    //一个客户端流式
    rpc HelloWorldClientStream() (ClientStream, error)
    //一个客户端和服务器端双向流式
    rpc HelloWorldClientAndServerStream() (ServerClientStream error)
}
//helloServiceClient结构体
type helloServiceClient struct {
    cc *grpc.ClientConn
}
//获取客户端存根,入参是一个client连接
func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {        
    return &helloServiceClient{cc}
}

//这是服务器端的接口,我们的服务端需要实现这些接口
type HelloServiceServer interface {
    //一个简单的
    rpc HelloWorld(in *HelloRequest) (*HelloResponse, error)
    //一个服务器端流式
    rpc HelloWorldServerStream(*HelloRequest, StreamServer) error
    //一个客户端流式
    rpc HelloWorldClientStream(ClientStream) error
    //一个客户端和服务器端双向流式
    rpc HelloWorldClientAndServerStream (ClientServerStream) error
}

编写服务端代码

  • 在server文件夹下新建hello_world_server.go文件,按如下步骤进行
    1. 创建HelloWorldServer结构体
    2. 实现pb.go文件中的HelloServiceServer接口(实现所有方法)
    3. StartServer(开启服务)
  • 详细代码依然看github
type HelloServiceServer struct {}

//简单rpc实现
func (*HelloServiceServer) HelloWorld() {
    log.Printf("%v",req.Request)
    return &pb.HelloResponse{Response:"hello my is gRpcServer"}, nil
}
//服务端流式rpc实现
func (*HelloServiceServer) HelloWorldServerStream() error {
    log.Printf("%v",req.Request)
    srv.Send(&pb.HelloResponse{Response:"hello my is gRpcServer stream"})
    return nil
}
//客户端流式rpc实现
func (*HelloServiceServer) HelloWorldClientStream() error {
    for{
       //不断接受客户端发送的消息
        req,err := srv.Recv()
        //直到发送结束终止for循环
        if err != nil && err.Error() == "EOF"{
            break
        }
        if err != nil{
            log.Fatalf("%v",err)
            break
        }else{
            log.Printf("%v",req.Request)
        }
    }
    //返回给客户端处理结果
    srv.SendAndClose(&pb.HelloResponse{Response:"hello my is gRpcServer"})
    return nil
}
//双向流式rpc
func (*HelloServiceServer) HelloWorldClientAndServerStream(){
    for{
        //接受客户端消息
        req,err := srv.Recv()
        if err != nil && err.Error() == "EOF"{
            break
        }
        if err != nil{
            log.Fatalf("%v",err)
            break
        }else{
            log.Printf("%v",req.Request)
           //返回客户端结果 
            srv.Send(&pb.HelloResponse{Response:"hello my is gRpcServer stream"})
        }
    }
    return nil
}
func StartServer(){
    //开启服务端监听
    lis, err := net.Listen("tcp", "127.0.0.1:8090")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    //创建grpcServer
    gRpcServer := grpc.NewServer()
    //注册服务实现
    pb.RegisterHelloServiceServer(gRpcServer, &HelloServiceServer{})
    //启动服务
    gRpcServer.Serve(lis)
}

编写客户端

  • 在client目录下创建hello_world_client.go文件
func StartClient(){
    //连接grpc的服务端
    conn, err := grpc.Dial("127.0.0.1:8090",grpc.WithInsecure())
    //创建客户端存根对象
    c := pb.NewHelloServiceClient(conn)
    // Contact the server and print out its response.
    //设置超时时间
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    //调用服务端的方法
    r, err := c.HelloWorldClientAndServerStream(ctx,grpc.EmptyCallOption{})
    if err != nil{
        log.Fatalf("%v", err)
        return
    }
    //客户端往服务端发送10次消息
    for i:=0;i<10;i++{
        r.Send(&pb.HelloRequest{Request:"my is golang gRpc client"})
    }
    //发送完毕
    r.CloseSend()
    //循环接受服务端返回的结果,直到返回EOF
    for{
        res,err := r.Recv()
        if err != nil && err.Error() == "EOF"{
            break
        }
        if err != nil {
            log.Fatalf("%v", err)
            break
        }
        log.Printf("result:%v",res.Response)
    }
    //关闭连接
    defer conn.Close()
}

github代码运行说明

  • github代码截图
  • 先启动 hello_world_server_test.go
  • 再启动hello_world_client_test.go
  • 如遇到启动的任何问题可留言

【GoLang那点事】实践gRPC之GoLang入门HelloWord

欢迎大家关注微信公众号:“golang那点事”,更多精彩期待你的到来

【GoLang那点事】实践gRPC之GoLang入门HelloWord