在 Linux 系統編程中,進程間通信(IPC)是核心課題之一。System V IPC 機制提供了消息隊列、信號量和共享內存三種通信方式。PHP 通過 sysvmsg 擴展封裝了 System V 消息隊列 API,使開發者能以腳本語言輕鬆實現高效的進程協作。本文將從基礎概念入手,通過完整代碼示例演示如何在 PHP 中創建、發送、接收及銷毀消息隊列,並結合 pcntl_fork 實現多進程異步通信場景。
| 函數 | 用途 | 關鍵參數 |
|---|---|---|
ftok() | 將路徑與項目標識轉換為 System V IPC 鍵值 | $pathname, $proj |
msg_get_queue() | 根據鍵值獲取或創建消息隊列資源 | $key, $perms (預設 0666) |
msg_send() | 向隊列寫入數據 | $queue, $msgtype, $message, $serialize, $blocking |
msg_receive() | 從隊列讀取數據 | $queue, $desiredmsgtype, $msgtype, $maxsize, $message, $unserialize, $flags |
msg_stat_queue() | 獲取隊列元數據(訊息數、最近讀寫 PID 等) | $queue |
msg_remove_queue() | 銷毀隊列並釋放資源 | $queue |
<?php
// 1. 生成唯一鍵值
$key = ftok(__FILE__, 'a');
// 2. 獲取/創建隊列
$queue = msg_get_queue($key, 0666);
var_dump($queue);
// 3. 查看初始狀態
$stat = msg_stat_queue($queue);
print_r($stat); // msg_qnum = 0
// 4. 發送訊息
msg_send($queue, 1, 'Hello, World!');
// 5. 再次查看狀態
$stat = msg_stat_queue($queue);
print_r($stat); // msg_qnum = 1, msg_stime 更新
// 6. 非阻塞接收
msg_receive($queue, 0, $type, 1024, $msg, true, MSG_IPC_NOWAIT);
echo $msg . PHP_EOL; // 輸出: Hello, World!
// 7. 清理資源
msg_remove_queue($queue);
?>
關鍵點解析:
ftok() 返回的鍵值在系統中唯一標識一個隊列,同一鍵值多次調用 msg_get_queue() 會返回同一隊列引用。msg_send() 預設序列化數據($serialize=true),接收端需設 $unserialize=true 自動反序列化。MSG_IPC_NOWAIT 標誌使 msg_receive() 非阻塞,隊列為空時立即返回 false 而非等待。結合 pcntl_fork() 創建子進程,父進程循環讀取隊列,子進程並發寫入,驗證消息隊列的 FIFO 特性與進程調度的非確定性。
<?php
$key = ftok(__FILE__, 'b');
$queue = msg_get_queue($key, 0666);
$pids = [];
// 創建 5 個子進程
for ($i = 0; $i < 5; $i++) {
$pids[$i] = pcntl_fork();
if ($pids[$i] > 0) {
// 父進程:記錄 PID
echo "No.$i child process created, pid={$pids[$i]}\n";
} elseif ($pids[$i] === 0) {
// 子進程:發送自身 PID 並退出
$pid = posix_getpid();
echo "process.$pid is writing now\n";
msg_send($queue, 1, "this is process.$pid's data\n");
posix_kill($pid, SIGTERM); // 子進程結束
exit(0);
}
}
// 父進程:非阻塞循環讀取
do {
$ret = msg_receive($queue, 0, $type, 1024, $msg, true, MSG_IPC_NOWAIT);
if ($ret) {
echo $msg;
} else {
// 隊列為空且所有子進程已結束時可 break
// 實際生產環境建議用 msg_stat_queue 判斷 msg_qnum
usleep(10000);
}
} while (true);
// 父進程最後清理
msg_remove_queue($queue);
?>
No.0 child process created, pid=5249
No.1 child process created, pid=5250
No.2 child process created, pid=5251
No.3 child process created, pid=5252
No.4 child process created, pid=5253
process.5251 is writing now
this is process.5251's data
process.5253 is writing now
process.5252 is writing now
process.5250 is writing now
this is process.5253's data
this is process.5252's data
this is process.5250's data
process.5249 is writing now
this is process.5249's data
結果印證了兩個特性:
msgtype=1)訊息在隊列中按發送順序排隊,接收端依序取出。msg_get_queue() 權限位(如 0660)需與運行用戶/組匹配,避免「Permission denied」。kernel.msgmnb(單隊列最大字節)與 kernel.msgmax(單條訊息最大字節)限制吞吐,大數據場景需調整 /etc/sysctl.conf。msg_remove_queue(),否則隊列殘留導致鍵值衝突或內核資源耗盡。$serialize=false),改用 JSON/Protobuf 等通用格式。