---
title: "gRPCとは？UDPは使うのかをTCP・HTTP/3で実測｜仕組み・RESTとの違い・実装例"
url: "https://www.issoh.co.jp/tech/details/3404/"
published: 2024-08-27
updated: 2026-10-21
categories: ["プロトコル"]
publisher: "株式会社一創"
---

# gRPCとは？UDPは使うのかをTCP・HTTP/3で実測｜仕組み・RESTとの違い・実装例

gRPCは、Googleが開発したオープンソースのRPC（Remote Procedure Call）フレームワークです。別のサーバーにある関数を手元の関数のように呼び出せ、通信路にHTTP/2、データ形式にProtocol Buffersを使います。「gRPCはUDPを使うのか」という疑問には、標準構成ではUDPを使わずTCPで動く、というのが答えです。この記事では、grpcio 1.84.0のサーバーが実際にTCPだけで待ち受けること、HTTP/3（QUIC）に載せるとgRPCがUDPの上を流れることを手元で計測した結果を示し、仕組み、スタブとチャネル、REST APIとの違い、Pythonでの実装と本番前の設定までを順に扱います。

## まとめ：gRPCはHTTP/2（TCP）上のRPC、UDPを通るのはHTTP/3にしたときだけ

gRPCは、.protoファイルで定義した契約からクライアントとサーバーのコードを生成し、HTTP/2の上でProtocol Buffersのバイナリをやり取りするRPCフレームワークです。2015年にGoogleが公開し、2017年2月からCNCFのincubatingプロジェクトです。HTTP/2はTCPの上で動くので、標準のgRPCはUDPを使いません。今回の計測でも、Pythonのgrpcioサーバーが開いたのは `TCP *:50051` の待ち受けだけでした。

UDPの上を流れるのは、gRPCをHTTP/3（QUIC）に載せたときです。gRPCの提案文書G2は、HTTP/3でもcontent-typeは `application/grpc` のまま変わらず、TLSが必須で平文のチャネルでは使われないと定めています。対応はgrpc-dotnetなど一部の実装に限られ、grpc-goのクライアントは同じ宛先にTCPで接続しようとして失敗しました。サーバー同士の内部通信はHTTP/2のままで足ります。HTTP/3を検討するのは、モバイル回線のように経路が不安定なクライアントを相手にするときです。

ブラウザから直接呼べず、バイナリなので目で追いにくいという弱点もあります。外部に公開するAPIはREST、自社で両端を管理する内部通信はgRPC、という分け方が基本です。

## gRPCとは：Google社内のStubbyからCNCFのOSSになった経緯

