問題概要#

JSBSimスクリプト実行時に “Socket bind error: Address already in use” エラーが発生し、UDP通信が開始できません。

原因#

ポート5550が既に別のプロセスによって使用されていることが原因です。

詳細な原因#

  1. 前回実行の残骸: JSBSimスクリプトを強制終了(Ctrl+C)した際、ポートが解放されずに残った
  2. FlightGearが起動中: FlightGearがポート5550でリッスンしている
  3. TIME_WAIT状態: Windowsではプロセス終了後もポートが数秒~数分間保持される
  4. 他のアプリケーション: 別のアプリケーションがポート5550を使用している

よくあるケース:

  • Ctrl+Cでスクリプトを強制終了後、すぐに再実行
  • FlightGearを起動したまま、JSBSimスクリプトを再実行
  • 前回のJSBSimプロセスがバックグラウンドで動作中

解決方法#

Windows環境#

ステップ1: ポート5550を使用中のプロセスを確認

netstat -ano | findstr 5550

出力例:

UDP    0.0.0.0:5550           *:*                                    12345

ステップ2: プロセスIDからプロセス名を確認

tasklist | findstr 12345

出力例:

python.exe                   12345 Console                    1     50,000 K

ステップ3: プロセスを終了

taskkill /PID 12345 /F

または、タスクマネージャーから該当プロセスを終了。

Linux/Mac環境#

ステップ1: ポート5550を使用中のプロセスを確認

lsof -i :5550

出力例:

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python  12345 user    3u  IPv4 123456      0t0  UDP *:5550

ステップ2: プロセスを終了

kill -9 12345

スクリプトでの自動化(Python)#

SO_REUSEADDRオプションを使用:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# ポート再利用を許可
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

try:
    sock.bind(('localhost', 5550))
    print("✅ ポート5550: バインド成功")
except OSError as e:
    print(f"❌ ポート5550: バインド失敗 - {e}")
    exit(1)

利点:

  • TIME_WAIT状態のポートを再利用可能
  • 強制終了後すぐに再実行できる

注意:

  • SO_REUSEADDRは既存プロセスが完全に終了している場合のみ有効
  • プロセスが実行中の場合は、手動で終了する必要がある

予防策#

1. 適切な終了処理の実装#

try-finallyでソケットをクローズ:

import socket

sock = None
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('localhost', 5550))

    # シミュレーション実行
    while True:
        # ... UDP送信処理 ...
        pass

finally:
    if sock:
        sock.close()
        print("ソケットをクローズしました")

2. シグナルハンドラの実装#

Ctrl+C時に確実にソケットをクローズ:

import socket
import signal
import sys

sock = None

def signal_handler(sig, frame):
    """Ctrl+C時の処理"""
    print("\nシャットダウン中...")
    if sock:
        sock.close()
        print("ソケットをクローズしました")
    sys.exit(0)

# シグナルハンドラを登録
signal.signal(signal.SIGINT, signal_handler)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', 5550))

try:
    while True:
        # ... シミュレーション実行 ...
        pass
except Exception as e:
    print(f"エラー: {e}")
finally:
    if sock:
        sock.close()

3. 起動スクリプトに既存プロセスのクリーンアップを追加#

自動的にポート使用中のプロセスを終了:

import subprocess
import re

def cleanup_port(port=5550):
    """ポート使用中のプロセスを終了"""
    try:
        # Windows
        result = subprocess.run(['netstat', '-ano'], capture_output=True, text=True)
        for line in result.stdout.split('\n'):
            if f':{port}' in line and 'LISTENING' in line or 'UDP' in line:
                match = re.search(r'\s+(\d+)$', line)
                if match:
                    pid = match.group(1)
                    print(f"ポート{port}を使用中のプロセス(PID: {pid})を終了します...")
                    subprocess.run(['taskkill', '/PID', pid, '/F'], capture_output=True)
                    print("✅ プロセスを終了しました")
                    return True
    except Exception as e:
        print(f"クリーンアップ失敗: {e}")
    return False

# スクリプト開始前にクリーンアップ
cleanup_port(5550)

まとめ#

Socket bind error問題は、ポートが既に使用中であることが原因です。

原因:

  • 前回実行のJSBSimプロセスが残っている
  • FlightGearがポート5550でリッスン中
  • TIME_WAIT状態でポートが保持されている

解決策:

  • netstat/lsofでポート使用中のプロセスを確認
  • taskkill/killでプロセスを終了
  • SO_REUSEADDRオプションでポート再利用を許可

予防策:

  • try-finallyで確実にソケットをクローズ
  • シグナルハンドラでCtrl+C時の処理を実装
  • 起動スクリプトに既存プロセスのクリーンアップを追加

SO_REUSEADDRオプションとシグナルハンドラを組み合わせることで、確実にポート問題を回避できます。


関連記事#

  • 【トラブル備忘】Windows FirewallによるUDP通信ブロック - FlightGear接続失敗(記事E-16)
  • 【トラブル備忘】FlightGearに機体が表示されない - UDP通信ポート番号不一致(記事E-3)

© 2025 Yaaasoh. All Rights Reserved.