PHP项目ESTABLISHED连接如何统计归属

wen PHP项目 24

本文目录导读:

PHP项目ESTABLISHED连接如何统计归属

  1. 方法一:使用 ss + lsof 组合(推荐,Linux)
  2. 方法二:使用 netstat + lsof(兼容性更广)
  3. 方法三:PHP 直接读取 /proc/net/tcp(无需外部命令,但复杂)
  4. 完整的 PHP 函数示例(基于 ss + lsof
  5. 注意事项
  6. 最终统计示例(输出归属)

针对PHP项目中获取“ESTABLISHED”状态的TCP连接并统计其归属(即关联的进程、脚本或用户),核心思路是利用操作系统提供的网络连接查看工具(如 ssnetstat),并在 PHP 中执行这些命令并解析输出。

由于 PHP 本身无法直接读取内核的 TCP 连接表,必须通过外部命令完成,以下是几种常用方法及步骤:


使用 ss + lsof 组合(推荐,Linux)

ss 速度快,能输出 socket 的 inode,lsof 可根据 inode 找到对应的 PHP 进程。

步骤:

  1. 获取所有 ESTABLISHED 状态的 TCP 连接及其 inode

    ss -t -e state established -p
    # 输出示例:
    # ESTAB      0      0      192.168.1.10:80    10.0.0.1:54321  timer:(keepalive,...)  uid:1000 ino:12345 sk:1
    # ESTAB      0      0      127.0.0.1:9000     127.0.0.1:40000  uid:33  ino:67890 sk:2
  2. 在 PHP 中执行并解析

    exec("ss -t -e state established -p 2>/dev/null", $output, $ret);
    if ($ret !== 0) {
        // 处理错误或 fallback 到 netstat
        die("ss 命令执行失败");
    }
    $connections = [];
    foreach ($output as $line) {
        // 跳过表头
        if (preg_match('/^ESTAB/', $line)) {
            // 提取 inode 号 (ino:XXXXX)
            preg_match('/ino:(\d+)/', $line, $inoMatch);
            $inode = $inoMatch[1] ?? null;
            // 提取本地/远程地址端口
            preg_match('/\s+(\S+):(\d+)\s+(\S+):(\d+)\s+/', $line, $addrMatch);
            if ($inode && $addrMatch) {
                $connections[] = [
                    'local'  => $addrMatch[1] . ':' . $addrMatch[2],
                    'remote' => $addrMatch[3] . ':' . $addrMatch[4],
                    'inode'  => $inode,
                ];
            }
        }
    }
    // 3. 根据 inode 查找对应的 PHP 进程
    foreach ($connections as &$conn) {
        exec("lsof -i TCP -i 'state established' -n -P | grep '{$conn['inode']}'", $procInfo);
        if (!empty($procInfo[0])) {
            // 输出如: php-fpm 12345 www-data 10u IPv4 12345 TCP 192.168.1.10:80->...
            $parts = preg_split('/\s+/', $procInfo[0]);
            $conn['process']  = $parts[0] ?? 'unknown';  // 进程名 (php-fpm, php-cgi 等)
            $conn['pid']      = $parts[1] ?? null;
            $conn['user']     = $parts[2] ?? null;
            $conn['fd_type']  = $parts[3] ?? null;
        }
    }
    print_r($connections);

使用 netstat + lsof(兼容性更广)

如果系统没有 ss(如某些旧版 Linux),可用 netstat

exec("netstat -tlnp 2>/dev/null | grep 'ESTABLISHED'", $output);
$connections = [];
foreach ($output as $line) {
    // 解析行: tcp  0  0 192.168.1.10:80  10.0.0.1:54321  ESTABLISHED 12345/php-fpm
    preg_match('/^tcp\s+\d+\s+\d+\s+(\S+:\d+)\s+(\S+:\d+)\s+ESTABLISHED\s+(\d+)\/(\S+)/', $line, $matches);
    if (count($matches) === 5) {
        $connections[] = [
            'local'   => $matches[1],
            'remote'  => $matches[2],
            'pid'     => (int)$matches[3],
            'process' => $matches[4],
        ];
    }
}

注意netstat-p 参数需 root 或特定权限才能查看其他进程的连接,PHP 进程(如 php-fpm worker)的连接也可用当前用户权限查看。


PHP 直接读取 /proc/net/tcp(无需外部命令,但复杂)

不再推荐,因为解析 /proc/net/tcp 需要处理六进制格式且无法直接获取进程 PID,仍要结合 /proc/[pid]/fd/lsof不如直接用方法一或二。


完整的 PHP 函数示例(基于 ss + lsof

function getEstablishedConnections(): array {
    $connections = [];
    // 步骤1:获取 ESTABLISHED TCP 连接的 inode 列表
    exec("ss -t -e state established -p 2>/dev/null", $ssOutput, $ret);
    if ($ret !== 0) {
        // fallback 到 netstat 方法
        return getEstablishedConnectionsWithNetstat();
    }
    // 步骤2:解析 ss 输出
    foreach ($ssOutput as $line) {
        if (strpos($line, 'ESTAB') !== 0) continue;
        // 提取地址和 inode
        preg_match('/^ESTAB\s+\d+\s+\d+\s+(\S+:\d+)\s+(\S+:\d+).*ino:(\d+)/', $line, $m);
        if (count($m) === 4) {
            $connections[] = [
                'local'  => $m[1],
                'remote' => $m[2],
                'inode'  => (int)$m[3],
            ];
        }
    }
    // 步骤3:通过 inode 查找进程
    $inodes = array_column($connections, 'inode');
    if (!empty($inodes)) {
        $inodeList = implode('|', $inodes);
        exec("lsof -i TCP -i 'state established' -n -P 2>/dev/null | grep -E '($inodeList)'", $lsofOutput);
        // 构建 inode => process 映射
        $inodeToProc = [];
        foreach ($lsofOutput as $line) {
            if (preg_match('/IPv4\s+(\d+)\s+TCP\s+(\S+)/', $line, $m)) {
                // 注意:lsof 输出格式可能变,需调试
                // 更稳健:用 awk 提取 inode 列(通常是第6或7列)
                $cols = preg_split('/\s+/', $line);
                $foundInode = null;
                foreach ($cols as $col) {
                    if (is_numeric($col) && strlen($col) > 4) { // inode 数通常 > 1000
                        $foundInode = (int)$col;
                        break;
                    }
                }
                if ($foundInode) {
                    $inodeToProc[$foundInode] = [
                        'process' => $cols[0] ?? '?',
                        'pid'     => (int)$cols[1] ?? 0,
                        'user'    => $cols[2] ?? '?',
                    ];
                }
            }
        }
        // 合并数据
        foreach ($connections as &$conn) {
            $ino = $conn['inode'];
            if (isset($inodeToProc[$ino])) {
                $conn = array_merge($conn, $inodeToProc[$ino]);
            }
        }
    }
    return $connections;
}

注意事项

  1. 权限问题:PHP 执行 ssnetstatlsof 通常需要 shell_exec/exec 权限,且这些命令可能受 SELinux/AppArmor 限制,生产环境中需配置 sudo 免密或使用 cap_net_admin 能力(不推荐)。
  2. 性能:频繁执行(如每几秒一次)可能开销大,建议将结果缓存(如写入 Redis/文件)并设定 TTL。
  3. Docker/容器:在容器内,/proc 只看到容器自己的网络命名空间,PHP 与 Web 服务不在同一容器,需通过共享命名空间或监听主机的 socket 文件。
  4. 统计归属:根据上述 process 字段可判断是 php-fpm(FastCGI)、php-cgi 还是 CLI 脚本(如 artisan queue:work),结合 remote 地址可进一步判断是用户请求、数据库连接还是内部服务。

最终统计示例(输出归属)

Array
(
    [0] => Array
        (
            [local] => 0.0.0.0:80
            [remote] => 10.0.0.1:54321
            [inode] => 12345
            [process] => php-fpm
            [pid] => 9876
            [user] => www-data
        )
    [1] => Array
        (
            [local] => 192.168.1.10:3306
            [remote] => 127.0.0.1:40000
            [inode] => 67890
            [process] => php
            [pid] => 1234
            [user] => deploy
        )
)

最稳定可靠的做法是用 ss + lsof 组合,解析 inode 关联进程,根据实际运行环境(是否有 root/权限、是否 Docker)调整实现。

抱歉,评论功能暂时关闭!