PHP 不僅能寫 Web 腳本,通過 PCNTL + POSIX + Sockets 可編寫系統級 Daemon 服務。本文實現標準雙重 Fork 脫離終端,並構建一個極簡 HTTP Server,展示 PHP 服務端編程全貌。
*/
// 1. 業務邏輯:處理 HTTP 請求
function handleHttpRequest(string $address, int $port): void {
$maxBacklog = 16;
$response = "HTTP/1.1 200 OK\r\n"
. "Content-Length: 15\r\n"
. "Content-Type: text/plain; charset=UTF-8\r\n"
. "\r\n"
. "PHP HTTP Server";
$responseLen = strlen($response);
// 創建 Socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
die("socket_create failed: " . socket_strerror(socket_last_error()) . "\n");
}
// 端口復用(重啟快速佔用)
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
// 綁定
if (socket_bind($socket, $address, $port) === false) {
die("socket_bind failed: " . socket_strerror(socket_last_error()) . "\n");
}
// 監聽
if (socket_listen($socket, $maxBacklog) === false) {
die("socket_listen failed: " . socket_strerror(socket_last_error()) . "\n");
}
echo "HTTP Server listening on {$address}:{$port}\n";
// 事件循環
while (true) {
$client = socket_accept($socket);
if ($client === false) {
usleep(10000); // 短暫休眠
continue;
}
// 讀取請求(簡單讀取,生產需解析完整 HTTP)
$request = socket_read($client, 8192);
// 發送響應
socket_write($client, $response, $responseLen);
socket_close($client);
}
}
// 2. Daemon 化封裝
function daemonize(): void {
// 第一次 Fork
$pid1 = pcntl_fork();
if ($pid1 === -1) die("First fork failed\n");
if ($pid1 > 0) exit(0); // 父進程退出
// 子進程:創建新會話
if (posix_setsid() === -1) die("posix_setsid failed\n");
// 第二次 Fork
$pid2 = pcntl_fork();
if ($pid2 === -1) die("Second fork failed\n");
if ($pid2 > 0) exit(0); // 第一子進程退出
// 孫進程:真正的 Daemon
// 可選:切換工作目錄、umask、關閉標準流
chdir('/');
umask(0);
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
// 重定向到 /dev/null 或日誌文件
$null = fopen('/dev/null', 'r');
$log = fopen('/var/log/php_daemon.log', 'a');
$err = fopen('/var/log/php_daemon_error.log', 'a');
// 注意:fopen 返回的資源 ID 不一定是 0/1/2,需用 dup2 (PHP 無原生 dup2)
// 簡單做法:不關閉 STDIN/OUT/ERR,或用 proc_open 處理
}
// 3. 入口
daemonize();
handleHttpRequest('0.0.0.0', 9999);
| 技術點 | 函數/常量 | 作用 |
|---|---|---|
| 創建進程 | pcntl_fork() | 複製進程空間,返回 PID |
| 創建會話 | posix_setsid() | 脫離控制終端,成為會話首領 |
| 信號處理 | pcntl_signal() | 捕獲 SIGTERM/SIGINT 實現優雅關閉 |
| Socket 創建 | socket_create(AF_INET, SOCK_STREAM, SOL_TCP) | TCP IPv4 Socket |
| 端口復用 | socket_set_option(..., SO_REUSEADDR, 1) | 避免 TIME_WAIT 導致重啟失敗 |
| 非阻塞接受 | socket_accept() + 輪詢 / socket_select() | 單進程處理多連接(生產用 Swoole/ReactPHP) |
/var/run/php_daemon.pid,停止讀取 PID 發送信號,防止多實例。/etc/systemd/system/php-daemon.service,標準化管理啟動/重啟/日誌。set([
'worker_num' => 4,
'daemonize' => true, // 內置 daemonize
'pid_file' => '/var/run/swoole_http.pid',
'log_file' => '/var/log/swoole_http.log',
]);
$server->on('Request', function ($request, $response) {
$response->header("Content-Type", "text/plain");
$response->end("Swoole HTTP Server");
});
$server->start();
優勢:內置多進程/協程、靜態文件服務、SSL、WebSocket、熱重載、監控指標,**生產環境首選**。
PHP 編寫 Daemon 核心是雙重 Fork + setsid + 信號處理 + Socket 事件循環。理解這套機制有助於深入掌握進程模型,但**生產環境請直接使用 Swoole/OpenSwoole/Workerman** 等成熟框架,避免重複造輪子並獲得企業級穩定性。