[PHP]stream_socket_get_nameでソケットのアドレス情報を取得する|接続元・接続先の特定と活用パターン実践ガイド

PHP

はじめに

TCPサーバーやクライアントを実装する際、「今どこと通信しているのか」「自分のIPとポートは何番か」を知りたい場面は多くあります。ログ記録・アクセス制限・デバッグなど、あらゆる場面でソケットのアドレス情報が必要になります。

stream_socket_get_name は、ストリームソケットの ローカル側またはリモート側のアドレスとポート を文字列で返す関数です。サーバー・クライアントを問わず、接続に関わるすべてのストリームで使えます。

この記事では、基本的な使い方から実践的なクラス実装まで、具体例とともに丁寧に解説します。


stream_socket_get_name とは

項目内容
関数名stream_socket_get_name
PHPバージョンPHP 5.0.0以降
カテゴリストリーム関数
返り値string(アドレス文字列)、false(失敗時)

構文

stream_socket_get_name(resource $socket, bool $remote): string|false

パラメータ

パラメータ説明
$socketresource対象のストリームソケット
$remotebooltrue でリモート側、false でローカル側のアドレスを返す

返り値

意味
string"IPアドレス:ポート番号" の形式(例:"93.184.216.34:80"
false失敗(ソケットが接続されていないなど)

ローカルとリモートの違い

【クライアント側から見た場合】

 自分(クライアント)              サーバー
  192.168.1.10:54321   ────────→  93.184.216.34:80

  stream_socket_get_name($sock, false) → "192.168.1.10:54321"  ← ローカル
  stream_socket_get_name($sock, true)  → "93.184.216.34:80"    ← リモート

【サーバー側から見た場合(accept後のクライアントストリーム)】

  クライアント                     自分(サーバー)
  192.168.1.10:54321   ────────→  0.0.0.0:8080

  stream_socket_get_name($client, false) → "0.0.0.0:8080"        ← ローカル
  stream_socket_get_name($client, true)  → "192.168.1.10:54321"  ← リモート(接続元)

基本的な使い方

<?php
// サーバーに接続
$socket = stream_socket_client('tcp://example.com:80', $errno, $errstr, 5.0);

if ($socket === false) {
    die("接続失敗: [{$errno}] {$errstr}");
}

// ローカル側(自分)のアドレス
$localAddr = stream_socket_get_name($socket, false);
echo "ローカル: {$localAddr}" . PHP_EOL; // 例: 192.168.1.10:54321

// リモート側(サーバー)のアドレス
$remoteAddr = stream_socket_get_name($socket, true);
echo "リモート: {$remoteAddr}" . PHP_EOL; // 例: 93.184.216.34:80

fclose($socket);

実践例(クラスを使った実装)

例1:アドレス情報をパースして使いやすくする SocketAddress クラス

返り値の文字列を IP・ポート・ホスト名などに分解する汎用クラスです。

<?php

class SocketAddress
{
    private string $raw;
    private string $ip;
    private int    $port;

    private function __construct(string $raw)
    {
        $this->raw = $raw;
        [$this->ip, $portStr] = $this->parse($raw);
        $this->port = (int) $portStr;
    }

    public static function fromSocket(resource $socket, bool $remote): ?self
    {
        $raw = stream_socket_get_name($socket, $remote);
        return $raw !== false ? new self($raw) : null;
    }

    public static function fromString(string $address): self
    {
        return new self($address);
    }

    public function getIp(): string
    {
        return $this->ip;
    }

    public function getPort(): int
    {
        return $this->port;
    }

    public function getRaw(): string
    {
        return $this->raw;
    }

    public function isPrivate(): bool
    {
        // RFC1918 プライベートアドレス
        return filter_var(
            $this->ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
        ) === false;
    }

    public function isLoopback(): bool
    {
        return $this->ip === '127.0.0.1' || $this->ip === '::1';
    }

    public function isIPv6(): bool
    {
        return str_contains($this->ip, ':');
    }

    public function getHostname(): string
    {
        $host = gethostbyaddr($this->ip);
        return $host !== false ? $host : $this->ip;
    }

    public function __toString(): string
    {
        return $this->raw;
    }

    private function parse(string $raw): array
    {
        // IPv6 の場合:[::1]:8080
        if (str_starts_with($raw, '[')) {
            preg_match('/^\[([^\]]+)\]:(\d+)$/', $raw, $m);
            return [$m[1] ?? $raw, $m[2] ?? '0'];
        }
        // IPv4 の場合:192.168.1.1:8080
        $pos = strrpos($raw, ':');
        if ($pos !== false) {
            return [substr($raw, 0, $pos), substr($raw, $pos + 1)];
        }
        return [$raw, '0'];
    }
}

// 使用例
$socket = stream_socket_client('tcp://example.com:80', $errno, $errstr, 5.0);

if ($socket) {
    $local  = SocketAddress::fromSocket($socket, false);
    $remote = SocketAddress::fromSocket($socket, true);

    if ($local && $remote) {
        echo "=== ローカル ===" . PHP_EOL;
        echo "  アドレス    : {$local->getRaw()}"                        . PHP_EOL;
        echo "  IP          : {$local->getIp()}"                         . PHP_EOL;
        echo "  ポート       : {$local->getPort()}"                      . PHP_EOL;
        echo "  プライベート: " . ($local->isPrivate()  ? 'はい' : 'いいえ') . PHP_EOL;
        echo "  ループバック: " . ($local->isLoopback() ? 'はい' : 'いいえ') . PHP_EOL;

        echo PHP_EOL . "=== リモート ===" . PHP_EOL;
        echo "  アドレス    : {$remote->getRaw()}"  . PHP_EOL;
        echo "  IP          : {$remote->getIp()}"   . PHP_EOL;
        echo "  ポート       : {$remote->getPort()}" . PHP_EOL;
        echo "  ホスト名     : {$remote->getHostname()}" . PHP_EOL;
    }

    fclose($socket);
}

出力例:

=== ローカル ===
  アドレス    : 192.168.1.10:54321
  IP          : 192.168.1.10
  ポート       : 54321
  プライベート: はい
  ループバック: いいえ

=== リモート ===
  アドレス    : 93.184.216.34:80
  IP          : 93.184.216.34
  ポート       : 80
  ホスト名     : 93.184.216.34.in-addr.arpa

例2:サーバー側でIPベースのアクセス制御を実装する

stream_socket_accept と組み合わせて、接続元IPに基づいてアクセスを許可・拒否します。

<?php

class AccessControlServer
{
    private $server;
    private array $allowList;
    private array $denyList;
    private array $connectionLog = [];

    public function __construct(
        string $address,
        array  $allowList = [],  // 空 = すべて許可
        array  $denyList  = []
    ) {
        $this->allowList = $allowList;
        $this->denyList  = $denyList;

        $this->server = stream_socket_server($address, $errno, $errstr);
        if (!$this->server) {
            throw new RuntimeException("起動失敗 [{$errno}]: {$errstr}");
        }

        echo "アクセス制御サーバー起動: {$address}" . PHP_EOL;
    }

    public function acceptWithControl(float $timeout = 5.0): void
    {
        $client = stream_socket_accept($this->server, $timeout, $peerName);

        if ($client === false) {
            echo "タイムアウト" . PHP_EOL;
            return;
        }

        // リモートアドレスを取得
        $remoteRaw = stream_socket_get_name($client, true);
        $localRaw  = stream_socket_get_name($client, false);

        $remoteIp  = $remoteRaw !== false ? explode(':', $remoteRaw)[0] : 'unknown';

        $allowed = $this->isAllowed($remoteIp);

        $this->connectionLog[] = [
            'time'     => date('H:i:s'),
            'remote'   => $remoteRaw,
            'local'    => $localRaw,
            'allowed'  => $allowed,
        ];

        if ($allowed) {
            echo "[許可] {$remoteRaw} → {$localRaw}" . PHP_EOL;
            fwrite($client, "200 接続を受け付けました({$remoteRaw})\r\n");
        } else {
            echo "[拒否] {$remoteRaw}" . PHP_EOL;
            fwrite($client, "403 アクセスが拒否されました({$remoteIp})\r\n");
        }

        fclose($client);
    }

    private function isAllowed(string $ip): bool
    {
        // 拒否リストに含まれていれば拒否
        foreach ($this->denyList as $denied) {
            if ($this->matchCidr($ip, $denied)) {
                return false;
            }
        }

        // 許可リストが空ならすべて許可
        if (empty($this->allowList)) {
            return true;
        }

        // 許可リストに含まれていれば許可
        foreach ($this->allowList as $allowed) {
            if ($this->matchCidr($ip, $allowed)) {
                return true;
            }
        }

        return false;
    }

    private function matchCidr(string $ip, string $cidr): bool
    {
        // 単純なIP比較(CIDR非対応の簡易版)
        return $ip === $cidr || str_starts_with($ip, rtrim($cidr, '*'));
    }

    public function printLog(): void
    {
        echo PHP_EOL . "=== 接続ログ ===" . PHP_EOL;
        foreach ($this->connectionLog as $entry) {
            $mark = $entry['allowed'] ? '✓' : '✗';
            echo "  [{$entry['time']}] {$mark} {$entry['remote']} → {$entry['local']}" . PHP_EOL;
        }
    }

    public function close(): void
    {
        fclose($this->server);
    }
}

// 使用例
$server = new AccessControlServer(
    'tcp://127.0.0.1:18083',
    allowList: ['127.0.0.1'],      // ループバックのみ許可
    denyList:  []
);

// テストクライアントで接続
$c1 = stream_socket_client('tcp://127.0.0.1:18083', $e, $es, 1);
$server->acceptWithControl(0.5);
if ($c1) {
    echo "応答: " . trim(fgets($c1)) . PHP_EOL;
    fclose($c1);
}

$server->printLog();
$server->close();

出力例:

アクセス制御サーバー起動: tcp://127.0.0.1:18083
[許可] 127.0.0.1:54322 → 127.0.0.1:18083
応答: 200 接続を受け付けました(127.0.0.1:54322)

=== 接続ログ ===
  [12:00:01] ✓ 127.0.0.1:54322 → 127.0.0.1:18083

例3:接続情報を詳細にログする接続トラッカー

複数クライアントの接続・通信・切断をソケットアドレスで追跡します。

<?php

class ConnectionTracker
{
    private array $connections = [];

    public function register(resource $socket): string
    {
        $local  = stream_socket_get_name($socket, false);
        $remote = stream_socket_get_name($socket, true);

        $id = spl_object_id((object) $socket) . '_' . microtime(true);

        $this->connections[$id] = [
            'id'           => $id,
            'local'        => $local  ?: 'N/A',
            'remote'       => $remote ?: 'N/A',
            'connected_at' => microtime(true),
            'bytes_sent'   => 0,
            'bytes_recv'   => 0,
            'closed_at'    => null,
            'close_reason' => null,
        ];

        echo "[登録] ID:{$id} | {$remote} → {$local}" . PHP_EOL;
        return $id;
    }

    public function recordSend(string $id, int $bytes): void
    {
        if (isset($this->connections[$id])) {
            $this->connections[$id]['bytes_sent'] += $bytes;
        }
    }

    public function recordRecv(string $id, int $bytes): void
    {
        if (isset($this->connections[$id])) {
            $this->connections[$id]['bytes_recv'] += $bytes;
        }
    }

    public function close(string $id, string $reason = '正常切断'): void
    {
        if (isset($this->connections[$id])) {
            $this->connections[$id]['closed_at']    = microtime(true);
            $this->connections[$id]['close_reason'] = $reason;

            $remote = $this->connections[$id]['remote'];
            echo "[切断] ID:{$id} | {$remote} ({$reason})" . PHP_EOL;
        }
    }

    public function getConnection(string $id): ?array
    {
        return $this->connections[$id] ?? null;
    }

    public function printReport(): void
    {
        echo PHP_EOL . "=== 接続トラッキングレポート ===" . PHP_EOL;

        foreach ($this->connections as $conn) {
            $duration = $conn['closed_at']
                ? round(($conn['closed_at'] - $conn['connected_at']) * 1000, 1) . 'ms'
                : '接続中';

            echo str_repeat('-', 50) . PHP_EOL;
            echo "  リモート  : {$conn['remote']}"   . PHP_EOL;
            echo "  ローカル  : {$conn['local']}"    . PHP_EOL;
            echo "  送信      : {$conn['bytes_sent']} bytes" . PHP_EOL;
            echo "  受信      : {$conn['bytes_recv']} bytes" . PHP_EOL;
            echo "  接続時間  : {$duration}"          . PHP_EOL;

            if ($conn['close_reason']) {
                echo "  切断理由  : {$conn['close_reason']}" . PHP_EOL;
            }
        }

        $total  = count($this->connections);
        $closed = count(array_filter($this->connections, fn($c) => $c['closed_at'] !== null));
        echo str_repeat('-', 50) . PHP_EOL;
        echo "合計: {$total} 接続(切断済み: {$closed})" . PHP_EOL;
    }
}

// 使用例
$server  = stream_socket_server('tcp://127.0.0.1:18084', $errno, $errstr);
$tracker = new ConnectionTracker();

// テスト用クライアントを3本作成
$clients = [];
for ($i = 0; $i < 3; $i++) {
    $c = stream_socket_client('tcp://127.0.0.1:18084', $e, $es, 1);
    if ($c) $clients[] = $c;
}

// サーバー側で受け付けてトラッキング
foreach ($clients as $c) {
    $accepted = stream_socket_accept($server, 0.5);
    if ($accepted) {
        $id = $tracker->register($accepted);

        $msg   = "Hello from server\n";
        $bytes = fwrite($accepted, $msg);
        $tracker->recordSend($id, $bytes ?: 0);

        $tracker->close($id);
        fclose($accepted);
    }
}

foreach ($clients as $c) fclose($c);
fclose($server);

$tracker->printReport();

出力例:

[登録] ID:xxx | 127.0.0.1:54323 → 127.0.0.1:18084
[登録] ID:xxx | 127.0.0.1:54324 → 127.0.0.1:18084
[登録] ID:xxx | 127.0.0.1:54325 → 127.0.0.1:18084
[切断] ID:xxx | 127.0.0.1:54323 (正常切断)
[切断] ID:xxx | 127.0.0.1:54324 (正常切断)
[切断] ID:xxx | 127.0.0.1:54325 (正常切断)

=== 接続トラッキングレポート ===
--------------------------------------------------
  リモート  : 127.0.0.1:54323
  ローカル  : 127.0.0.1:18084
  送信      : 18 bytes
  受信      : 0 bytes
  接続時間  : 0.8ms
  切断理由  : 正常切断
...
合計: 3 接続(切断済み: 3)

例4:ローカルアドレスを確認して動的バインドポートを検出する

0 番ポートで接続するとOSが空きポートを自動割り当てます。stream_socket_get_name でその番号を取得できます。

<?php

class DynamicPortDetector
{
    /**
     * OSに空きポートを割り当てさせてそのポート番号を返す
     */
    public static function getFreePort(string $bindAddress = '127.0.0.1'): int
    {
        // ポート0でバインドするとOSが空きポートを自動割り当て
        $server = stream_socket_server("tcp://{$bindAddress}:0", $errno, $errstr);

        if (!$server) {
            throw new RuntimeException("サーバー起動失敗 [{$errno}]: {$errstr}");
        }

        // ローカルアドレスからポート番号を取得
        $localAddr = stream_socket_get_name($server, false);
        fclose($server);

        if ($localAddr === false) {
            throw new RuntimeException("アドレス取得失敗");
        }

        // "127.0.0.1:XXXXX" からポート番号を抽出
        $port = (int) substr($localAddr, strrpos($localAddr, ':') + 1);
        return $port;
    }

    /**
     * クライアント接続時のエフェメラルポートを取得する
     */
    public static function getEphemeralPort(string $host, int $port): array
    {
        $socket = stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, 5.0);

        if ($socket === false) {
            throw new RuntimeException("接続失敗 [{$errno}]: {$errstr}");
        }

        $local  = stream_socket_get_name($socket, false);
        $remote = stream_socket_get_name($socket, true);
        fclose($socket);

        $localPort = $local !== false
            ? (int) substr($local, strrpos($local, ':') + 1)
            : 0;

        return [
            'local_address'  => $local,
            'local_port'     => $localPort,
            'remote_address' => $remote,
            'is_ephemeral'   => $localPort >= 49152, // IANA エフェメラルポート範囲
        ];
    }
}

