服务器模板代码
我们的构建系统已经准备好了,现在我们可以专注于代码部分。但是,在开始之前,我们先来定义一下我们想要的目标。在这一节中,我们希望构建一个可重用的 gRPC 服务器模板,以便在后续章节甚至未来的项目中使用。为了实现这一点,我们希望避免以下几点:
我们可以通过不再关心生成的代码来解决这些问题。生成的代码仅用于测试我们的构建系统。然后,我们将默认使用不安全的连接进行测试。最后,我们将把 IP 地址作为程序的参数。
让我们一步步来实现这些目标:
-
添加 gRPC 依赖
我们首先需要将 gRPC 依赖添加到
server/go.mod
文件中。因此,在server
目录下,我们可以输入以下命令:$ go get google.golang.org/grpc
-
获取程序参数
接下来,我们将获取传递给程序的第一个参数,并在没有传递参数时返回用法消息:
args := os.Args[1:] if len(args) == 0 { log.Fatalln("usage: server [IP_ADDR]") } addr := args[0]
-
监听传入的连接
然后,我们需要使用 Go 提供的
net.Listen
来监听传入的连接。该监听器在程序结束时需要关闭。这个关闭操作可能是在用户终止程序时,或者在服务器出现故障时。显然,如果在构建监听器时出现错误,我们希望程序失败并提示用户:lis, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("failed to listen: %v\n", err) } defer func(lis net.Listener) { if err := lis.Close(); err != nil { log.Fatalf("unexpected error: %v", err) } }(lis) log.Printf("listening at %s\n", addr)
-
创建 gRPC 服务器
现在,借助之前的配置,我们可以开始创建一个
grpc.Server
。首先,我们需要定义一些连接选项。由于这是一个未来项目的模板,我们将保持选项为空。使用grpc.ServerOption
数组,我们可以创建一个新的 gRPC 服务器。这个服务器将在后续步骤中用于注册端点。然后,我们需要在某个时刻关闭服务器,因此我们使用defer
语句来处理。最后,我们调用grpc.Server
上的Serve
函数,传入监听器作为参数。如果发生错误,我们将其返回给客户端:opts := []grpc.ServerOption{} s := grpc.NewServer(opts...) // 注册端点 defer s.Stop() if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v\n", err) }
在完成上述步骤后,我们的
main
函数(server/main.go
)将如下所示:package main import ( "log" "net" "os" "google.golang.org/grpc" ) func main() { args := os.Args[1:] if len(args) == 0 { log.Fatalln("usage: server [IP_ADDR]") } addr := args[0] lis, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("failed to listen: %v\n", err) } defer func(lis net.Listener) { if err := lis.Close(); err != nil { log.Fatalf("unexpected error: %v", err) } }(lis) log.Printf("listening at %s\n", addr) opts := []grpc.ServerOption{} s := grpc.NewServer(opts...) // 注册端点 defer s.Stop() if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v\n", err) } }
现在,我们可以通过在 server/main.go
上运行 go run
命令来启动服务器。我们可以通过使用 【Ctrl + C】 来终止执行:
$ go run server/main.go 0.0.0.0:50051
listening at 0.0.0.0:50051
要停止服务器,可以使用 Ctrl + C
。
这个模板现在可以作为我们后续章节和未来项目的基础。
Bazel
如果你想使用 Bazel
,你需要进行一些额外的步骤。第一步是更新我们根目录下的 BUILD.bazel
文件。在这个文件中,我们将使用一个 Gazelle
命令,它将自动检测项目所需的所有依赖,并将它们输出到一个名为 deps.bzl
的文件中。执行 Gazelle
命令后,我们只需添加以下内容:
gazelle(
name = "gazelle-update-repos",
args = [
"-from_file=go.work",
"-to_macro=deps.bzl%go_dependencies",
"-prune",
],
command = "update-repos",
)
然后,我们可以运行以下命令:
$ bazel run //:gazelle-update-repos
该命令完成后,它将检测到服务器模块的所有依赖并创建一个 deps.bzl
文件,并将其链接到我们的 WORKSPACE.bazel
文件中。你应该在 WORKSPACE.bazel
文件中看到以下内容:
load("//:deps.bzl", "go_dependencies")
# gazelle:repository_macro deps.bzl%go_dependencies
go_dependencies()
接下来,我们可以重新运行 Gazelle
命令,以确保它为我们的服务器创建了 BUILD.bazel
文件。我们运行以下命令:
$ bazel run //:gazelle
然后,server
目录中将生成 BUILD.bazel
文件。需要注意的是,在这个文件中,我们可以看到 Bazel
将 gRPC 连接到 server_lib
。我们应该看到类似这样的内容:
go_library(
name = "server_lib",
srcs = ["main.go"],
deps = [
"@org_golang_google_grpc//:go_default_library",
],
#...
)
现在,我们可以像使用 go run
命令一样运行我们的服务器:
此命令将拉取 |
$ bazel run //server:server 0.0.0.0:50051
listening at 0.0.0.0:50051
到此为止,我们已经完成了服务器的设置。这个简单的模板将使我们能够在接下来的章节中轻松地创建新的服务器。它正在指定的端口上监听并等待请求。接下来,为了简化请求的发送,我们将为客户端创建一个模板。