はじめに
今回はPingreq/Pingrespについて見ていきましょう。MQTTには定期的に通信することで相互に生存を確認する仕組みがあります。
Pingreq/Pingresp
必要な理由
下位のtcpにはkeepaliveの仕組みがありますが。tcpが通じていてもその上のアプリケーション層のレイヤであるmqttが通じているとは限りません。
Pingreq/Pingresの仕様
そこでMQTTには定期的に通信することで、サーバはクライアントが生きていることを、クライアントはサーバが生きていることを確かめる仕掛けがあります。
- クライアントはConnectの際にkeepalive値(秒)をVariable Headerに入れて送信します
- クライアントは自分で指定したkeepalive秒数以上の無言間隔が続かないようにPingreqを送信します
- クライアントはPingreqに対してはサーバから即時Pingresが返って来ることを期待し、返ってこない場合はサーバ側と通信できなくなったと判断し、クライアント側から切断すべきです
- クライアントは他のパケットがサーバに送信されていて無言になっていなければPingreqの送信は不要です
無言カウンタのルール
- サーバはクライアントからのPingreqに対してPingres即時応答すると同時に無言時間カウンタを0にします。無言時間カウンタはクライアントが宣言したkeepalive値の1.5倍を過ぎると切断扱いとします
- 切断扱いになるとWill(遺言)が存在する場合は処理します
実際の値の例
実際にはkeepaliveの時間は2,3分が設定されるようです。
The actual value of the Keep Alive is application specific; typically this is a few minutes.
- AWS IoT 30秒-1200秒 https://docs.aws.amazon.com/ja_jp/general/latest/gr/iot-core.html
- Moswuitto デフォルト60秒 https://mosquitto.org/man/mosquitto-conf-5.html
実験
クライアントからわざとPingを送らず、切断される様子を確認しましょう。
5秒でkeepaliveすると宣言してConnectしたあとにクライアントの構造体のメンバ変数を壊してkeepaliveしなくなるクライアントを作成して放置します。
_keepalive の書き換えはライブラリ内部の非公開フィールドを利用したハックで単なる実験目的です
import paho.mqtt.client as mqtt
import time
BROKER = "localhost"
def on_disconnect(client, userdata, flags, reason_code, properties):
print(f"[切断] reason={reason_code}, flags={flags}")
def bad_client():
cli = mqtt.Client(
client_id="bad-client",
callback_api_version=mqtt.CallbackAPIVersion.VERSION2
)
cli.on_disconnect = on_disconnect
# keepalive=5 → 本来は5秒ごとにPINGREQが必要
cli.connect(BROKER, 1883, keepalive=5)
# ★ loop_start は使う(コールバックを動かす)
# ★ 内部 keepalive を 0 にして PINGREQ を送らない(非公式ハック)
cli._keepalive = 0
cli.loop_start()
print("[INFO] Connected but keepalive=5 を宣言したまま PINGREQ を送らない")
# Broker が切断するまで待つ
time.sleep(30)
if __name__ == "__main__":
bad_client()
keepaliveが5秒ですので、その1.5倍の7.5秒後に切断されます。切断の理由はis_disconnect_packet_from_server=Falseとなっています。
[~/qiita/client]$uv run pingreq.py
[2025-11-20 22:08:07] [INFO] Connected but keepalive=5 を宣言したまま PINGREQ を送らない
[2025-11-20 22:08:14] [切断] reason=Unspecified error, flags=DisconnectFlags(is_disconnect_packet_from_server=False)
[~/qiita/client]$
Wiresharkから見ても、サーバ側からFINが送信されてTCP切断がされています。

- サーバが keepalive timeout に達すると、MQTT レイヤではなく TCP レイヤで切断(FIN)を送る
- (このケースでは)サーバからMQTTのDISCONNECTパケットは送られない
著作権情報
Copyright © OASIS Open 2014. All Rights Reserved.
Available at: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
Copyright © OASIS Open 2019. All Rights Reserved.
Available at: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html