// 使用例1:空きポートを取得
$freePorts = [];
for ($i = 0; $i < 5; $i++) {
    $freePorts[] = DynamicPortDetector::getFreePort();
}
echo "空きポート一覧: " . implode(', ', $freePorts) . PHP_EOL;

// 使用例2:クライアント接続時のエフェメラルポートを確認
echo PHP_EOL;
try {
    $info = DynamicPortDetector::getEphemeralPort('example.com', 80);
    echo "ローカルアドレス : {$info['local_address']}"                              . PHP_EOL;
    echo "ローカルポート   : {$info['local_port']}"                                 . PHP_EOL;
    echo "リモートアドレス : {$info['remote_address']}"                             . PHP_EOL;
    echo "エフェメラルポート: " . ($info['is_ephemeral'] ? 'はい' : 'いいえ')       . PHP_EOL;
} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . PHP_EOL;
}

出力例:

空きポート一覧: 52341, 52342, 52343, 52344, 52345

ローカルアドレス : 192.168.1.10:54899
ローカルポート   : 54899
リモートアドレス : 93.184.216.34:80
エフェメラルポート: はい

例5:STARTTLSの前後でアドレス情報の継続性を確認する

stream_socket_enable_crypto 前後でもアドレス情報が変わらないことを検証するデモです。

