管道是 Unix/Linux 系統中最古老的進程間通訊(IPC)機制之一。可視為一個先進先出(FIFO)的佇列:寫入端將資料放入管道尾端,讀取端從管道頭部取出。管道具有以下特性:
stream_set_blocking() 切換為非阻塞模式。/tmp)。以下封裝建立、讀寫、關閉、刪除管道的完整類別,支援阻塞與非阻塞模式:
<?php
class Pipe {
private $fifoPath;
private $wPipe; // 寫入控制代碼
private $rPipe; // 讀取控制代碼
/**
* 建立命名管道
* @param string $name 管道名稱
* @param int $mode 權限(預設 0666 所有人可讀寫)
*/
public function __construct($name = 'pipe', $mode = 0666) {
$this->fifoPath = "/tmp/{$name}" . posix_getpid();
if (!file_exists($this->fifoPath)) {
if (!posix_mkfifo($this->fifoPath, $mode)) {
throw new RuntimeException("建立管道失敗: {$this->fifoPath}");
}
} else {
throw new RuntimeException("管道已存在: {$this->fifoPath}");
}
}
// ========== 寫入相關 ==========
public function openWrite() {
$this->wPipe = fopen($this->fifoPath, 'w');
if (!$this->wPipe) throw new RuntimeException("開啟管道寫入失敗");
return true;
}
public function write($data) {
return fwrite($this->wPipe, $data);
}
public function writeAll($data) {
$fp = fopen($this->fifoPath, 'w');
$written = fwrite($fp, $data);
fclose($fp);
return $written;
}
public function closeWrite() {
return fclose($this->wPipe);
}
// ========== 讀取相關 ==========
public function openRead() {
$this->rPipe = fopen($this->fifoPath, 'r');
if (!$this->rPipe) throw new RuntimeException("開啟管道讀取失敗");
return true;
}
public function read($bytes = 1024) {
return fread($this->rPipe, $bytes);
}
public function readAll() {
$fp = fopen($this->fifoPath, 'r');
$data = '';
while (!feof($fp)) {
$data .= fread($fp, 1024);
}
fclose($fp);
return $data;
}
public function closeRead() {
return fclose($this->rPipe);
}
// ========== 工具方法 ==========
public function setNonBlocking($handle) {
return stream_set_blocking($handle, 0);
}
public function remove() {
return unlink($this->fifoPath);
}
public function __destruct() {
$this->remove();
}
}
使用 pcntl_fork() 建立子進程,父進程寫入,子進程讀取:
<?php
require 'Pipe.php';
$pipe = new Pipe('demo_pipe');
$pid = pcntl_fork();
if ($pid == -1) {
die('Fork 失敗');
} elseif ($pid > 0) {
// 父進程:生產者
$pipe->openWrite();
$pipe->setNonBlocking($pipe->wPipe);
for ($i = 1; $i <= 10; $i++) {
$msg = "訊息 #$i " . date('H:i:s') . "\n";
$pipe->write($msg);
echo "[父] 寫入: $msg";
sleep(1);
}
$pipe->closeWrite();
pcntl_wait($status); // 等待子進程結束
echo "[父] 完成\n";
} else {
// 子進程:消費者
$pipe->openRead();
$pipe->setNonBlocking($pipe->rPipe);
$count = 0;
while ($count < 10) {
$data = $pipe->read(1024);
if ($data !== '' && $data !== false) {
echo "[子] 讀取: $data";
$count++;
} else {
usleep(100000); // 0.1 秒避免 CPU 空轉
}
}
$pipe->closeRead();
exit(0);
}
--enable-pcntl 且在 Linux/Unix 環境。fwrite() 會等待讀取端;非阻塞模式需自行處理 EAGAIN 錯誤。SIGTERM、SIGCHLD 訊號處理器,優雅關閉管道並清理資源。PIPE_BUF(通常 4096/65536 位元組)可能非原子性,大數據建議分塊寫入。| 方案 | 優點 | 缺點 |
|---|---|---|
| 命名管道 (FIFO) | 系統級、跨進程、支援檔案權限 | 單向、僅限本機、大小固定 |
| Unix Domain Socket | 雙向、高效能、支援傳遞檔案描述符 | 程式較複雜 |
| Message Queue (sysvmsg) | 持久化、優先級、結構化訊息 | 需核心支援、設定較繁 |
| Redis/RabbitMQ | 分散式、高可用、功能豐富 | 引入外部依賴、延遲較高 |
注意:Windows 不支援 posix_mkfifo,若需跨平台請改用 Socket 或佇列服務。