通過(guò)抓包來(lái)認(rèn)識(shí)gRpc
在使用gRpc的過(guò)程中,有一個(gè)想法:gRpc客戶(hù)端、服務(wù)端是怎么交互的呢?
從這個(gè)想法萌生出一個(gè)驗(yàn)證方法,通過(guò)抓包來(lái)分析其交互過(guò)程與底層數(shù)據(jù),一起來(lái)看看吧。
1. gRpc是什么
gRpc是什么?
gRPC是一個(gè)高性能、開(kāi)源和通用的 RPC 框架,面向移動(dòng)和 HTTP/2 設(shè)計(jì)。目前提供 C、Java 和 Go 語(yǔ)言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持。
gRPC基于 HTTP/2 標(biāo)準(zhǔn)設(shè)計(jì),帶來(lái)諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多復(fù)用請(qǐng)求等特。這些特性使得其在移動(dòng)設(shè)備上表現(xiàn)更好,更省電和節(jié)省空間占用。
一句話(huà)概括:gRpc是Google基于 HTTP/2 + ProtoBuf 開(kāi)源的一個(gè)RPC框架。
2. 準(zhǔn)備工作
正所謂:欲善其事必先利其器,所以在開(kāi)始抓包之前需要做好如下準(zhǔn)備:
抓包軟件:wireshark
代碼:gRpc代碼
操作系統(tǒng):Windows、Linux、Macos 其一
3. wireshark安裝
官網(wǎng)下載地址:
https://www.wireshark.org/download.html

wireshark
4. gRpc示例代碼
示例代碼目錄結(jié)構(gòu)
- .
 - └── helloworld
 - ├── client
 - │ └── main.go
 - ├── go.mod
 - ├── go.sum
 - ├── proto
 - │ ├── helloworld.pb.go
 - │ └── helloworld.proto
 - └── server
 - └── main.go
 
helloworld.proto
- syntax = "proto3";
 - package proto;
 - // The greeting service definition.
 - service Greeter {
 - // Sends a greeting
 - rpc SayHello (HelloRequest) returns (HelloReply) {}
 - }
 - // The request message containing the user's name.
 - message HelloRequest {
 - string name = 1;
 - int32 age = 2;
 - }
 - // The response message containing the greetings
 - message HelloReply {
 - string message = 1;
 - string address = 2;
 - }
 
