PHP sockets 擴展提供了更底層、更接近 C 語言 BSD Socket API 的介面,支援 TCP/UDP、Unix Domain、原始 Socket 等,適合開發高效能網路服務、自定義協定伺服器/客戶端。本文系統整理 Socket 建立、綁定、監聽、接受、讀寫、關閉、非阻塞、配對等核心操作與實戰範例。
# 安裝 sockets 擴展 (通常預設編譯包含)
php -m | grep sockets
# 或安裝
apt-get install php-sockets # Debian/Ubuntu
yum install php-sockets # CentOS/RHEL
| 函數 | 用途 | 關鍵參數 |
|---|---|---|
| socket_create | 建立 Socket | domain, type, protocol |
| socket_bind | 綁定 IP/埠 | socket, address, port |
| socket_listen | 開始監聽 | socket, backlog |
| socket_accept | 接受連線 | socket |
| socket_read | 讀取資料 | socket, length, type |
| socket_write | 寫入資料 | socket, buffer, length |
| socket_close | 關閉 Socket | socket |
| socket_connect | 發起連線 | socket, address, port |
| socket_set_nonblock | 設定非阻塞 | socket |
| socket_set_block | 設定阻塞 | socket |
| socket_select | 多路複用 | read, write, except, tv_sec |
| socket_create_pair | 建立 Socket 配對 | domain, type, protocol, array |
| socket_strerror | 錯誤訊息 | errno |
| socket_last_error | 最後錯誤碼 | socket |
<?php
$ip = '127.0.0.1';
$port = 1000;
// 1. 建立 IPv4 TCP Socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) die("建立失敗: " . socket_strerror(socket_last_error()));
// 2. 綁定位址
if (!socket_bind($socket, $ip, $port)) {
die("綁定失敗: " . socket_strerror(socket_last_error($socket)));
}
// 3. 監聽 (backlog 5)
if (!socket_listen($socket, 5)) {
die("監聽失敗: " . socket_strerror(socket_last_error($socket)));
}
echo "伺服器啟動於 $ip:$port\n";
// 4. 接受連線 (阻塞)
$client = socket_accept($socket);
if (!$client) die("接受失敗: " . socket_strerror(socket_last_error($socket)));
// 5. 寫入歡迎訊息
$welcome = "歡迎來到 PHP Socket 伺服器!\n";
socket_write($client, $welcome, strlen($welcome));
// 6. 讀取客戶端資料 (簡易回聲)
while (($buf = socket_read($client, 1024, PHP_NORMAL_READ)) !== false) {
$buf = trim($buf);
if ($buf === 'quit') break;
socket_write($client, "Echo: $buf\n", strlen("Echo: $buf\n"));
}
// 7. 關閉
socket_close($client);
socket_close($socket);
<?php
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) die(socket_strerror(socket_last_error()));
// 清除錯誤狀態
socket_clear_error($socket);
// ... 正常操作 ...
// 確保關閉
socket_close($socket);
<?php
$sockets = [];
// 建立一對相連的 Unix Stream Socket
if (!socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $sockets)) {
die("建立配對失敗: " . socket_strerror(socket_last_error()));
}
$parent = $sockets[0];
$child = $sockets[1];
// 父進程寫入
if (!socket_write($parent, "Hello Child\n", 12)) {
die("寫入失敗: " . socket_strerror(socket_last_error($parent)));
}
// 子進程讀取
$data = socket_read($child, 1024, PHP_BINARY_READ);
if ($data === false) {
die("讀取失敗: " . socket_strerror(socket_last_error($child)));
}
var_dump($data); // string(12) "Hello Child\n"
// 關閉
socket_close($parent);
socket_close($child);
<?php
$ip = '127.0.0.1';
$port = 1000;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) die(socket_strerror(socket_last_error()));
// 設為非阻塞
socket_set_nonblock($socket);
// 發起連線 (立即返回,不等待)
$result = socket_connect($socket, $ip, $port);
// 連線狀態檢查
if ($result === false) {
$err = socket_last_error($socket);
if ($err !== EINPROGRESS && $err !== EALREADY && $err !== EWOULDBLOCK) {
die("連線啟動失敗: " . socket_strerror($err));
}
}
// 使用 socket_select 等待可寫 (連線完成)
$read = $write = $except = [$socket];
$tv_sec = 5;
$sel = socket_select($read, $write, $except, $tv_sec);
if ($sel === false) {
die("select 錯誤: " . socket_strerror(socket_last_error()));
} elseif ($sel === 0) {
die("連線逾時 (5秒)\n");
}
// 檢查連線結果
if (isset($write[0])) {
// 連線成功,改回阻塞模式進行後續通訊
socket_set_block($socket);
echo "連線成功!\n";
// 發送測試資料
socket_write($socket, "TEST\n", 5);
$resp = socket_read($socket, 1024);
echo "回應: $resp\n";
} else {
// 連線失敗
$err = socket_last_error($socket);
echo "連線失敗: " . socket_strerror($err) . "\n";
}
socket_close($socket);
<?php
class SocketListener {
private $socket;
private $port;
private $clients = [];
public function __construct(int $port = 8080) {
$this->port = $port;
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->socket) {
throw new RuntimeException("建立 Socket 失敗: " . socket_strerror(socket_last_error()));
}
// 設定選項:地址重用
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1);
if (!socket_bind($this->socket, '0.0.0.0', $this->port)) {
throw new RuntimeException("綁定失敗: " . socket_strerror(socket_last_error($this->socket)));
}
if (!socket_listen($this->socket, SOMAXCONN)) {
throw new RuntimeException("監聽失敗: " . socket_strerror(socket_last_error($this->socket)));
}
// 非阻塞模式
socket_set_nonblock($this->socket);
echo "SocketListener 啟動於 port {$this->port}\n";
}
public function run() {
while (true) {
$read = [$this->socket];
$write = $except = [];
// 加入所有客戶端到讀取集合
foreach ($this->clients as $client) {
$read[] = $client;
}
$ready = @socket_select($read, $write, $except, 1); // 1秒超時
if ($ready === false) continue;
// 新連線
if (in_array($this->socket, $read)) {
$client = socket_accept($this->socket);
if ($client) {
socket_set_nonblock($client);
$this->clients[] = $client;
echo "新客戶端連線: " . count($this->clients) . "\n";
}
}
// 處理客戶端資料
foreach ($this->clients as $i => $client) {
if (!in_array($client, $read)) continue;
$data = @socket_read($client, 4096, PHP_NORMAL_READ);
if ($data === false || $data === '') {
// 客戶端斷線
echo "客戶端斷線\n";
socket_close($client);
unset($this->clients[$i]);
continue;
}
$data = trim($data);
echo "收到: $data\n";
// 簡易回聲
$response = "Server: $data\n";
@socket_write($client, $response, strlen($response));
if ($data === 'quit') {
socket_close($client);
unset($this->clients[$i]);
}
}
}
}
public function __destruct() {
foreach ($this->clients as $c) socket_close($c);
if ($this->socket) socket_close($this->socket);
}
}
// 使用
// $server = new SocketListener(9000);
// $server->run();
| 問題 | 原因 | 解決 |
|---|---|---|
| Address already in use | 埠被佔用或 TIME_WAIT | SO_REUSEADDR、等待或換埠 |
| Permission denied | 權限不足 (port < 1024) | 用 >1024 埠或 root/sudo |
| Connection refused | 伺服器未啟動/防火牆 | 確認伺服器、防火牆規則 |
| Resource temporarily unavailable | 非阻塞下無資料 | 正常,繼續 select |
| Broken pipe | 寫入已關閉的連線 | 檢查回傳值、捕獲 SIGPIPE |
socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1)socket_set_option($sock, SOL_SOCKET, SO_RCVBUF, 65536)socket_set_option($sock, SOL_TCP, TCP_NODELAY, 1) 關閉 Nagle 演算法pcntl_fork 或 Swoole\Process\Pool 多核心處理PHP Socket 擴展提供了完整的 BSD Socket API 對應,能構建高效能網路應用。掌握 socket_create、bind、listen、accept、read/write、select、nonblock 等核心流程,並結合非阻塞 I/O 與多路複用,即可開發出支援高併發的伺服器程式。生產環境建議使用成熟的事件驅動框架 (Swoole/ReactPHP) 而非手寫迴圈。