<?php

class TlsAddressInspector
{
    /**
     * TCP → TLS昇格の各フェーズでアドレス情報を取得して記録する
     */
    public function inspect(string $host, int $port = 443): array
    {
        $phases = [];

        // フェーズ1:TCP接続直後
        $socket = stream_socket_client(
            "tcp://{$host}:{$port}",
            $errno, $errstr, 5.0
        );

        if ($socket === false) {
            throw new RuntimeException("接続失敗 [{$errno}]: {$errstr}");
        }

        $phases['TCP接続後'] = $this->snapshot($socket);

        // フェーズ2:TLS有効化後
        stream_context_set_option($socket, 'ssl', 'verify_peer',      true);
        stream_context_set_option($socket, 'ssl', 'verify_peer_name', true);
        stream_context_set_option($socket, 'ssl', 'peer_name',        $host);

        $result = stream_socket_enable_crypto(
            $socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT
        );

        if ($result === true) {
            $phases['TLS有効化後'] = $this->snapshot($socket);

            // フェーズ3:HTTP送信後
            fwrite($socket, "GET / HTTP/1.0\r\nHost: {$host}\r\n\r\n");
            $phases['HTTP送信後'] = $this->snapshot($socket);
        }

        fclose($socket);
        return $phases;
    }

    private function snapshot(resource $socket): array
    {
        $local  = stream_socket_get_name($socket, false);
        $remote = stream_socket_get_name($socket, true);
        $meta   = stream_get_meta_data($socket);

        return [
            'local'    => $local  ?: 'N/A',
            'remote'   => $remote ?: 'N/A',
            'blocking' => $meta['blocked'],
            'tls'      => isset($meta['crypto']) ? ($meta['crypto']['protocol'] ?? 'none') : 'none',
        ];
    }
}

