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-status
和 grpc-message
)。然后,我们会收到两个标志——一个表示这是流的结束(在我们的例子中是请求/响应),另一个表示这个 trailer 到此为止。
区别总结
操作 | 作用 | 发送内容 | 特点 |
---|---|---|---|
Send Header |
发送请求的元数据(头部信息) |
请求的基本信息(服务名、方法名等) |
初始化调用,包含元数据,不包含实际的数据负载。 |
Send Message |
发送实际的数据 |
Protobuf 序列化的数据(请求或响应) |
发送实际的请求或响应数据,数据为 Protobuf 格式。 |
Send Half Close |
关闭输入流或输出流(标志) |
布尔值表示客户端已完成数据发送 |
通常表示客户端请求结束,服务器可以开始处理,但不会再发送更多数据。 |
Send Trailer |
发送调用的最终状态和结束标志 |
调用状态(grpc-status, grpc-message) |
发送调用的状态信息,标志着流的结束,提供状态码和错误信息(如果有的话)。 |