通過(guò)protoc對(duì)proto文件生成go代碼
- cd proto
 - protoc --go_out=plugins=grpc:. ./*
 
服務(wù)端代碼 - server/main.go
- package main
 - import (
 - "context"
 - "log"
 - "net"
 - pb "github.com/ivansli/grpc_helloworld/proto"
 - "google.golang.org/grpc"
 - )
 - const (
 - port = ":8080"
 - )
 - // server is used to implement helloworld.GreeterServer.
 - type server struct {}
 - // SayHello implements helloworld.GreeterServer
 - func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
 - log.Printf("Received: %v", in.GetName())
 - return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
 - }
 - 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)
 - }
 - }
 
客戶(hù)端代碼 - client/main.go
- package main
 - import (
 - "context"
 - "google.golang.org/grpc/metadata"
 - "log"
 - "os"
 - "time"
 - pb "github.com/ivansli/grpc_helloworld/proto"
 - "google.golang.org/grpc"
 - )
 - const (
 - address = "localhost:8080"
 - defaultName = "world"
 - )
 - func main() {
 - // Set up a connection to the server.
 - conn, err := grpc.Dial(address, grpc.WithInsecure())
 - if err != nil {
 - log.Fatalf("did not connect: %v", err)
 - }
 - defer conn.Close()
 - c := pb.NewGreeterClient(conn)
 - // Contact the server and print out its response.
 - name := defaultName
 - iflen(os.Args) > 1 {
 - name = os.Args[1]
 - }
 - ctx, cancel := context.WithTimeout(context.Background(), time.Second)
 - defer cancel()
 - // !metadata 用于在服務(wù)間傳遞一些參數(shù),注意后面它在交互中出現(xiàn)的位置
 - ctx = metadata.AppendToOutgoingContext(ctx, "metadata", "is metadata")
 - r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
 - if err != nil {
 - log.Fatalf("could not greet: %v", err)
 - }
 - log.Printf("Greeting: %s", r.GetMessage())
 - }
 
5. 步驟
① 運(yùn)行服務(wù)端代碼 server/main.go,監(jiān)聽(tīng)在8080端口
- go run server/main.go
 
② 打開(kāi)wireshark,等待抓包
③ 運(yùn)行客戶(hù)端代碼 client/main.go
- go run client/main.go
 
6. wireshark抓包gRpc交互過(guò)程

wireshark抓包gRpc交互過(guò)程
- 仔細(xì)觀(guān)察Protocol列的協(xié)議類(lèi)型:
 - TCP 傳輸層
 - HTTP2 應(yīng)用層
 - GRPC 應(yīng)用層(基于HTTP/2)
 - GRPC 跟 HTTPS/2 不同之處在于:GRPC協(xié)議中包含了ProtoBuf序列化的數(shù)據(jù),也間接印證了 GRPC = HTTP/2 + ProtoBuf
 
通過(guò)抓包我們發(fā)現(xiàn),客戶(hù)端使用53726端口與監(jiān)聽(tīng)在8080的服務(wù)端進(jìn)行交互以及數(shù)據(jù)傳遞,其過(guò)程大概分為10個(gè)。
① TCP三次握手
② Magic
③ SETTINGS
④ HEADERS
⑤ DATA
⑥ WINDOW_UPDATE, PING
⑦ PING(pong)
⑧ HEADERS, DATA
⑨ WINDOW_UPDATE, PING
⑩ PING(pong)
在抓包的過(guò)程中,發(fā)現(xiàn)應(yīng)用層出現(xiàn)若干不同幀類(lèi)型,分別有:Magic、SETTINGS、HEADERS、DATA、WINDOW_UPDATE、PING。
至于它們的作用,且看下面分析:
Magic

magic幀
Magic幀的主要作用是對(duì)使用HTTP/2雙方協(xié)議的確認(rèn), 是一個(gè)鏈接前言。作用是確定啟用HTTP/2連接。
SETTINGS
SETTINGS的主要作用是設(shè)置這一個(gè)連接的參數(shù),作用于是整個(gè)連接。從圖中可以看到出現(xiàn)了多個(gè)SETTINGS,原因是在發(fā)送完連接前言后,客戶(hù)端、服務(wù)端還需要進(jìn)一步地確定一些信息。

客戶(hù)端發(fā)送給服務(wù)端的SETTINGS

服務(wù)端發(fā)送給客戶(hù)端的SETTINGS
HEADERS
HEADERS的主要作用是存儲(chǔ)和傳遞HTTP的頭信息。

客戶(hù)端發(fā)送給服務(wù)端的HEADERS
可以看到很多重要的信息:
- method
 - scheme
 - path
 - authority
 - content-type
 - user-agent 等
 
這些都是gRpc很重要的基礎(chǔ)屬性。
- 注意:
 - 使用
 - google.golang.org/grpc/metadata包metadata在客戶(hù)端、服務(wù)端傳遞的數(shù)據(jù),也在HEADERS中
 

服務(wù)端發(fā)送給客戶(hù)端的HEADERS
仔細(xì)觀(guān)察,服務(wù)端發(fā)送給客戶(hù)端的HEADERS幀中 HEADERS數(shù)據(jù)分為兩部分:
- HTTP的響應(yīng)狀態(tài)及內(nèi)容(第一個(gè) Stream:HEADERS)
 
- status: 200
 - content-type: application/grpc
 
- gRpc承載的狀態(tài)信息(第二個(gè) Stream:HEADERS,圖片中框起的部分)
 
- grpc-status: 0
 - grpc-message:
 
DATA
DATA的主要作用是填充主體信息,是數(shù)據(jù)幀。

客戶(hù)端發(fā)送給服務(wù)端的DATA
其中,包含兩個(gè)重要部分:
① GRPC Message
/proto.Greeter/SayHello 為proto中service定義的的方法,也是服務(wù)端對(duì)外提供的方法。
② Protocol Buffers
與protobuf有關(guān),其中Field(1)為定義的pb.HelloRequest結(jié)構(gòu)中Name參數(shù) &pb.HelloRequest{Name: "world"} 。
- 注意:
 - ① pb.HelloRequest{} 結(jié)構(gòu)體中的age字段由于是零值,傳輸時(shí)被忽略,所以沒(méi)有Field(2)
 - ② 帶有request標(biāo)識(shí),說(shuō)明這是客戶(hù)端發(fā)起的請(qǐng)求
 

服務(wù)端發(fā)送給客戶(hù)端的DATA
與客戶(hù)端發(fā)送給服務(wù)端的DATA類(lèi)似,其中 Field(1) 為 &pb.HelloReply{Message: "Hello world"}的Message字段。
- 注意:
 - ① pb.HelloReply{} 結(jié)構(gòu)體中的address字段由于是零值,傳輸時(shí)被忽略,所以沒(méi)有Field(2)
 - ② 帶有response標(biāo)識(shí),說(shuō)明這是個(gè)響應(yīng)信息
 
WINDOW_UPDATE
WINDOW_UPDATE的主要作用是管理流控制窗口。

客戶(hù)端發(fā)送給服務(wù)端的WINDOW_UPDATE

服務(wù)端發(fā)送給客戶(hù)端的WINDOW_UPDATE
PING
主要作用是用于判斷當(dāng)前連接是否依舊可用,相當(dāng)于心跳,分為:
- 客戶(hù)端ping服務(wù)端,服務(wù)端pong
 - 服務(wù)端ping客戶(hù)端,客戶(hù)端pong
 

服務(wù)端發(fā)送給客戶(hù)端的PING

客戶(hù)端響應(yīng)給服務(wù)端的PONG
- 注意:同一個(gè)Ping/Pong,有相同的標(biāo)識(shí)字符串(圖示中標(biāo)識(shí)為:02041010090e0707)
 
總結(jié)
我們通過(guò)抓包gRpc的交互過(guò)程,分析不同類(lèi)型幀的作用,進(jìn)一步了解了gRpc。
總結(jié)如下:
- gRpc在三次握手之后,客戶(hù)端/服務(wù)端會(huì)發(fā)送連接前言(Magic+SETTINGS)以確立協(xié)議和配置
 - gRpc在傳輸數(shù)據(jù)過(guò)程中會(huì)設(shè)計(jì)滑動(dòng)窗口(WINDOW_UPDATE)等流控策略
 - gRpc附加信息基于HEADERS幀進(jìn)行傳遞,具體的請(qǐng)求/響應(yīng)數(shù)據(jù)存儲(chǔ)在DATA幀中
 - gRpc請(qǐng)求/響應(yīng)結(jié)果分為HTTP和gRpc狀態(tài)響應(yīng)(grpc-status、grpc-message)兩種類(lèi)型
 - 如果服務(wù)端發(fā)起PING,客戶(hù)端會(huì)響應(yīng)PONG,反之亦然
 















 
 
 







 
 
 
 