> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# 使用 wireshark 抓包GRPC
- URL: https://huizhou92.com/how-to-capture-and-analyze-grpc-packets/
- Published: 2026-09-06T11:40:57.000Z
- Updated: 2026-09-08T02:07:56.000Z
- Description: 使用 wireshark 抓包GRPC. 摘要 wireshark 是一个 流行的抓取网络报文的工具,他不仅自己可以抓包，也可以解析tcpdump抓包的文件。 gRPC 是Google开发的一个高性能RPC框架，基于HTTP/2协议+protobuf序列化协议. 本文主要介绍如何使用wireshark抓取gR。
- Author: huizhou92
- Tags: tech, grpc, Wireshark, network, #lang-zh-cn

## 摘要

[wireshark](https://en.wikipedia.org/wiki/Wireshark?ref=huizhou92.com) 是一个 流行的抓取网络报文的工具,他不仅自己可以抓包，也可以解析tcpdump抓包的文件。  
[gRPC](https://grpc.io/?ref=huizhou92.com) 是Google开发的一个高性能RPC框架，基于HTTP/2协议+protobuf序列化协议.  
本文主要介绍如何使用wireshark抓取gRPC的报文，并解析报文内容。

> This article is first published in the medium MPP plan. If you are a medium user, please follow me in [medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

Wireshark version: 4.2.2

## 配置

因为gRPC 是基于protobuf序列化协议，所以我们需要先添加protobuf的文件地址。  
点击 Wireshark -> Preferences... -> Protocols -> Protobuf -> Protobuf search paths -> Edit...  
点击+ 添加您要抓包的protobuf 文件路径，不要忘记勾选右边的 Load all files  
![Pasted image 20240511225144](https://images.yixiao9206.cn/blog-images/2024/05/1a9713fe0a3351183153022057ec8fd0.png)

## 具体操作

首先我们写一个最简单的gRPC服务，

```proto
syntax = "proto3";  
option go_package = "example.com/hxzhouh/go-example/grpc/helloworld/api";  
package api;  
  
// 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;  
}  
  
// The response message containing the greetings  
message HelloReply {  
  string message = 1;  
}

```

它仅仅就一个函数 `Greeter` ,补充完服务端代码，把它运行起来。

```go
type server struct {  
    api.UnimplementedGreeterServer  
}  
  
func (s *server) SayHello(ctx context.Context, in *api.HelloRequest) (*api.HelloReply, error) {  
    log.Printf("Received: %v", in.GetName())  
    return &api.HelloReply{Message: "Hello " + in.GetName()}, nil  
}  
  
func main() {  
    lis, err := net.Listen("tcp", ":50051")  
    if err != nil {  
       log.Fatalf("failed to listen: %v", err)  
    }  
    s := grpc.NewServer()  
    api.RegisterGreeterServer(s, &server{})  
    if err := s.Serve(lis); err != nil {  
       log.Fatalf("failed to serve: %v", err)  
    }  
}

```

然后我们打开 wireshark ，选择本地网卡，监听 tcp.port == 50051

> 如果您以前没接触过 wireshark，我建议您先看看这篇文章：[https://www.lifewire.com/wireshark-tutorial-4143298](https://www.lifewire.com/wireshark-tutorial-4143298?ref=huizhou92.com)

![Pasted image 20240511230443](https://images.yixiao9206.cn/blog-images/2024/05/4eeb7c7807a7631de7fe25d9fdbf47d3.png)  
![Pasted image 20240511230512](https://images.yixiao9206.cn/blog-images/2024/05/cb06c62507b550a76232004d2a577f03.png)

### 一元函数

现在我们有一个gRPC 服务运行再本地的`50051` 端口， 我们可以使用`BloomRPC` 或者其他您任何喜欢的工具对服务端发起一个RPC请求,或者直接像我一样使用下面的代码

```go
func Test_server_SayHello(t *testing.T) {  
    // Set up a connection to the server.  
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock())  
    if err != nil {  
       log.Fatalf("did not connect: %v", err)  
    }  
    defer conn.Close()  
    c := api.NewGreeterClient(conn)  
  
    // Contact the server and print out its response.  
    name := "Hello"  
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)  
    defer cancel()  
    r, err := c.SayHello(ctx, &api.HelloRequest{Name: name})  
    if err != nil {  
       log.Fatalf("could not greet: %v", err)  
    }  
    log.Printf("Greeting: %s", r.GetMessage())  
}

```

这个时候，wireshark 应该就能抓到流量包了。  
![Pasted image 20240511232027](https://images.yixiao9206.cn/blog-images/2024/05/7bc4dfc35da907893667cd8d2054a170.png)  
前面我们说过，gRPC = http2+protobuf, 并且我们前面已经加载了protobuf 文件，理论上我们现在已经能解析报文了。  
使用wireshark快捷键 `shift+command+U` 或者 用鼠标点击 `Analyze -> Decode As...` 然后设置一下将报文解析成HTTP2 格式。  
![Pasted image 20240511232316](https://images.yixiao9206.cn/blog-images/2024/05/cf93744e0a24f21827748b9c3c2d1953.png)  
这个时候，我们就能很清晰的看到这个请求了

![Pasted image 20240511232448](https://images.yixiao9206.cn/blog-images/2024/05/143b988235f1d9f4123c2e7a3aed4da6.png)  
![Pasted image 20240511232507](https://images.yixiao9206.cn/blog-images/2024/05/8e4a2e3af863de81124d29cd997b8496.png)  
![Pasted image 20240511232539](https://images.yixiao9206.cn/blog-images/2024/05/7470e60a7cd0364702e9cf56201168bd.png)

### metadata

我们知道 gRPC 的metadata 是通过 http2 的header 来传递的。 现在我们通过抓包来验证一下。  
稍微改造一下客户端代码

```go
func Test_server_SayHello(t *testing.T) {  
    // Set up a connection to the server.  
   ..... 
   // add md 
    md := map[string][]string{"timestamp": {time.Now().Format(time.Stamp)}}  
    md["testmd"] = []string{"testmd"}  
    ctx := metadata.NewOutgoingContext(context.Background(), md)  
    // Contact the server and print out its response.  
    name := "Hello"  
    ctx, cancel := context.WithTimeout(ctx, time.Second)  
   ....
}

```

然后重新抓包。 我们就能看到 md 确实放在 header 里面。  
![Pasted image 20240512154517](https://images.yixiao9206.cn/blog-images/2024/05/36eefdf35b8f66f40824a5702409a44c.png)

并且我们还在header 看到了`grpc-timeout ` 可见请求超时操作也是房子啊header 里面的。里面涉及的具体细节，我可能会出一篇专门的文章来说明，今天我们只关注抓包。

### TLS

上面使用的例子都是明文 传输的 我们再Dial 的时候使用了` grpc.WithInsecure()` ,但是在生产环境中，我们一般使用TLS 对进行加密传输。具体的细节可以参考我以前写的文章。  
**https://huizhou92.com**/content/files/2026/09/secure-communication-with-grpc-from-ssl-tls-certification-to-san-certification-d9464c3d706f.html  
我们改造一下 服务端代码  
[https://gist.github.com/hxzhouh/e08546cf0457d28a614d59ec28870b11](https://gist.github.com/hxzhouh/e08546cf0457d28a614d59ec28870b11?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/e08546cf0457d28a614d59ec28870b11?ref=huizhou92.com)

```go
func main() {  
  
    certificate, err := tls.LoadX509KeyPair("./keys/server.crt", "./keys/server.key")  
    if err != nil {  
       log.Fatalf("Failed to load key pair: %v", err)  
    }  
    certPool := x509.NewCertPool()  
    ca, err := os.ReadFile("./keys/ca.crt")  
    if err != nil {  
       log.Fatalf("Failed to read ca: %v", err)  
    }  
  
    if ok := certPool.AppendCertsFromPEM(ca); !ok {  
       log.Fatalf("Failed to append ca certificate")  
    }  
  
    opts := []grpc.ServerOption{  
       grpc.Creds( // 为所有传入的连接启用TLS  
          credentials.NewTLS(&tls.Config{  
             ClientAuth:   tls.RequireAndVerifyClientCert,  
             Certificates: []tls.Certificate{certificate},  
             ClientCAs:    certPool,  
          },  
          )),  
    }  
  
    listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", 50051))  
    if err != nil {  
       log.Fatalf("failed to listen %d port", 50051)  
    }  
    // 通过传入的TLS服务器凭证创建新的gRPC服务实例  
    s := grpc.NewServer(opts...)  
    api.RegisterGreeterServer(s, &server{})  
    log.Printf("server listening at %v", listen.Addr())  
    if err := s.Serve(listen); err != nil {  
       log.Fatalf("Failed to serve: %v", err)  
    }  
}

```

client  
[https://gist.github.com/hxzhouh/46a7a31e2696b87fe6fb83c8ce7e036c](https://gist.github.com/hxzhouh/46a7a31e2696b87fe6fb83c8ce7e036c?ref=huizhou92.com)  
[View code snippet on GitHub Gist →](https://gist.github.com/hxzhouh/46a7a31e2696b87fe6fb83c8ce7e036c?ref=huizhou92.com)

```go
func Test_server_SayHello(t *testing.T) {  
    certificate, err := tls.LoadX509KeyPair("./keys/client.crt", "./keys/client.key")  
    if err != nil {  
       log.Fatalf("Failed to load client key pair, %v", err)  
    }  
  
    certPool := x509.NewCertPool()  
    ca, err := os.ReadFile("./keys/ca.crt")  
    if err != nil {  
       log.Fatalf("Failed to read %s, error: %v", "./keys/ca.crt", err)  
    }  
  
    if ok := certPool.AppendCertsFromPEM(ca); !ok {  
       log.Fatalf("Failed to append ca certs")  
    }  
  
    opts := []grpc.DialOption{  
       grpc.WithTransportCredentials(credentials.NewTLS(  
          &tls.Config{  
             ServerName:   "localhost",  
             Certificates: []tls.Certificate{certificate},  
             RootCAs:      certPool,  
          })),  
    }  
    // conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))  
    conn, err := grpc.Dial("localhost:50051", opts...)  
    if err != nil {  
       log.Fatalf("Connect to %s failed", "localhost:50051")  
    }  
    defer conn.Close()  
  
    client := api.NewGreeterClient(conn)  
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)  
    defer cancel()  
    r, err := client.SayHello(ctx, &api.HelloRequest{Name: "Hello"})  
    if err != nil {  
       log.Printf("Failed to greet, error: %v", err)  
    } else {  
       log.Printf("Greeting: %v", r.GetMessage())  
    }  
}

```

这个时候我们再抓包，然后使用相同的方式解析。但是，我们会发现，使用HTTP2 已经无法解密了，但是可以解码成 TLS1.3  
![Pasted image 20240519210518](https://images.yixiao9206.cn/blog-images/2024/05/61ece2aeee6ae2976ded717d45f08b52.png)  
![Pasted image 20240519211040](https://images.yixiao9206.cn/blog-images/2024/05/f07e7f5de6fffa9b5c54ea6ce4a1212f.png)

## 总结

这篇文章，首先总结了使用 Wireshark 抓gRPC 包的一个基本流程。  
然后我们通过抓包知道了gRPC的参数传递是通过 HTTP2 的data-frame，CTX 等meta 是通过 header 传递的。这些知识我们以前肯定听过，但是只有动手实验才能加深理解。  
通过TLS 我们可以实现 安全的gRPC 通信，下一篇文章，我们将尝试解密TLS 报文。

## 参考资料

- [Wireshark Tutorial](https://www.wireshark.org/docs/wsug%5Fhtml%5Fchunked/ChapterIntroduction.html?ref=huizhou92.com)
- [https://grpc.io/blog/wireshark/](https://grpc.io/blog/wireshark/?ref=huizhou92.com)
- [https://www.lifewire.com/wireshark-tutorial-4143298](https://www.lifewire.com/wireshark-tutorial-4143298?ref=huizhou92.com)