gRPC 服务实现已准备就绪,下面讨论如何创建应用程序来与该服务器对话。这里先从根据服务定义生成客户端存根开始。基于生成的客户端存根,可以创建简单的 gRPC 客户端,使其连接前面创建的 gRPC 服务器,并调用服务器所提供的远程方法。

本示例将同时使用 Java 和 Go 这两种语言来编写客户端应用程序。但是,这并不代表在实际创建服务器和客户端时,必须使用相同的语言,也不代表它们必须在相同的平台上才能运行。由于 gRPC 能够跨语言和跨平台运行,因此你可以使用 gRPC 所支持的任意语言来创建它们。下面先来看 Go 语言的实现,如果你对 Java 实现更感兴趣,可以跳到第 2 小节,直接学习 Java 客户端。

01. 实现 gRPC 的 Go 客户端

首先创建新的 Go 模块(productinfo/client),并在该模块中创建子目录(ecommerce)。然后,为了实现 Go 客户端应用程序,还需要像实现 Go 服务那样生成存根文件。2.2.1 节介绍了如何生成存根文件,因为遵循相同的步骤来创建相同的文件(product_info.pb.go),所以这里不再赘述。

在 Go 模块中(productinfo/client),创建名为productinfo_client.go 的新 Go 文件,并实现调用远程方法的主方法,如代码清单 2-11 所示。

代码清单 2-11 使用 Go 语言编写的 gRPC 客户端应用程序

package main
import (
    "context"
    "log"
    "time"
    pb "productinfo/client/ecommerce""google.golang.org/grpc"
)
const (
    address = "localhost:50051"
)
func main() {
    conn, err := grpc.Dial(address, grpc.WithInsecure()) ➋
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close() ➐
    c := pb.NewProductInfoClient(conn) ➌
    name := "Apple iPhone 11"
    description := `Meet Apple iPhone 11. All-new dual-camera system with Ultra Wide and Night mode.`
    price := float32(1000.0)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second) ➍
    defer cancel()
    r, err := c.AddProduct(ctx, &pb.Product{Name: name, Description: description, Price: price}) ➎
    if err != nil {
        log.Fatalf("Could not add product: %v", err)
    }
    log.Printf("Product ID: %s added successfully", r.Value)
    product, err := c.GetProduct(ctx, &pb.ProductID{Value: r.Value}) ➏
    if err != nil {
        log.Fatalf("Could not get product: %v", err)
    }
    log.Printf("Product: ", product.String())
}

❶ 导入 protobuf 编译器生成代码所在的包。
❷ 根据提供的地址(localhost: 50051)创建到服务器端的连接。这里创建了一个客户端和服务器端之间的连接,但它目前不安全。
❸ 传递连接并创建存根文件。这个实例包含可调用服务器的所有远程方法。
❹ 创建 Context 以传递给远程调用。这里的 Context 对象包含一些元数据,如终端用户的标识、授权令牌以及请求的截止时间,该对象会在请求的生命周期内一直存在。
❺ 使用商品的详情信息调用 AddProduct 方法。如果操作成功完成,就会返回一个商品 ID,否则将返回一个错误。
❻ 使用商品 ID 来调用 GetProduct 方法。如果操作成功完成,将返回商品详情,否则会返回一个错误。
❼ 所有事情都完成后,关闭连接。

现在,我们已使用 Go 语言构建了 gRPC 客户端,接下来使用 Java语言来创建客户端。这里只是用不同的方式实现相同的目标,也就是说,如果你对使用 Java 构建 gRPC 感兴趣,那么可以继续往下阅读,否则可以跳过这一部分,直接阅读 2.3 节。

02. 实现 gRPC 的 Java 客户端

为了创建 Java 客户端应用程序,需要搭建一个 Gradle 项目(product-info-client),并且要像实现 Java 服务那样使用Gradle 插件来生成类。请按照 2.2.1 节中的步骤搭建 Java 客户端项目。

在通过 Gradle 构建工具生成项目的客户端存根代码之后,接下来在 ecommerce 包中创建名为 ProductInfoClient 的新类,并添加代码清单 2-12 所示的内容。

代码清单 2-12 使用 Java 语言编写的 gRPC 客户端应用程序

package ecommerce;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import java.util.logging.Logger;
/**
* ProductInfo服务的gRPC客户端示例
*/
public class ProductInfoClient {
    public static void main(String[] args) throws InterruptedException {
        ManagedChannel channel = ManagedChannelBuilder
        .forAddress("localhost", 50051) ➊
        .usePlaintext()
        .build();

        ProductInfoGrpc.ProductInfoBlockingStub stub = ProductInfoGrpc.newBlockingStub(channel); ➋
        ProductInfoOuterClass.ProductID productID = stub.addProduct( ➌
        ProductInfoOuterClass.Product.newBuilder()
        .setName("Apple iPhone 11")
        .setDescription("Meet Apple iPhone 11. All-new dual-camera system with Ultra Wide and Night mode.");
        .setPrice(1000.0f)
        .build());

        System.out.println(productID.getValue());
        ProductInfoOuterClass.Product product = stub.getProduct(productID); ➍
        System.out.println(product.toString());
        channel.shutdown(); ➎
    }
}

❶ 创建 gRPC 通道并指定希望连接的服务器地址和端口。这里希望连接在本地机器上运行并监听端口 50051 的服务器。同时启用了明文(plaintext),这意味着在客户端和服务器端之间建立的连接不安全。
❷ 使用新建的通道来创建客户端存根代码,其中包括两种类型:一种是 BlockingStub,它会一直等待,直到接收到服务器的响应为止;另一种是 NonBlockingStub,它不会等待服务器的响应,而会注册一个观察者(observer)来接收响应。本例使用的是BlockingStub,这使客户端更加简单。
❸ 使用商品详情调用 addProduct 方法。如果操作成功完成,会返回商品 ID。
❹ 使用商品 ID 调用 getProduct 方法。如果操作成功完成,会返回商品详情。
❺ 在所有的事情都完成后,关闭连接。这样一来,当应用程序所使用的网络资源用完后,就能够安全回收。

这样就完成了 gRPC 客户端的开发。接下来实现客户端和服务器之间的对话。

文档更新时间: 2023-09-02 04:21   作者:Minho