Webアプリ PHP VSCode デバッグ
VS Code で PHP をデバッグするときの通信の仕組みを追いかける

VS Code で PHP をデバッグするときの通信の仕組みを追いかける

netstat で見える接続の状態

VS Code と Xdebug がどうやってつながっているのかを確認するために、まずは OS 側から見える接続状態を確認してみます。
netstat -ano | findstr 9003
結果はだいたい以下のようになります。
TCP 127.0.0.1:9003 0.0.0.0:0 LISTENING 17904 ← VS Code 待機中 TCP 127.0.0.1:9003 127.0.0.1:57846 ESTABLISHED 17904 ← VS Code 受信側 TCP 127.0.0.1:53650 127.0.0.1:9003 TIME_WAIT 0 ← 前の接続の残り(消えかけ) TCP 127.0.0.1:57846 127.0.0.1:9003 ESTABLISHED 38624 ← PHP(Xdebug) 送信側
LISTENING は「そのポートで接続を待っている状態」です。 つまり VS Code 側がデバッグ接続を待機しているということです。 ESTABLISHED は接続が確立済み、TIME_WAIT は前の接続の後片付け中の状態です。

ポートの役割

ログに出てくるポート番号(9003 と 57846)は、それぞれ以下のような役割を持っています。
  • xdebug.client_port = 9003(php.ini)… Xdebug が接続しに行く「行き先」ポート
  • "port": 9003(launch.json)… VS Code が待ち受ける「受け取り」ポート
  • 57846(Xdebug 側)… OS が自動で割り当てる送信元ポート
php.ini の Xdebug 側のポートは「行き先」で、launch.json のポートは「受け取り」ってこと?
その通りです。どちらも同じ 9003 を指していますが、役割が違います。Xdebug から見れば「どこに投げるか」、VS Code から見れば「どこで受け取るか」です。
Xdebug VS Code :57846(ランダム) →接続→ :9003 :57846 ←制御← :9003 ブレークポイント情報など :57846 →返答→ :9003 変数の中身など
Xdebug の返答自体は 57846 から出てくるってこと?
はい。 接続が確立したあと、Xdebug からの返答はすべて 57846 から送られ、VS Code の 9003 で受け取ります。

57846 はどこから来るのか

57846 は指定しなくてもいいの?
指定する必要はありません。この番号は OS が自動で割り当てています。
  1. VS Code が 9003 で待機(LISTENING)
  2. PHP のリクエストが来る
    • Xdebug が OS に「ポートちょうだい」と依頼
    • OS が 57846 を割り当て
    • 9003 に接続しにいく
  3. VS Code が接続を受け入れる → ESTABLISHED
  4. 以降は 57846 ↔ 9003 でやり取り
接続は必ず Xdebug 側から始めます。 VS Code から先に話しかけることはありません。 そのため VS Code は 9003 で待っているだけでよく、Xdebug 側のポートは気にしなくて大丈夫です。

ブレークポイントの情報はいつ送られるのか

Xdebug から接続しに行くのはわかったけど、ブレークポイントの場所を VS Code が最初に送らないといけないはず。 まだポートを知らない段階でどうやって送っているの?
ここがポイントで、「ブレークポイントに到達したから接続する」のではなく、「PHP が実行されたら問答無用で毎回接続する」 という順番になっています。
  1. VS Code が 9003 で待機
  2. PHP のリクエストが来る
    • Xdebug が起動して問答無用で 9003 に接続しにいく
    • (ブレークポイントの有無に関係なく毎回接続する)
  3. VS Code が接続を受け入れる → ESTABLISHED
    • ここで初めて VS Code が Xdebug にブレークポイントの場所を送る
  4. Xdebug がコードを1行ずつ実行
    • ブレークポイントの行に到達したら止まる
    • VS Code に通知
  5. 変数などのやり取り
「まず接続、そのあと情報交換」という順番がキモです。 Xdebug は毎リクエストごとに接続しにいくので、VS Code は接続してきたタイミングでブレークポイント情報を送り付けることができます。

PHP のリクエストとは

ここで言う「PHP のリクエスト」とは、ブラウザから URL にアクセスしたときに Apache や PHP に届く処理依頼のことです。
ブラウザ → http://127.0.0.1:8000/XXXX/XXXX/1 → PHP 実行開始
この「PHP 実行開始」のタイミングで Xdebug が起動し、9003 に接続しにきます。

リクエストごとの接続ライフサイクル

リクエストのたびに接続して、終わったら解除するの?
はい。 接続はリクエスト単位で生まれて、レスポンスを返し終わったら切れます。
リクエスト1  → Xdebug 接続 → ESTABLISHED  → PHP 実行  → レスポンス返す  → 接続解除 → TIME_WAIT → 消える リクエスト2  → Xdebug 接続 → ESTABLISHED  → PHP 実行  → レスポンス返す  → 接続解除
netstat で TIME_WAIT が見えるのは、直前のリクエストで使われた接続がまだ完全に消えていない名残です。 少し時間が経つと自然に消えます。

Xdebug 側のポートは毎回変わる

Xdebug 側のポートはリクエストのたびに OS に聞きに行って変わるの?
はい、毎回変わります。
リクエスト1 → OS が 57846 を割り当て → 9003 に接続 リクエスト2 → OS が 58210 を割り当て → 9003 に接続 リクエスト3 → OS が 59033 を割り当て → 9003 に接続
これは エフェメラルポート(短命ポート) と呼ばれる仕組みで、クライアント側の接続ポートは OS が空いている番号を自動で割り当てます。 Xdebug 固有の話ではなく、TCP 通信全般で共通の仕組みです。
VS Code 側は 9003 だけ固定で待っていれば、Xdebug 側のポートが毎回違っても問題なく通信できます。

まとめ

  • VS Code は 9003 で「待機」するだけ(LISTENING)
  • 接続は毎リクエストごとに Xdebug 側から始まる
  • Xdebug 側のポートは OS が自動で割り当てる(毎回違う)
  • 接続確立後に VS Code がブレークポイント情報を送る
  • リクエストが終わると接続は切れ、次のリクエストでまた新しく張り直す
netstat の出力にいろんな状態が並んでいて最初は戸惑いましたが、「どちらから接続しにいくのか」「どのポートが固定でどのポートが自動なのか」が整理できると、デバッグがつながらないときの切り分けも一気に楽になります。