本文目录导读:

- 方法一:使用
ss+lsof组合(推荐,Linux) - 方法二:使用
netstat+lsof(兼容性更广) - 方法三:PHP 直接读取
/proc/net/tcp(无需外部命令,但复杂) - 完整的 PHP 函数示例(基于
ss+lsof) - 注意事项
- 最终统计示例(输出归属)
针对PHP项目中获取“ESTABLISHED”状态的TCP连接并统计其归属(即关联的进程、脚本或用户),核心思路是利用操作系统提供的网络连接查看工具(如 ss 或 netstat),并在 PHP 中执行这些命令并解析输出。
由于 PHP 本身无法直接读取内核的 TCP 连接表,必须通过外部命令完成,以下是几种常用方法及步骤:
使用 ss + lsof 组合(推荐,Linux)
ss 速度快,能输出 socket 的 inode,lsof 可根据 inode 找到对应的 PHP 进程。
步骤:
-
获取所有 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
-
在 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;
}
注意事项
- 权限问题:PHP 执行
ss、netstat、lsof通常需要shell_exec/exec权限,且这些命令可能受 SELinux/AppArmor 限制,生产环境中需配置sudo免密或使用cap_net_admin能力(不推荐)。 - 性能:频繁执行(如每几秒一次)可能开销大,建议将结果缓存(如写入 Redis/文件)并设定 TTL。
- Docker/容器:在容器内,
/proc只看到容器自己的网络命名空间,PHP 与 Web 服务不在同一容器,需通过共享命名空间或监听主机的 socket 文件。 - 统计归属:根据上述
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)调整实现。