// 使用例
$inspector = new TlsAddressInspector();

try {
    $phases = $inspector->inspect('www.example.com', 443);

    echo str_pad("フェーズ",      16)
       . str_pad("ローカル",      24)
       . str_pad("リモート",      24)
       . "TLS" . PHP_EOL;
    echo str_repeat('-', 72) . PHP_EOL;

    foreach ($phases as $phase => $info) {
        echo str_pad($phase,          16)
           . str_pad($info['local'],  24)
           . str_pad($info['remote'], 24)
           . $info['tls'] . PHP_EOL;
    }

    echo PHP_EOL . "※ TCP → TLS 昇格後もローカル・リモートアドレスは変化しません" . PHP_EOL;

} catch (RuntimeException $e) {
    echo "エラー: " . $e->getMessage() . PHP_EOL;
}

出力例:

フェーズ        ローカル                リモート                TLS
------------------------------------------------------------------------
TCP接続後       192.168.1.10:54901      93.184.216.34:443       none
TLS有効化後     192.168.1.10:54901      93.184.216.34:443       TLSv1.3
HTTP送信後      192.168.1.10:54901      93.184.216.34:443       TLSv1.3

※ TCP → TLS 昇格後もローカル・リモートアドレスは変化しません

例6:マルチクライアントサーバーで全接続情報を一覧表示する

