You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP中如何优化无限循环监听Socket的CPU占用问题?

PHP Socket监听优化方案(解决高CPU占用问题)

你的代码出现高CPU占用的核心原因是两层无限空循环:外层非阻塞socket_accept后无等待直接循环,内层子进程里非阻塞socket_recv也无等待循环,导致CPU被空轮询占满。以下是几种优化方案:

方案一:使用IO多路复用(socket_select,推荐)

利用socket_select监听socket的可读事件,只有当有新连接或客户端发数据时才会执行对应逻辑,彻底避免空循环。同时处理子进程回收,防止僵尸进程:

<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
$port = 1234;
socket_bind($socket, '192.168.1.1', $port);
socket_listen($socket);
// 将监听socket加入可读集合
$readSockets = [$socket];
// 处理子进程退出的信号
pcntl_signal(SIGCHLD, function() {
    while(pcntl_waitpid(-1, $status, WNOHANG) > 0);
});

while (true) {
    $activeRead = $readSockets;
    // 阻塞等待可读事件,无事件时会休眠,不占用CPU
    if (socket_select($activeRead, $write = null, $except = null, null) === false) {
        continue;
    }

    foreach ($activeRead as $sock) {
        // 新连接到来
        if ($sock === $socket) {
            $newConnection = socket_accept($socket);
            if ($newConnection === false) continue;

            $childProcessID = pcntl_fork();
            if ($childProcessID == -1) {
                socket_close($newConnection);
                continue;
            } else if (!$childProcessID) {
                // 子进程处理客户端连接,切换为阻塞模式避免空循环
                socket_set_block($newConnection);
                while (true) {
                    $bytes = socket_recv($newConnection, $currentMsg, 2048, 0);
                    if ($bytes <= 0) {
                        // 客户端断开连接,关闭socket并退出子进程
                        socket_close($newConnection);
                        exit();
                    }
                    // 处理消息逻辑
                    // echo "Received: $currentMsg\n";
                }
            } else {
                // 父进程关闭连接句柄(子进程已复制句柄)
                socket_close($newConnection);
            }
        }
    }
    // 分发信号,处理子进程回收
    pcntl_signal_dispatch();
}

socket_close($socket);
?>

方案二:简单休眠(快速改法)

如果不想重构代码,在两层循环的空分支里加入短暂休眠,让CPU获得空闲时间:

<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$port = "1234";
socket_bind($socket, '192.168.1.1', $port);
socket_listen($socket);
socket_set_nonblock($socket);
// 处理子进程回收
pcntl_signal(SIGCHLD, function() {
    while(pcntl_waitpid(-1, $status, WNOHANG) > 0);
});

while (true) {
    $newConnection = socket_accept($socket);
    if ($newConnection !== false) {
        $childProcessID = pcntl_fork();
        if ($childProcessID == -1) {
            throw new Exception('Could not fork');
        } else if (!$childProcessID) {
            $myProcessID = getmypid();
            $lastActive = time();

            while (true) {
                if (socket_recv($newConnection, $currentMsg, 2045, MSG_DONTWAIT) >= 1) {
                    // Process Message

                } else {
                    // 无数据时休眠10毫秒,降低CPU占用
                    usleep(10000);
                }
            }
        } else {
            socket_close($newConnection);
        }
    } else {
        // 无新连接时休眠10毫秒
        usleep(10000);
    }
    pcntl_signal_dispatch();
}
?>

关键优化点说明

  • IO多路复用:socket_select是阻塞式监听,只有当socket有事件时才会唤醒进程,彻底解决空轮询问题,是高性能场景的首选。
  • 子进程回收:通过pcntl_signal处理SIGCHLD信号,用pcntl_waitpid回收退出的子进程,避免系统中出现大量僵尸进程。
  • 阻塞模式切换:子进程中可以将socket设为阻塞模式,这样socket_recv会等待数据到来,无需空循环,进一步降低CPU占用。

内容的提问来源于stack exchange,提问作者Sim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 18:32:18