【问题标题】:Basic changes to Hello World GRPCHello World GRPC 的基本更改
【发布时间】:2021-12-06 12:49:17
【问题描述】:

我正在使用 gRPCGolang

在成功运行他们的HelloWorld tutorial 后,需要我添加第二个函数,编译 protoc 文件,我决定进行一些进一步的更改。

我的代码已包含在内,但它似乎无法正常工作,因为它希望 pb 变量是 HelloWorld 对象或其他东西,如下面命令行中的错误消息所示。有人可以告诉我哪里出了问题,应该是一些非常小的变化。

我在下面的原始帖子代码中修正了一些错别字。

命令行:

go run server/server.go
# command-line-arguments
server/server.go:24:71: undefined: helloworld.TheGRPCNotificationMessage

命令行协议文件:

协议 --go_out=。 --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative GRPCNotification/GRPCNotification.proto

GRPCNotification.proto

syntax = "proto3";

import "google/protobuf/empty.proto";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package grpcnotificationpackage;

// The greeting service definition.
service TheGRPCNotificationService {
  // Sends a notification and nothing sent back
  rpc SendNotificationWithGRPC (TheGRPCNotificationMessage) returns (google.protobuf.Empty);
  // Receives a notification and does not reply
  rpc ReceiveNotificationWithGRPC (stream TheGRPCNotificationMessage) returns (google.protobuf.Empty);
}

// The request notification message
message TheGRPCNotificationMessage {
  string message = 1;
  // ... add more here
}

// Since nothing gets returned from server we need this empty
message Empty {
  // Nothing goes here
}

Client.go

// Package main implements a client for Greeter service.
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SendNotificationWithGRPC(ctx, &pb.TheGRPCNotificationMessage{Message: "Horse"})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Sent a message, please check it reached server...")
}

Server.go

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    port = ":50051"
)


// server is used to implement helloworld.GreeterServer
type server struct {
    pb.UnimplementedGreeterServer
}


// SendNotificationWithGRPC implements helloworld.GreeterServer   <--- Problem?
func (s *server) ReceiveNotificationWithGRPC(ctx context.Context, in *pb.TheGRPCNotificationMessage) {
    log.Printf("Received: %v", in.Name)
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

我希望示例教程没有让我如此粗略地粘住它!

【问题讨论】:

    标签: linux go tcp grpc grpc-go


    【解决方案1】:

    您的 go_packageclient.goserver.go 包导入不正确。这是 gRPC 的“繁琐”部分,但它的结果是因为协议必须支持许多(不同的)语言及其包解决方案。

    您还重命名了服务和方法,因此无法使用,例如GreeterService 因为它不再存在。大多数编辑器(例如 VSCode)会帮助您导航包以查找命名错误。

    pb "google.golang.org/grpc/examples/helloworld/helloworld"
    
    1. 假设您的代码位于名为 ${REPO} 的 Go 模块中(可能形式为 github.com/YOUR-ORG/YOUR-REPO,即您:
    go mod init ${REPO}
    
    1. 在 proto 文件中,对于 Golang(我将留给您解决其他语言),您需要:
    option go_package = "${REPO}/helloworld";
    

    注意${REPO}替换为它的值,即github.com/....

    1. 我建议您与原始包名称 grpcnotificationpackage 和您生成的代码包名称保持一致,但目前,您的原始包是 grpcnotificationpackage,但您正在将代码生成到 helloworld

    2. 在 repo 中创建一个名为 helloworld 的目录。您要求 protoc 将 Go 源代码 (go_package) 生成到名为 ${REPO}/helloworld 的包中,而 Go 要求文件位于您的存储库中名为 helloworld 的目录中。

    3. 然后:

    protoc \
    --go_out=${PWD}/helloworld \
    --go_opt=paths=source_relative \
    --go-grpc_out=${PWD}/helloworld \
    --go-grpc_opt=paths=source_relative \
    GRPCNotification.proto
    

    此命令有多种形式,但这会将源代码生成到您的 repo 中的${PWD}/helloworld

    1. 那么你必须正确导入:
    import (
      ...
      ${REPO}/helloworld
    )
    
    1. 那么您必须更正您的参考资料。如果你调用一个服务 TheGRPCNotificationService 和一个方法 SendNotificationWithGRPC 和一个消息 TheGRPCNotificationMessage,这就是 protoc 将生成的(你可以查看生成的 Go 源来确认这一点)。所以:
    package main
    
    import (
        "log"
        "net"
    
        pb "${REPO}/helloworld"
    
        "google.golang.org/grpc"
        "google.golang.org/grpc/codes"
        "google.golang.org/grpc/status"
    )
    
    const (
        port = ":50051"
    )
    
    // Ensure that `server` implements the interface
    var _ pb.TheGRPCNotificationServiceServer = &server{}
    
    // server is used to implement helloworld.GreeterServer
    type server struct {
        pb.UnimplementedTheGRPCNotificationServiceServer
    }
    
    // This is the correct signature of the method
    func (s *server) ReceiveNotificationWithGRPC(
        stream pb.TheGRPCNotificationService_ReceiveNotificationWithGRPCServer,
    ) error {
        // Implement
        return status.Errorf(codes.Unimplemented, "not implemented")
    }
    
    func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer()
        pb.RegisterTheGRPCNotificationServiceServer(s, &server{})
        log.Printf("server listening at %v", lis.Addr())
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }
    
    1. 我不推荐returns (google.protobuf.Empty)。我不知道这是否有最佳实践,但我认为最好返回不包含字段的消息,例如:
    rpc Foo (FooRequest) returns (FooResponse);
    
    message FooResponse {}
    
    1. 考虑使用gRPCurl等工具进行调试:

    在本例中,gRPCurl 使用 proto 而不是 service 来枚举服务(在 proto 中定义):

    grpcurl \
    -plaintext \
    --proto *.proto \
    localhost:50051 list
    

    产量:

    grpcnotificationpackage.TheGRPCNotificationService
    

    相同,但对于给定服务的方法:

    grpcurl \
    -plaintext \
    --proto *.proto \
    localhost:50051 list grpcnotificationpackage.TheGRPCNotificationService
    

    产生方法:

    grpcnotificationpackage.TheGRPCNotificationService.ReceiveNotificationWithGRPC
    grpcnotificationpackage.TheGRPCNotificationService.SendNotificationWithGRPC
    

    调用服务器流式处理方法:

    grpcurl \
    -plaintext \
    --proto *.proto \
    localhost:50051 grpcnotificationpackage.TheGRPCNotificationService.ReceiveNotificationWithGRPC
    

    产量(正确的是未实现的错误)

    ERROR:
      Code: Unimplemented
      Message: not implemented
    

    【讨论】:

    • 感谢您的帮助。你介意给我一个客户端源代码吗,因为上面只有服务器的代码。
    • 我需要一个客户端源代码,以便向服务器发送消息。