gRPCのルーツは、Googleが社内のサービス間通信に長年使ってきた「Stubby」というRPC基盤です。通信部分を標準のHTTP/2に置き換えるなどして整理し、2015年にオープンソースとして公開されました。[CNCF（Cloud Native Computing Foundation）](https://www.cncf.io/projects/grpc/)には2017年2月16日に受け入れられ、2026年10月時点のCNCF Landscapeでも成熟度はincubatingのままです（graduatedではありません）。

RPCは、ネットワーク越しの処理を「関数を呼ぶ」書き方で実行する考え方です。どのURLにどんなJSONを送るかではなく、`GetUser(id)` を呼ぶ感覚でコードを書けます。gRPCはこのRPCを、HTTP/2という通信路とProtocol Buffersというデータ形式の上に作ったものです。言語に依存しないため、Goで書いたサーバーをPythonやJavaのクライアントから呼ぶ構成も自然に組めます。

名前の「g」に決まった意味はありません。公式リポジトリの[g\_stands\_for.md](https://github.com/grpc/grpc/blob/master/doc/g%5Fstands%5Ffor.md)には、リリースごとに別の単語を当てると書かれており、1.0は「gRPC」、1.84は「gimbal」です。

## gRPCの仕組み：HTTP/2・Protocol Buffers・ワイヤ上のメッセージ形式

gRPCの動きは、通信路のHTTP/2、データ形式のProtocol Buffers、その上を流れるバイト列の形に分けると追いやすくなります。

### HTTP/2：1本のTCP接続で複数の呼び出しを流す通信路

gRPCは通信路にHTTP/2を使います。HTTP/2は1本の接続の上で複数のストリームを同時に流す多重化、ヘッダー圧縮、双方向のストリーミングを備えます。[RFC 9113](https://www.rfc-editor.org/rfc/rfc9113)はHTTP/2をTCP接続上で動くプロトコルとして定義しており、これがgRPCがTCPで動く直接の理由です。gRPCのストリーミングや接続の使い回しは、このHTTP/2の性質に依存しています。

### Protocol Buffers：.protoで契約を先に決めるデータ形式

gRPCは標準でProtocol Buffers（protobuf）を使います。やり取りするメッセージとサービスのメソッドを拡張子 `.proto` のファイルに定義し、そこから各言語のコードを生成します。データはJSONのようなテキストでなくバイナリに変換されるため小さく、契約が先に決まるのでクライアントとサーバーの型ずれが起きにくくなります。文法は[protobuf公式のLanguage Guide（proto3）](https://protobuf.dev/programming-guides/proto3/)が一次情報です。

gRPCとProtocol Buffersは別物です。理屈の上では別のシリアライズ形式に差し替えられますが、実務ではほぼ例外なくProtocol Buffersが前提になります。フィールド番号の運用やスキーマの変更ルールは[Protocol Buffersとは？protoファイルの書き方と採用判断を実装者向けに解説](/tech/details/16245/)で扱っています。

### ワイヤ上のメッセージ形式：5バイトの接頭辞と結果を返すトレーラ

HTTP/2の上で何を送っているかは、gRPCリポジトリの[PROTOCOL-HTTP2.md](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md)に定義されています。リクエストはHTTP/2のPOSTで、パスは「/パッケージ名.サービス名/メソッド名」、content-typeは `application/grpc` で始まります。本文は各メッセージの前に「圧縮の有無を示す1バイト＋長さを示す4バイト」を付けた形で並び、処理結果は本文の後ろのHTTP/2トレーラで `grpc-status` と `grpc-message` として返ります。

```
POST /greeter.Greeter/SayHello HTTP/2
content-type: application/grpc
te: trailers
grpc-timeout: 2S

[0x00][0x00 0x00 0x00 0x07][HelloRequestのprotobufバイト列]

// ここから下がトレーラ
grpc-status: 0
grpc-message:
```

先頭の0x00が非圧縮、続く4バイトがメッセージ長で、`grpc-status: 0` はOKです。HTTPのステータスが200なのにアプリ側ではエラーになるのは、成否がトレーラ側で返るためです。トレーラを落とすプロキシや、HTTP/1.1に変換してしまう中継装置を挟むとgRPCが動かなくなるのも同じ理由です。終了コードの意味とリトライの可否は[gRPCのステータスコードとは？16種の意味・HTTP対応表・リトライ可否を解説](/tech/details/17200/)に対応表があります。

## スタブとチャネルとは：接続の抽象と自動生成される呼び出し口

gRPCのクライアントコードは、チャネルとスタブの2つで読めます。チャネルはサーバーへの接続を抽象化したもの、スタブは.protoから生成されるクライアント側の代理オブジェクトです。開発者は「チャネルを作る→スタブを作る→スタブのメソッドを呼ぶ」の3手でリモート呼び出しができます。用語の公式な定義は[grpc.ioの「Core concepts, architecture and lifecycle」](https://grpc.io/docs/what-is-grpc/core-concepts/)にあります。

### チャネル：接続の抽象化と接続を始めるタイミング

チャネルは内部でHTTP/2の接続を張り、複数のRPCを多重化して流します。接続の確立、切れたときの再接続、宛先が複数あるときの負荷分散もチャネルの仕事です。Pythonのgrpcioと、grpc-goの `grpc.NewClient` では、チャネルを作っただけでは接続を始めません（非推奨になった `grpc.Dial` は作成時に接続を始めます）。誰も待ち受けていないポートを宛先にしてチャネルとスタブを作り、状態の変化を記録しました。

```
import time, grpc, greeter_pb2, greeter_pb2_grpc

states = []
channel = grpc.insecure_channel("localhost:59999")  # 誰も待ち受けていないポート
channel.subscribe(lambda s: states.append(s.name), try_to_connect=False)
stub = greeter_pb2_grpc.GreeterStub(channel)         # ここまでは例外が出ない
time.sleep(0.5)
print("初回RPC前:", states)
try:
    stub.SayHello(greeter_pb2.HelloRequest(name="World"), timeout=2.0)
except grpc.RpcError as e:
    print("初回RPC:", e.code().name)
time.sleep(0.2)
print("初回RPC後:", states)

# 実行結果（grpcio 1.84.0）
# 初回RPC前: ['IDLE']
# 初回RPC: UNAVAILABLE
# 初回RPC後: ['IDLE', 'CONNECTING', 'TRANSIENT_FAILURE']
```

チャネルとスタブの作成では例外が出ず、状態は `IDLE` のままでした。接続を試みたのは初回のRPCを呼んだときで、そこで初めて `UNAVAILABLE` になります。つまり「チャネルを作れた＝サーバーにつながる」ではありません。起動時に疎通を確かめたいなら、ヘルスチェックのRPCを1回呼ぶか、`grpc.channel_ready_future()` で待つ必要があります。接続は重いので、1つのチャネルを複数のスタブや呼び出しで共有し、リクエストごとに作り直さないのが基本です。

### スタブ：.protoから生成されるクライアントの代理

スタブは、.protoのサービス定義から生成されるクライアント側のクラスです。Pythonなら `greeter_pb2_grpc.GreeterStub(channel)` のようにチャネルを渡して作り、メソッドを呼ぶと、シリアライズとHTTP/2での送受信をスタブが肩代わりします。サーバー側には対になる基底クラス（Pythonでは `GreeterServicer`）が生成され、そこに処理を書きます。

Goでは、grpc-goのv1.63.0で `grpc.Dial` が非推奨になり、`grpc.NewClient` を使うのが現在の書き方です。

```
// grpc-go v1.63以降の推奨API（insecure は google.golang.org/grpc/credentials/insecure）
conn, err := grpc.NewClient("localhost:50051",
    grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
    log.Fatal(err)
}
defer conn.Close()
stub := pb.NewGreeterClient(conn) // チャネル（ClientConn）からスタブを作る
resp, err := stub.SayHello(ctx, &pb.HelloRequest{Name: "world"})
```

テストの分野でいう「スタブ」（依存先を差し替える偽物の実装）とgRPCのスタブは別の概念です。gRPCのスタブは通信を代行する生成コードであって、テスト用のダミーではありません。言語ごとの生成手順は[gRPC Pythonの実装｜スタブ生成からasync・FastAPI併存まで解説](/tech/details/17213/)と[gRPC Goの実装｜protoc-gen-go-grpcの生成手順とNewClient移行を解説](/tech/details/17217/)で詳しく扱っています。

## gRPCはUDPを使うのか：TCP・UDP・HTTP/3（QUIC）の関係

標準構成のgRPCはUDPを使いません。gRPCはHTTP/2の上で動き、HTTP/2はTCPの上で動くため、通信はTCPです。「高速な通信＝UDP」という連想や、UDPを使うHTTP/3の登場から混同が生まれやすいだけです。

### 標準はHTTP/2 over TCP：UDPに差し替えられない理由

HTTP/2は、1本のTCP接続の上でストリームを多重化し、順序保証・再送・フロー制御といった信頼性のある配送を前提に設計されています。gRPCのストリーミングやデッドライン、エラーの返し方はこの土台に依存しているので、到達保証のないUDPソケットにそのまま載せ替えることはできません。TCPとUDPのソケットの違いそのものは[ソケットとは？Pythonで作って動かすTCP・UDP・UNIXドメインの実装入門](/tech/details/17511/)で、実際にコードを動かして比べています。

### HTTP/3ならUDPの上を流れる：G2が定める条件

HTTP/3（[RFC 9114](https://www.rfc-editor.org/rfc/rfc9114)）は、TCPではなくQUIC（[RFC 9000](https://www.rfc-editor.org/rfc/rfc9000)）の上で動きます。QUICはUDPの上に多重化・再送・暗号化を作り直したトランスポートなので、gRPCをHTTP/3に載せれば、その通信はUDPを流れます。QUIC自体の仕組みは[QUIC（通信プロトコル）とは？HTTP/3を支える仕組みとTCPとの違いを実装視点で解説](/tech/details/16234/)にまとめています。

gRPC over HTTP/3の決まりは、提案文書[G2「gRPC over HTTP/3」](https://github.com/grpc/proposal/blob/master/G2-http3-protocol.md)にあります（2021年12月にマージ、ステータスはImplemented）。要点は4つです。

- リクエストとレスポンスの形はHTTP/2のときと同じで、content-typeも `application/grpc` のまま
- HTTP/3はTLSが必須で、平文（insecure）のチャネルでは使われない
- 使い始め方は2通り。最初からHTTP/3だけで接続するか、HTTP/2で接続してサーバーの `alt-svc` ヘッダーを見てHTTP/3へ切り替える
- デッドライン超過時のストリーム中断は、HTTP/2の `RST_STREAM`（CANCEL）からQUICの `RESET_STREAM`（H3\_REQUEST\_CANCELLED）に変わる

2つ目の条件は実務で効きます。チュートリアルどおり `add_insecure_port` や `insecure_channel` で書いた平文の構成は、どの実装を使ってもUDPにはなりません。

### 実装ごとのHTTP/3対応状況（2026年10月時点）

HTTP/3を使えるかは、gRPCの仕様ではなく使う実装で決まります。公式の記述とリポジトリで確認できた範囲を表にしました。

| 実装                         | HTTP/3             | 根拠              |
| -------------------------- | ------------------ | --------------- |
| grpc-dotnet（クライアント）        | .NET 6以降で対応        | Microsoft Learn |
| ASP.NET Core Kestrel（サーバー） | .NET 7以降で完全サポート    | Microsoft Learn |
| C++・Java（クライアント）           | Cronet経由で可         | grpc/grpc#19126 |
| grpc-go（v1.84.0）           | なし（TCPで接続）         | 本記事の実測          |
| Python grpcio（1.84.0）      | なし（TCPで待受）         | 本記事の実測          |
| connect-go＋quic-go         | 可（http.Handlerを流用） | 本記事の実測          |

.NETの記述は[Microsoft Learnの「Configure gRPC client to use HTTP/3」](https://learn.microsoft.com/aspnet/core/grpc/troubleshoot#configure-grpc-client-to-use-http3)にあり、サーバーが `alt-svc` を返すとクライアントが自動でHTTP/3へ切り替わります。[KestrelのHTTP/3](https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel/http3)は既定で無効で、MsQuicが動かない環境では自動でHTTP/2以下に戻ります。.NETでの実装手順は[gRPC C#の実装｜Grpc.AspNetCoreのビルド時生成とホスティング制約を解説](/tech/details/17219/)を参照してください。

Cronetについては、grpc-javaの責任者が[grpc/grpc#19126](https://github.com/grpc/grpc/issues/19126)で、Cronetはクライアント専用なので、通常はロードバランサがQUIC（HTTP/3）を受けてHTTP/2に変換し、バックエンドのgRPCサーバーへはHTTP/2で渡すと説明しています。このissueは2019年に立てられ、2026年10月時点でも未完了のままです。サーバー同士をHTTP/3でつなぐ構成は、主要実装の標準機能にはなっていません。

## 実測：gRPCの待ち受けソケットとHTTP/3での呼び出し

「gRPCはTCP」「HTTP/3ならUDP」を、待ち受けているソケットの種類で確かめました。環境はmacOS、Python 3.12＋grpcio 1.84.0、Go 1.27.2＋quic-go v0.64.1＋connect-go v1.21.1＋grpc-go v1.84.0です。

### Pythonのgrpcio：TCP 50051の待ち受け実測

後半の実装例のserver.pyを起動し、そのプロセスが開いているソケットを `lsof` で見ました。

```
$ python server.py &
gRPC server started on :50051
$ lsof -nP -a -p <サーバーのPID> -i
COMMAND     PID     USER   FD   TYPE  DEVICE  SIZE/OFF NODE NAME
python3.1 92755 shichiza    5u  IPv6  0x...        0t0  TCP *:50051 (LISTEN)
```

開いていたのはTCPの待ち受け1本だけで、UDPのソケットはありません。クライアントからRPCを呼んでいる最中に見ても同じでした。grpcioで「UDPを使う設定」を探しても見つからないのはこのためです。

### connect-go＋quic-go：HTTP/3によるgRPC通信の実測

次に、HTTP/3のサーバーとしてquic-goの `http3.Server` を立て、そこにConnect（connectrpc）のgRPCヘルスチェックのハンドラを載せました。Connectのハンドラは標準の `http.Handler` なので、HTTP/3のサーバーにもそのまま渡せます。

```
package main

import (
	"log"
	"net/http"

	"connectrpc.com/grpchealth"
	"github.com/quic-go/quic-go/http3"
)

func main() {
	mux := http.NewServeMux()
	// grpc.health.v1.Health/Check に応答する標準ヘルスチェック
	mux.Handle(grpchealth.NewHandler(grpchealth.NewStaticChecker("greeter.Greeter")))
	srv := &http3.Server{Addr: "127.0.0.1:50443", Handler: mux}
	// HTTP/3 は TLS 必須。証明書は検証用の自己署名
	log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))
}
```

クライアントはquic-goの `http3.Transport` を通信路にし、`connect.WithGRPC()` でgRPCプロトコルを指定して呼びました。比較のため、grpc-go v1.84.0の `grpc.NewClient` でも同じ宛先にヘルスチェックを送っています。

```
// 抜粋：HTTP/3 のトランスポートで gRPC プロトコルを話すクライアント
tr := &http3.Transport{TLSClientConfig: &tls.Config{RootCAs: pool}}
hc := &http.Client{Transport: logTransport{tr}} // 応答の res.Proto を表示するだけのラッパー

client := connect.NewClient[healthpb.HealthCheckRequest, healthpb.HealthCheckResponse](
	hc, "https://localhost:50443/grpc.health.v1.Health/Check",
	connect.WithGRPC(), // Connect 独自形式ではなく gRPC プロトコルで送る
)
res, err := client.CallUnary(ctx,
	connect.NewRequest(&healthpb.HealthCheckRequest{Service: "greeter.Greeter"}))
```

```
$ lsof -nP -a -p <サーバーのPID> -i
COMMAND   PID   USER     FD   TYPE  DEVICE  SIZE/OFF NODE NAME
server  93044 shichiza    4u  IPv4  0x...        0t0  UDP 127.0.0.1:50443

$ ./client          # connect-go + quic-go（HTTP/3）
proto: HTTP/3.0 content-type: application/grpc
status: SERVING

$ ./grpcgo          # grpc-go v1.84.0 の grpc.NewClient で同じ宛先へ
grpc-go: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp 127.0.0.1:50443: connect: connection refused"
```

サーバーが開いたのはUDPのソケットだけで、応答は `HTTP/3.0`、content-typeは `application/grpc` でした。G2のとおり、HTTP/3でもメッセージの形はgRPCのままです。一方のgrpc-goは `dial tcp` を試みて `connection refused` で失敗しました。grpc-goのクライアントはTCPでしか接続しないため、UDPで待ち受けるサーバーには届きません。

この直接接続の構成では、クライアントとサーバーの両方にHTTP/3対応が必要です。途中でHTTP/3を終端してHTTP/2へ変換する場合は、バックエンドまでHTTP/3にする必要はありません。なお、connect-goは2026年10月7日にv2を公開し、Goでの書き方が変わりました。ただしv2のリリースノートには、ワイヤプロトコルとgRPCとの互換性は変わらないと書かれています。上の結果はv1系（v1.21.1）で取ったものです。v2での書き方は[Connect（connectrpc）とは？gRPC・RESTとの違いとconnect-go v2での使い方](/tech/details/5504/)で扱っています。

## HTTP/3にする判断：効く場面と、UDPが欲しいときの代替

HTTP/3の利点は、TCP由来のヘッドオブラインブロッキングがなくなることです。TCPでは1つのパケットロスが同じ接続のすべてのストリームを待たせますが、QUICはストリームごとに回復します。接続確立の往復も減り、IPアドレスが変わっても接続を続けられます。効くのは、Wi-Fiとモバイル回線を行き来するクライアントや、パケットロスの多い経路です。

経路変更やパケットロスが少ないデータセンター内では、これらの利点を採用理由にしにくいため、実際の負荷で遅延とCPU使用量を比較して判断します。HTTP/3にするとUDPを通すファイアウォール設定、HTTP/3を終端できるロードバランサ、HTTP/3対応の実装という3つの条件が増えます。内部通信はHTTP/2のまま構築し、モバイルクライアントの遅延や切断が実測で問題になってから、入口のロードバランサだけHTTP/3にする順番が手戻りの少ない進め方です。

そもそもUDPで送りたい要件があるなら、gRPCにこだわらず要件で選び直すほうが早く済みます。

- ブラウザとサーバーの間で低遅延の双方向通信をしたい：[WebTransport](/tech/details/16251/)（HTTP/3上の双方向通信）
- 省電力のIoT機器でUDPのままリクエストと応答をしたい：[CoAP](/tech/details/16237/)（UDP上のREST）
- 多少失ってもよいので最新値を送り続けたい（ゲームの位置同期など）：UDPソケットにProtocol Buffersのメッセージを直接載せる

3つ目の構成では、gRPCのデッドライン・ステータスコード・ストリームの順序保証はすべて失われます。それでよいと言い切れる用途に限って選んでください。

## gRPCの4つの通信方式：Unaryと3種類のストリーミング

gRPCは1往復の呼び出しに加え、HTTP/2のストリーミングを使う3つの方式を持ちます。

- Unary RPC：1リクエストに1レスポンス。通常の関数呼び出しに近い形
- サーバーストリーミング：1リクエストに対し、サーバーが複数のレスポンスを順に返す。一覧の逐次配信や進捗通知向き
- クライアントストリーミング：クライアントが複数送り、サーバーが最後に1つ返す。ログや計測値の集約向き
- 双方向ストリーミング：両側が同時に複数のメッセージを送受信する。チャットやリアルタイム同期向き

4方式の違いは、.protoの `rpc` 定義で引数と戻り値の側に `stream` を付けるかどうかだけで表します。

```
service Chat {
  rpc GetRoom (RoomRequest) returns (Room);                      // Unary
  rpc ListMessages (RoomRequest) returns (stream Message);       // サーバーストリーミング
  rpc UploadLogs (stream LogLine) returns (UploadSummary);       // クライアントストリーミング
  rpc Talk (stream Message) returns (stream Message);            // 双方向ストリーミング
}
```

サービス定義を見れば通信の形が読めるので、方式の取り違えが起きにくくなります。各方式の実装と切断時の設計は[gRPCストリーミングとは？4方式の使い分けと切断対策を実装者向けに解説](/tech/details/16304/)で詳しく扱っています。

## gRPCとREST APIの違い：プロトコル・データ形式・エラー表現の比較

gRPCと最もよく比べられるのがREST APIです。どちらもHTTPを使いますが、前提にしている層が違います。

| 項目          | gRPC                   | REST API               |
| ----------- | ---------------------- | ---------------------- |
| ベースのプロトコル   | HTTP/2（TCP）            | HTTP/1.1・HTTP/2・HTTP/3 |
| データ形式       | Protocol Buffers（バイナリ） | JSON・XML・バイナリ等         |
| スキーマ        | 標準では.protoで定義          | 任意（OpenAPI等で補う）        |
| 通信方式        | 4種類                    | リクエストとレスポンス            |
| エラーの返し方     | grpc-statusの0〜16       | HTTPステータスコード           |
| タイムアウトの伝え方  | grpc-timeoutヘッダーで伝搬    | 実装ごとに個別                |
| ブラウザからの直接利用 | 不可（gRPC-Web等が必要）       | 可能                     |
| 人による可読性     | 低い                     | 高い                     |

「gRPCとHTTPの違い」と聞かれたら、gRPCはHTTPを捨てたのではなくHTTP/2の多重化とトレーラを前提にしている、と答えるのが正確です。RESTはHTTPのバージョンやJSONに固定された方式ではありません。gRPCとの主な違いは、リソースを中心に操作を設計するか、サービスのRPCメソッドを定義するかにあります。既存のgRPCサービスにJSONのREST入口だけを足す方法は[gRPC-Gatewayとは？protoからRESTを生やす仕組みと生成構成・移行判断を解説](/tech/details/17196/)、ほかのAPI方式との比較は[gRPCとGraphQLの違いは？役割分担・スキーマ運用・BFF併用で使い分ける](/tech/details/17211/)と[gRPCとWebSocketの違いは？双方向通信の仕組み・ブラウザ到達性・LB通過性で使い分ける](/tech/details/17207/)で扱っています。

## gRPCのメリットとデメリット：速度・型安全と学習コスト

メリットは、Protocol Buffersによる小さな転送量、HTTP/2の多重化による低遅延、.protoに基づく型安全、そして同じ.protoからGo・Java・Python・C++・C#・Node.js・Kotlin・Dartなどのコードを生成できる多言語対応です。デッドラインは呼び出し先へ伝わり、受けたRPCから次のRPCへの自動の引き継ぎは、[公式のDeadlinesガイド](https://grpc.io/docs/guides/deadlines/)によるとJavaとGoでは既定で有効、C++では明示的な有効化が必要です。Pythonのサーバーから別サービスを呼ぶ場合は、`context.time_remaining()` で残り時間を取り、次のRPCの `timeout` に渡します。

デメリットは3つあります。.protoの設計やコード生成の環境づくりという初期の手間、バイナリなのでログやデバッグで中身を目で追いにくいこと、ブラウザから直接呼べないことです。ブラウザから使うには[gRPC-Web](/tech/details/16253/)とEnvoyなどのプロキシ、またはConnectのような中間層が要ります。デバッグのしにくさは、後述のサーバーリフレクションとgrpcurlでかなり緩和できます。

## gRPCによるテレメトリ収集：OTLPとネットワーク機器のストリーミングテレメトリ

gRPCは、メトリクス・ログ・トレースといった観測データを集める経路としても使われます。protobufの小さなペイロードと、1本の接続を使い回せる点が継続的な送信に合うためです。ただし送り方は用途で違い、OTLP/gRPCは並行するUnary RPCで送り、gNMIのSubscribeはストリーミングRPCで受け取ります。使われる場面は、アプリケーションの可観測性とネットワーク機器の状態監視の2つに分かれます。

アプリケーション側の代表が、OpenTelemetryの標準プロトコルOTLPです。[OTLPの仕様](https://opentelemetry.io/docs/specs/otlp/)はgRPCとHTTPの両方の送り方を定めており、既定のポートはOTLP/gRPCが4317、OTLP/HTTPが4318です。CollectorやSDKがデータを継続的に送る構成では、OTLP/gRPCが標準的な選択肢です。Collectorを立ててOTLPを受ける手順は[OpenTelemetryとは｜LinuxでCollectorを動かしテレメトリを集める手順とPrometheusとの違い](/tech/details/3625/)で扱っています。

ネットワーク機器では、ルーターやスイッチがgRPCで状態の変化や一定間隔の値をコレクタへ送ります。標準化された[gNMI（gRPC Network Management Interface）](https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md)の仕様では、購読を始めるのはクライアント（コレクタ）側で、コレクタから機器へ接続してSubscribe RPCを呼ぶ形（dial-in）です。機器側からコレクタへ接続する形（dial-out）は、CiscoやJuniperの独自のgRPCサービスのほか、OpenConfigの[grpctunnel](https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmignoissh-dialout-grpctunnel.md)でgRPCのトンネルを機器側から張り、その中でgNMIを使う方式があります。dial-outは、NATやファイアウォールの内側の機器から内向きのポートを開けずに送れる点が利点です。取得できる粒度は、機器の更新頻度と購読設定に依存します。gNMIのON\_CHANGEは値の変化に応じて通知し、SAMPLEは指定間隔で値を送るため、SNMPポーリングより常に細かくなるわけではありません。

## gRPCの実装例（Python）：protoからサーバーとクライアントまで

最も基本的なUnary RPCを、Pythonで最小構成のサーバーとクライアントにします。手順は[grpc.ioのPython Quick start](https://grpc.io/docs/languages/python/quickstart/)と同じ流れです。本体パッケージの[grpcio](https://pypi.org/project/grpcio/)は、2026年10月時点の安定版が1.84.0（2026年9月14日公開）で、Python 3.10以上が必要です（1.85.0rc1も公開されています）。以下のコードはこの版で実行し、記載の出力を確認しました。

まず通信の契約となる.protoファイルを書きます。

```
syntax = "proto3";

package greeter;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```

次にライブラリを入れ、.protoからPythonのコードを生成します。pipはPythonのパッケージ管理ツールで、そのpipでgRPC本体のgrpcioとコード生成用のgrpcio-toolsを入れる、という関係です。

```
python -m pip install grpcio==1.84.0 grpcio-tools==1.84.0
python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. greeter.proto
```

`greeter_pb2.py`（メッセージ）と `greeter_pb2_grpc.py`（スタブとServicer）が生成されます。生成されたServicerを継承してサーバーを書きます。

```
from concurrent import futures
import grpc
import greeter_pb2
import greeter_pb2_grpc

class GreeterServicer(greeter_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        return greeter_pb2.HelloReply(message=f"Hello, {request.name}!")

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)
    server.add_insecure_port("[::]:50051")
    server.start()
    print("gRPC server started on :50051")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()
```

クライアントはチャネルを作り、そこからスタブを作って呼ぶだけです。

```
import grpc
import greeter_pb2
import greeter_pb2_grpc

def run():
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = greeter_pb2_grpc.GreeterStub(channel)
        response = stub.SayHello(greeter_pb2.HelloRequest(name="World"))
        print("Server replied:", response.message)

if __name__ == "__main__":
    run()

# 実行結果
# Server replied: Hello, World!
```

同じ.protoからGoやJavaのコードも生成できます。Rustで書く場合は[gRPC Rustの実装｜tonic 0.14のクレート分割とTLS・相互接続を解説](/tech/details/17215/)を参照してください。

## grpcurlで動作を確かめる：サーバーリフレクションとヘルスチェック

curlでもHTTP/2対応ビルドと適切なバイナリペイロードがあればgRPCリクエストを送れますが、protobufの組み立てと応答のデコードは別途必要です。そこで使うのが[grpcurl](https://github.com/fullstorydev/grpcurl)です。JSONで書いたリクエストをprotobufに変換して送り、応答をJSONで表示します。サーバー側で[サーバーリフレクション](https://grpc.io/docs/guides/reflection/)を有効にしておくと、grpcurlが手元の.protoなしでサービス定義を取り寄せられます。

あわせて入れておきたいのが、`grpc.health.v1.Health` として仕様が決まっている[標準ヘルスチェック](https://grpc.io/docs/guides/health-checking/)です。KubernetesのgRPCプローブやロードバランサの死活監視がこのサービスを呼びます。Pythonでは[grpcio-reflection](https://pypi.org/project/grpcio-reflection/)と[grpcio-health-checking](https://pypi.org/project/grpcio-health-checking/)を追加し、server.pyの `serve()` を次のように書き換えます。

```
# 事前に追加パッケージを導入：pip install grpcio-reflection grpcio-health-checking
from grpc_reflection.v1alpha import reflection
from grpc_health.v1 import health, health_pb2, health_pb2_grpc

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    greeter_pb2_grpc.add_GreeterServicer_to_server(GreeterServicer(), server)

    # ヘルスチェック：空文字はサーバー全体、サービス名ごとにも状態を持てる
    health_servicer = health.HealthServicer()
    health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server)
    health_servicer.set("", health_pb2.HealthCheckResponse.SERVING)
    health_servicer.set("greeter.Greeter", health_pb2.HealthCheckResponse.SERVING)

    # リフレクション：grpcurl などが .proto なしでサービス定義を取得できる
    service_names = (
        greeter_pb2.DESCRIPTOR.services_by_name["Greeter"].full_name,
        reflection.SERVICE_NAME,
    )
    reflection.enable_server_reflection(service_names, server)

    server.add_insecure_port("[::]:50051")
    server.start()
    server.wait_for_termination()
```

起動し直したサーバーに、grpcurl v1.9.4で一覧取得・呼び出し・ヘルスチェックを送った結果です。TLSを張っていない開発用サーバーなので `-plaintext` を付けます。

```
$ grpcurl -plaintext localhost:50051 list
greeter.Greeter
grpc.reflection.v1alpha.ServerReflection

$ grpcurl -plaintext -d '{"name": "World"}' localhost:50051 greeter.Greeter/SayHello
{
  "message": "Hello, World!"
}

$ grpcurl -plaintext -d '{"service": "greeter.Greeter"}' localhost:50051 grpc.health.v1.Health/Check
{
  "status": "SERVING"
}
```

リフレクションはサービス定義を外に見せる機能です。社外に公開するエンドポイントでは無効にするか、認証の内側でだけ有効にしてください。

## 本番前に足す設定：デッドライン・ステータスコード・TLS

最小実装のままだと、応答を待つ時間の上限がなく、アプリケーション独自の失敗を適切なステータスコードに対応付けておらず、通信も平文です。本番前に次の3点を足します。

### デッドライン：RPCごとの待ち時間上限の設定

gRPCのクライアントは、既定ではタイムアウトを持ちません。[公式のDeadlinesガイド](https://grpc.io/docs/guides/deadlines/)は、呼び出しごとにデッドラインを設定するよう勧めています。Pythonでは `timeout` 引数に秒数を渡すと、期限を過ぎた呼び出しが `DEADLINE_EXCEEDED` で打ち切られます。デッドラインは `grpc-timeout` ヘッダーでサーバーへ伝わるので、サーバーは `context.time_remaining()` で残り時間を見て、間に合わない処理を早めに切り上げられます。

### ステータスコード：失敗の分類と再試行条件

サーバーは例外を投げっぱなしにせず、[公式のStatus codes一覧](https://grpc.io/docs/guides/status-codes/)から意味の合うコードを返します。入力不備なら `INVALID_ARGUMENT`、対象がなければ `NOT_FOUND`、一時的な過負荷なら `UNAVAILABLE` です。クライアントは `grpc.RpcError` からコードを取り出して分岐します。`UNAVAILABLE` は一時的な状態を示すコードですが、公式一覧は冪等でない操作の再試行が安全とは限らないと注記しています。再送するのは冪等な操作か重複実行への対策がある操作に限り、バックオフと回数の上限を付けてください。

```
# サーバー側：入力不備は INVALID_ARGUMENT で返す
class GreeterServicer(greeter_pb2_grpc.GreeterServicer):
    def SayHello(self, request, context):
        if not request.name:
            context.abort(grpc.StatusCode.INVALID_ARGUMENT, "name is required")
        return greeter_pb2.HelloReply(message=f"Hello, {request.name}!")

# クライアント側：2秒のデッドラインを付け、コードで分岐する
def run():
    with grpc.insecure_channel("localhost:50051") as channel:
        stub = greeter_pb2_grpc.GreeterStub(channel)
        try:
            response = stub.SayHello(greeter_pb2.HelloRequest(name=""), timeout=2.0)
            print("Server replied:", response.message)
        except grpc.RpcError as e:
            if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
                print("timeout")
            else:
                print("error:", e.code().name, e.details())

# 実行結果（name を空にした場合）
# error: INVALID_ARGUMENT name is required
```

### TLS：証明書を用いたチャネルの暗号化

ここまでの例は `add_insecure_port` と `insecure_channel` による平文の通信です。[公式のAuthenticationガイド](https://grpc.io/docs/guides/auth/)のとおり、サーバーは証明書と秘密鍵から作った資格情報で `add_secure_port` を呼び、クライアントは信頼するCA証明書から作った資格情報で `secure_channel` を開きます。相互TLS（mTLS）にする場合は、サーバー側でクライアント証明書の検証を有効にし、クライアント側にも証明書と秘密鍵を渡します。

```
# サーバー側
with open("server.key", "rb") as f:
    private_key = f.read()
with open("server.crt", "rb") as f:
    certificate_chain = f.read()
server_credentials = grpc.ssl_server_credentials([(private_key, certificate_chain)])
server.add_secure_port("[::]:50051", server_credentials)

# クライアント側
with open("ca.crt", "rb") as f:
    root_certificates = f.read()
channel_credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates)
with grpc.secure_channel("api.example.internal:50051", channel_credentials) as channel:
    stub = greeter_pb2_grpc.GreeterStub(channel)
```

先に見たとおり、HTTP/3はTLSが前提です。将来HTTP/3を使う可能性があるなら、最初からTLSありの構成で動かしておくと移行が楽になります。Kubernetes上でサービスメッシュを使うなら、mTLSはメッシュ側に任せてアプリは平文のまま書く設計もあります。どちらに寄せるかは、証明書の配布と更新を誰が担うかで決めてください。

## gRPCとRESTの使い分け：向いている場面と見送る場面

gRPCが最も活きるのは、サービス同士が頻繁に呼び合う[マイクロサービス](/column/details/2910/)の内部通信、言語の違うチームが同じ契約で開発する場面、双方向ストリーミングでチャットや進捗通知を流す場面です。サービス間の通信にmTLSや再試行をまとめてかけるなら[サービスメッシュ](/tech/details/13224/)、外からの入口を1か所に集めるなら[APIゲートウェイ](/tech/details/13210/)と組み合わせます。

判断を言い切るなら、次の3条件がそろったときにgRPCを採用し、1つでも欠けるならRESTから始めます。(1) 呼び出し元と呼び出し先の両方を自社で管理している、(2) 通信量か遅延に具体的な要求値がある、またはストリーミングが要る、(3) .protoを1か所で管理し、コード生成をCIに組み込む体制がある。

逆に、取引先や外部の開発者に使ってもらうAPI、ブラウザから直接呼ぶ公開API、単純なCRUDが中心の管理画面、開発速度を優先するプロトタイプは、RESTのほうが総コストが低く済みます。外部向けのAPIをgRPCで作っても、結局gRPC-GatewayなどでRESTの入口を足すことになりやすいためです。ブラウザからも同じ.protoで呼びたい場合は、gRPC・gRPC-Web・JSONを1つのサーバーで受けられるConnectも候補に入ります。

既存のシステムでは、まずRESTのまま動かし、通信がボトルネックだと分かった内部の区間からgRPCへ移すのが進めやすい順番です。API設計の作法は[Google API設計ガイドとAIPとは｜主要規約の要点とapi-linterでの準拠チェック](/tech/details/10557/)が参考になります。内部通信のgRPC化や、外部向けREST APIとの併用構成を具体的に詰めたい場合は、一創の[API開発・システム連携](/service/system/api/)でご相談いただけます。

## gRPCについてよくある質問

### gRPCのポート番号は決まっていますか？

gRPC専用に決まったポート番号はなく、IANAへの登録もありません。公式のサンプルで使われる50051は慣例で、49152〜65535の動的・私用ポートの範囲にあります。gRPCの[名前解決の仕様](https://github.com/grpc/grpc/blob/master/doc/naming.md)では、宛先のポートを省略すると443が使われます（平文のチャネルでは80を既定にする実装もあります）。OpenTelemetryのOTLP/gRPCは4317が既定で、これはOTLPの仕様で決まっているものです。

### gRPCの通信は暗号化できますか？

できます。HTTP/2の上でTLSを使い、サーバー証明書で通信を暗号化します。クライアントにも証明書を持たせて互いに検証する相互TLS（mTLS）も使えます。ネットワークを越える通信ではTLSが前提で、HTTP/3を使う場合はTLSが必須です。Pythonでの書き方は本文の「本番前に足す設定」に載せています。

### gRPCはブラウザから直接使えますか？

そのままでは使えません。gRPCはHTTP/2のトレーラなどを使いますが、ブラウザのJavaScriptからはそれを扱えないためです。ブラウザから呼ぶには、gRPC-Webに対応したコードとEnvoyなどの変換プロキシを使うか、Connectのようにブラウザ向けの形式も受けられるサーバーを使います。

### pipとgRPCの違いは何ですか？

比べる対象ではありません。pipはPythonのパッケージ管理ツール、gRPCはRPCフレームワークです。PythonでgRPCを使うときは `pip install grpcio grpcio-tools` のように、pipでgRPCのライブラリを入れます。

### Protocol Buffersは必須ですか？

標準ではProtocol Buffersを使います。gRPCの仕組みとしては別のシリアライズ形式にも差し替えられますが、公式ドキュメント・ツール・各言語の生成器がProtocol Buffersを前提にしているため、実務ではほぼ必須と考えてください。

## 関連記事

- [QUIC（通信プロトコル）とは？HTTP/3を支える仕組みとTCPとの違いを実装視点で解説](/tech/details/16234/)
- [gRPC Pythonの実装｜スタブ生成からasync・FastAPI併存まで解説](/tech/details/17213/)
- [gRPCのステータスコードとは？16種の意味・HTTP対応表・リトライ可否を解説](/tech/details/17200/)
- [Protocol Buffersとは？protoファイルの書き方と採用判断を実装者向けに解説](/tech/details/16245/)
- [gRPCとWebSocketの違いは？双方向通信の仕組み・ブラウザ到達性・LB通過性で使い分ける](/tech/details/17207/)

---

出典: [gRPCとは？UDPは使うのかをTCP・HTTP/3で実測｜仕組み・RESTとの違い・実装例](<https://www.issoh.co.jp/tech/details/3404/>)（株式会社一創）