stream_select と組み合わせ、全クライアントのアドレスをリアルタイムで管理します。

<?php

class ConnectionRegistry
{
    private $server;
    private array $clients   = [];
    private array $addresses = [];

    public function __construct(string $address)
    {
        $this->server = stream_socket_server($address, $errno, $errstr);
        if (!$this->server) {
            throw new RuntimeException("起動失敗 [{$errno}]: {$errstr}");
        }
        stream_set_blocking($this->server, false);
    }

    public function tick(): void
    {
        $read   = array_merge([$this->server], $this->clients);
        $write  = $except = null;

        if (stream_select($read, $write, $except, 0, 50_000) === false) {
            return;
        }

        foreach ($read as $sock) {
            if ($sock === $this->server) {
                $client = stream_socket_accept($this->server, 0);
                if ($client) {
                    stream_set_blocking($client, false);
                    $id = (int) $client;

                    // 両方向のアドレスを取得して記録
                    $local  = stream_socket_get_name($client, false) ?: 'N/A';
                    $remote = stream_socket_get_name($client, true)  ?: 'N/A';

                    $this->clients[$id]   = $client;
                    $this->addresses[$id] = [
                        'local'        => $local,
                        'remote'       => $remote,
                        'connected_at' => date('H:i:s'),
                    ];

                    echo "[+] 接続: {$remote}" . PHP_EOL;
                    fwrite($client, "接続しました(あなたのアドレス: {$remote})\n");
                }
            } else {
                $id   = (int) $sock;
                $data = fread($sock, 1024);

                if ($data === false || $data === '' || feof($sock)) {
                    $addr = $this->addresses[$id]['remote'] ?? 'unknown';
                    echo "[-] 切断: {$addr}" . PHP_EOL;
                    fclose($sock);
                    unset($this->clients[$id], $this->addresses[$id]);
                }
            }
        }
    }

