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
相关产品推荐
相关产品推荐

