RPC 操作

每次通过 gRPC 进行的服务器和客户端之间的交互,都可以通过四种 RPC 操作来描述。这些操作以一种方式组合在一起,形成框架中的复杂高级操作。接下来我们将介绍这些操作,然后我将解释一个简单的 gRPC 调用是如何使用它们的。

在这一部分,我将使用 Wireshark 对 RPC 调用的捕获结果。我稍后会在书中解释如何复现这一部分的操作。现在,我只是会重点指出在数据包中需要注意的关键内容。

发送头部(Header)

Send Header 操作让服务器知道客户端将发送请求,或者让客户端知道服务器将发送响应。这个操作充当服务器和客户端之间的开关,使双方都知道谁需要读取,谁需要写入。

通过使用 Wireshark 分析一个简单的 gRPC 调用,我们可以观察到客户端发送的以下头部(简化版),用来通知服务器它将发送请求:

HyperText Transfer Protocol 2
    Stream: HEADERS, Stream ID: 1, Length 67, POST
        /greet.GreetService/Greet
        Flags: 0x04, End Headers
            00.0 ..0. = Unused: 0x00
            ..0. .... = Priority: False
            .... 0... = Padded: False
            .... .1.. = End Headers: True
            .... ...0 = End Stream: False
        Header: :method: POST
        Header: content-type: application/grpc

需要注意的是,在这个头部中,它提到客户端希望在 /greet.GreetService/Greet 路由上调用 HTTP POST,并且在标志中提到这是头部数据的结束。

然后,在调用的后续阶段,我们将看到以下由服务器发送的头部(简化版),用于通知客户端它将发送响应:

HyperText Transfer Protocol 2
    Stream: HEADERS, Stream ID: 1, Length 14, 200 OK
        Flags: 0x04, End Headers
            00.0 ..0. = Unused: 0x00
            ..0. .... = Priority: False
            .... 0... = Padded: False
            .... .1.. = End Headers: True
            .... ...0 = End Stream: False
        Header: :status: 200 OK
        Header: content-type: application/grpc

在这里,我们再次看到这是一个头部,并且这是最后一个将发送的头部。主要的区别是,服务器告诉客户端请求已经正确处理,并通过发送 200 状态码来表示这一点。

发送消息(Message)

Send Message 操作是实际发送数据的操作。作为 API 开发者,这个操作对我们最为重要。客户端在发送头部后,可以将消息作为请求发送,服务器也可以将消息作为响应发送。

通过使用 Wireshark 分析与我们之前分析的 "Send Header" 相同的 gRPC 调用,我们可以观察到客户端作为请求发送的以下数据(简化版):

GRPC Message: /greet.GreetService/Greet, Request
    0... .... = Frame Type: Data (0)
    .... ...0 = Compressed Flag: Not Compressed (0)
    Message Length: 9
    Message Data: 9 bytes
Protocol Buffers: /greet.GreetService/Greet,request
    Message: <UNKNOWN> Message Type
        Field(1):
            [Field Name: <UNKNOWN>]
            .000 1... = Field Number: 1
            .... .010 = Wire Type: Length-delimited (2)
            Value Length: 7
            Value: 436c656d656e74

需要注意的是,在这个头部中提到客户端在 /greet.GreetService/Greet 路由上发送数据,这与在头部中发送的路由相同。然后,我们可以看到我们正在发送 Protocol Buffers 数据(稍后会详细介绍),并且该消息的二进制值是 436c656d656e74

接着,在调用的后续部分,服务器头部发送之后,我们看到以下数据(简化后)作为响应由服务器发送:

GRPC Message: /greet.GreetService/Greet, Response
    0... .... = Frame Type: Data (0)
    .... ...0 = Compressed Flag: Not Compressed (0)
    Message Length: 15
    Message Data: 15 bytes
Protocol Buffers: /greet.GreetService/Greet,response
    Message: <UNKNOWN> Message Type
        Field(1):
            [Field Name: <UNKNOWN>]
            .000 1... = Field Number: 1
            .... .010 = Wire Type: Length-delimited (2)
            Value Length: 13
            Value: 48656c6c6f20436c656d656e74

在这里,我们可以看到这是一个响应消息,针对在路由 /greet.GreetService/Greet 上发出的调用,并且该消息的二进制值是 48656c6c6f20436c656d656e74

发送半关闭(Half Close)

Send Half Close 操作关闭了一个参与者的输入或输出。例如,在传统的请求/响应场景中,当客户端完成发送请求时,发送 Half Close 会关闭客户端流。这有点类似于 Send Header,因为它充当了一个开关,告诉服务器现在是时候开始处理了。

再次,如果我们查看相同 gRPC 调用的 Wireshark 会话记录,我们应该能够看到在 Send Message 操作期间设置了一个头部。我们可以观察到以下数据(简化版):

HyperText Transfer Protocol 2
    Stream: DATA, Stream ID: 1, Length 14
        Length: 14
        Type: DATA (0)
        Flags: 0x01, End Stream
            0000 .00. = Unused: 0x00
            .... 0... = Padded: False
            .... ...1 = End Stream: True

这次,我们有一个标志表示请求的结束。然而,请注意,在这里我们发送的是类型为 DATA 的有效负载。这与我们之前看到的不同,因为 DATA 比头部要轻得多。它用于 Half Close,因为我们只是想发送一个布尔值,表示客户端已经完成。

发送尾部(Trailer)

最后,我们有一个用于终止整个 RPC 的操作,这就是 Send Trailer 操作。这个操作还为我们提供了关于调用的更多信息,比如状态码、错误信息等等。在本书的这一部分,我们只需要知道,这些信息主要用于处理 API 错误。

如果我们查看相同的 Wireshark 调用,我们将看到以下数据(简化版):

HyperText Transfer Protocol 2
    Stream: HEADERS, Stream ID: 1, Length 24
        Length: 24
        Type: HEADERS (1)
        Flags: 0x05, End Headers, End Stream
            00.0 ..0. = Unused: 0x00
            ..0. .... = Priority: False
            .... 0... = Padded: False
            .... .1.. = End Headers: True
            .... ...1 = End Stream: True
        Header: grpc-status: 0
        Header: grpc-message:

请注意,trailer 本质上是一个头部。通过这个头部,我们将获得更多关于调用的信息(如 grpc-statusgrpc-message)。然后,我们会收到两个标志——一个表示这是流的结束(在我们的例子中是请求/响应),另一个表示这个 trailer 到此为止。

区别总结

操作 作用 发送内容 特点

Send Header

发送请求的元数据(头部信息)

请求的基本信息(服务名、方法名等)

初始化调用,包含元数据,不包含实际的数据负载。

Send Message

发送实际的数据

Protobuf 序列化的数据(请求或响应)

发送实际的请求或响应数据,数据为 Protobuf 格式。

Send Half Close

关闭输入流或输出流(标志)

布尔值表示客户端已完成数据发送

通常表示客户端请求结束,服务器可以开始处理,但不会再发送更多数据。

Send Trailer

发送调用的最终状态和结束标志

调用状态(grpc-status, grpc-message)

发送调用的状态信息,标志着流的结束,提供状态码和错误信息(如果有的话)。