    public function printTable(): void
    {
        echo PHP_EOL . "=== 接続一覧 ===" . PHP_EOL;
        echo str_pad("リモート",      24)
           . str_pad("ローカル",      24)
           . "接続時刻" . PHP_EOL;
        echo str_repeat('-', 58) . PHP_EOL;

        if (empty($this->addresses)) {
            echo "  接続なし" . PHP_EOL;
        }

        foreach ($this->addresses as $info) {
            echo str_pad($info['remote'],       24)
               . str_pad($info['local'],        24)
               . $info['connected_at'] . PHP_EOL;
        }

        echo "合計: " . count($this->addresses) . " 接続" . PHP_EOL;
    }

    public function shutdown(): void
    {
        foreach ($this->clients as $c) fclose($c);
        fclose($this->server);
    }
}

// 使用例
$registry = new ConnectionRegistry('tcp://127.0.0.1:18085');

// テストクライアントを3本接続
$testClients = [];
for ($i = 0; $i < 3; $i++) {
    $tc = stream_socket_client('tcp://127.0.0.1:18085', $e, $es, 1);
    if ($tc) $testClients[] = $tc;
}

// 数回 tick して接続を処理
for ($i = 0; $i < 5; $i++) {
    $registry->tick();
}

$registry->printTable();

foreach ($testClients as $tc) fclose($tc);
$registry->shutdown();

出力例:

