英文:
Basic changes to Hello World GRPC
问题
我正在使用Golang
的gRPC
。
在成功运行了他们的HelloWorld教程
后,我需要添加一个第二个函数,编译protoc文件,我决定进行一些进一步的更改。
我的代码包含在下面,但似乎不起作用,因为它期望pb
变量是一个HelloWorld对象,或者其他什么东西,如下面命令行中的错误消息所示。请问有人可以告诉我我哪里出错了,因为应该只是一些很小的改动。
我已经在原始帖子的代码中修复了一些拼写错误。
命令行:
go run server/server.go
# command-line-arguments
server/server.go:24:71: undefined: helloworld.TheGRPCNotificationMessage
命令行protoc文件:
> protoc --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)
}
}
我希望这个示例教程不要把我粘得这么紧!
英文:
I am using gRPC
with Golang
.
After successfully running their HelloWorld tutorial
which required me to add a second function, compile the protoc file, I decided to make some further changes.
My code is included, which appears not to work because it expects the pb
variable to be a HelloWorld object, or something, as shown in the error message in command line below. Could someone please tell me where I have gone wrong, for what should be some very small changes.
I have fixed some typos in my original post's code below.
Command line:
go run server/server.go
# command-line-arguments
server/server.go:24:71: undefined: helloworld.TheGRPCNotificationMessage
Command line protoc file:
> protoc --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)
}
}
I wish the example tutorial did not glue me to it so roughly!
答案1
得分: 2
你的go_package
、client.go
和server.go
包导入是错误的。这是gRPC中一个比较棘手的部分,因为协议必须支持许多不同的语言和它们的包解决方案。
你还重命名了服务和方法,所以不能再使用GreeterService
,因为它已经不存在了。大多数编辑器(如VSCode)会帮助你导航到包以查找命名错误。
pb "google.golang.org/grpc/examples/helloworld/helloworld"
- 假设你的代码在一个名为
${REPO}
的Go模块中(可能是形如github.com/YOUR-ORG/YOUR-REPO
的形式),你需要执行以下命令:
go mod init ${REPO}
- 在proto文件中,对于Golang(其他语言由你解决),你需要添加以下内容:
option go_package = "${REPO}/helloworld";
注意:将
${REPO}
替换为实际的值,即github.com/....
-
我建议你在proto包名
grpcnotificationpackage
和生成的代码包名之间保持一致,但目前你的proto包名是grpcnotificationpackage
,而你正在将代码生成到helloworld
中。 -
在仓库中创建一个名为
helloworld
的目录。你要求protoc
将Go源代码(go_package
)生成到名为${REPO}/helloworld
的包中,而Go要求文件位于仓库中名为helloworld
的目录中。 -
然后执行以下命令:
protoc \
--go_out=${PWD}/helloworld \
--go_opt=paths=source_relative \
--go-grpc_out=${PWD}/helloworld \
--go-grpc_opt=paths=source_relative \
GRPCNotification.proto
这个命令有多种变体,但是这个命令将在你的仓库中的${PWD}/helloworld
目录中生成源代码。
- 然后你需要正确导入生成的代码:
import (
...
${REPO}/helloworld
)
- 接下来你需要修正引用。如果你将服务命名为
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"
)
// 确保 `server` 实现了接口
var _ pb.TheGRPCNotificationServiceServer = &server{}
// server 用于实现 helloworld.GreeterServer
type server struct {
pb.UnimplementedTheGRPCNotificationServiceServer
}
// 这是方法的正确签名
func (s *server) ReceiveNotificationWithGRPC(
stream pb.TheGRPCNotificationService_ReceiveNotificationWithGRPCServer,
) error {
// 实现方法
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)
}
}
- 我不建议使用
returns (google.protobuf.Empty)
。我不知道是否有最佳实践,但我认为最好返回一个不包含字段的消息,例如:
rpc Foo (FooRequest) returns (FooResponse);
message FooResponse {}
- 考虑使用诸如gRPCurl之类的工具进行调试:
在这个例子中,gRPCurl
使用proto而不是服务来枚举服务(在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
英文:
Your go_package
and client.go
and server.go
package imports are incorrect. This is a "fiddly" part of gRPC but it results because the protocol must support many (varied) languages and their package solutions.
You've also renamed the service and methods so can't use e.g. GreeterService
as this no longer exists. Most editors (e.g. VSCode) will help you navigate packages to find naming errors.
pb "google.golang.org/grpc/examples/helloworld/helloworld"
- Assuming your code is in a Go Module called
${REPO}
(and probably something of the formgithub.com/YOUR-ORG/YOUR-REPO
, i.e. you:
go mod init ${REPO}
- In the proto file, for Golang (I'll leave the other languages for you to solve), you'll want:
option go_package = "${REPO}/helloworld";
> NOTE Replace ${REPO}
with its value i.e. github.com/....
-
I would encourage you to be consistent with proto package name name
grpcnotificationpackage
and your generated code package names but, for now, your proto package isgrpcnotificationpackage
but you're generating code intohelloworld
. -
Create a directory in the repo called
helloworld
. You're askingprotoc
to generate Go source (go_package
) into a package called${REPO}/helloworld
and Go requires that the files be in a directory calledhelloworld
in your repo. -
Then:
protoc \
--go_out=${PWD}/helloworld \
--go_opt=paths=source_relative \
--go-grpc_out=${PWD}/helloworld \
--go-grpc_opt=paths=source_relative \
GRPCNotification.proto
There are various flavors of this command but this will generate the sources into ${PWD}/helloworld
in your repo.
- Then you must import correctly:
import (
...
${REPO}/helloworld
)
- Then you must correct your references. If you call a service
TheGRPCNotificationService
and a methodSendNotificationWithGRPC
and a messageTheGRPCNotificationMessage
, this is what protoc will generate (you can review the generated Go sources to confirm this). So:
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)
}
}
- I don't recommend
returns (google.protobuf.Empty)
. I don't know whether there's a best practice for this but I think it's better to return a message that contains no fields, e.g.:
rpc Foo (FooRequest) returns (FooResponse);
message FooResponse {}
- Consider using a tool such as gRPCurl to debug:
In this example, gRPCurl
uses the proto not the service to enumerate the services (defined in the proto):
grpcurl \
-plaintext \
--proto *.proto \
localhost:50051 list
Yields:
grpcnotificationpackage.TheGRPCNotificationService
Same-same but for the methods for the given service:
grpcurl \
-plaintext \
--proto *.proto \
localhost:50051 list grpcnotificationpackage.TheGRPCNotificationService
Yields the methods:
grpcnotificationpackage.TheGRPCNotificationService.ReceiveNotificationWithGRPC
grpcnotificationpackage.TheGRPCNotificationService.SendNotificationWithGRPC
Invoke the server streaming method:
grpcurl \
-plaintext \
--proto *.proto \
localhost:50051 grpcnotificationpackage.TheGRPCNotificationService.ReceiveNotificationWithGRPC
Yields (correctly an Unimplemented error)
ERROR:
Code: Unimplemented
Message: not implemented
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论