[+] 接続: 127.0.0.1:54326
[+] 接続: 127.0.0.1:54327
[+] 接続: 127.0.0.1:54328

=== 接続一覧 ===
リモート                ローカル                接続時刻
----------------------------------------------------------
127.0.0.1:54326         127.0.0.1:18085         12:00:01
127.0.0.1:54327         127.0.0.1:18085         12:00:01
127.0.0.1:54328         127.0.0.1:18085         12:00:01
合計: 3 接続

関連する関数との比較

関数役割
stream_socket_get_nameソケットのローカル/リモートアドレスを取得
stream_socket_clientソケット接続を確立(クライアント側)
stream_socket_serverリスニングソケットを作成(サーバー側)
stream_socket_accept接続を受け付け($peer_name 引数でも取得可)
stream_get_meta_dataソケットのメタ情報全般を取得
gethostbyaddrIPアドレスからホスト名を逆引き

stream_socket_accept の $peer_name との違い

// stream_socket_accept の $peer_name でリモートアドレスを取得
$client = stream_socket_accept($server, 30, $peerName);
echo $peerName; // "192.168.1.10:54321"(accept時の1回のみ)

// stream_socket_get_name は accept 後いつでも取得可能
$remote = stream_socket_get_name($client, true);
$local  = stream_socket_get_name($client, false);
観点$peer_name(accept引数)stream_socket_get_name
取得できる情報リモートのみローカル・リモート両方
取得タイミングaccept時のみいつでも呼び出し可能
用途シンプルな接続元ログ詳細な接続情報管理

よくある注意点・落とし穴

1. 接続前・切断後は false を返す

ソケットが接続状態にない場合は false が返ります。必ずチェックしてください。

$socket = stream_socket_client('tcp://example.com:80', $e, $es, 5);

// 接続前はアドレスが取れない(stream_socket_client 直後は接続済みだが念のため確認)
$name = stream_socket_get_name($socket, true);
if ($name === false) {
    echo "アドレス取得失敗";
}

2. UNIXドメインソケットはポートがない

unix:// スキームのソケットでは IP:ポート形式ではなく、ソケットファイルのパスが返ります。

$socket = stream_socket_client('unix:///var/run/app.sock');
$name   = stream_socket_get_name($socket, false);
// → "/var/run/app.sock" (IPアドレスでなくパスが返る)

3. IPv6 アドレスは括弧付きで返る

IPv6 の場合、返り値は [::1]:8080 のような形式になります。パース時は注意が必要です。

$name = stream_socket_get_name($socket, true);
// IPv6 の例: "[::1]:8080"

// IPv6 対応のパース
if (str_starts_with($name, '[')) {
    preg_match('/^\[([^\]]+)\]:(\d+)$/', $name, $m);
    $ip   = $m[1]; // "::1"
    $port = $m[2]; // "8080"
}

4. リスニングソケット自体に $remote=true は使えない

stream_socket_server で作成したリスニングソケットに対してリモートアドレスを取得しようとすると false になります。

$server = stream_socket_server('tcp://0.0.0.0:8080');
echo stream_socket_get_name($server, false); // "0.0.0.0:8080"(OK)
echo stream_socket_get_name($server, true);  // false(リモートは存在しない)

まとめ

項目内容
関数名stream_socket_get_name(resource $socket, bool $remote): string|false
$remote=falseローカル側(自分)のアドレスを返す
$remote=trueリモート側(相手)のアドレスを返す
返り値形式"IPアドレス:ポート"(IPv6は "[::1]:port"
UNIXソケットソケットファイルのパスを返す
注意点未接続・切断後は false、リスニングソケットへの truefalse
PHP バージョンPHP 5.0.0 以上

stream_socket_get_name はシンプルながら、TCPサーバー・クライアントの実装において接続管理・アクセス制御・ログ記録など多くの場面で活躍する重要な関数です。stream_socket_accept$peer_name では取れないローカルアドレスも取得できる点が大きな強みです。

IPv6対応のパース処理や、UNIXドメインソケットでの挙動の違いを押さえつつ、接続情報の可視化や制御に積極的に活用してみてください。

タイトルとURLをコピーしました