UDP(用戶數據報協議)無連接、低延遲、開銷小,適合即時性要求高、容忍少量丟包的場景:遊戲心跳、日誌收集、廣播發現、DNS 查詢、物聯網設備上報。PHP 的 stream_socket_* 系列函數提供了簡潔的流式 Socket API,無需安裝擴展即可開發高性能網絡程序。
<?php
// 配置
$listenAddr = 'udp://0.0.0.0:9998'; // 監聽所有網卡
$msgDelimiter = "\n"; // 消息邊界分隔符
$maxBuffer = 65535; // UDP 最大數據報
// 創建 Socket
$server = stream_socket_server($listenAddr, $errno, $errstr, STREAM_SERVER_BIND);
if (!$server) {
die("Socket 創建失敗: $errstr ($errno)\n");
}
echo "UDP 服務已啟動,監聽 $listenAddr\n";
// 設置非阻塞(可選,配合 stream_select 實現多路複用)
stream_set_blocking($server, 0);
$clients = []; // 記錄客戶端地址
while (true) {
// 使用 stream_select 實現非阻塞輪詢
$read = [$server];
$write = $except = null;
if (stream_select($read, $write, $except, 1) > 0) {
if (in_array($server, $read)) {
$data = stream_socket_recvfrom($server, $maxBuffer, 0, $peer);
if ($data === false) continue;
$data = rtrim($data, "\r\n");
echo "[$peer] 收到: $data\n";
// 記錄新客戶端
if (!isset($clients[$peer])) {
$clients[$peer] = time();
echo "新客戶端連接: $peer\n";
}
// 業務邏輯:原樣返回 + 時間戳
$response = "$data -- " . date('D M j H:i:s Y') . $msgDelimiter;
stream_socket_sendto($server, $response, 0, $peer);
}
}
// 定期清理超時客戶端(示例:30 秒無數據視為離線)
$now = time();
foreach ($clients as $addr => $lastTime) {
if ($now - $lastTime > 30) {
unset($clients[$addr]);
echo "客戶端超時移除: $addr\n";
}
}
}
<?php
function udp_client_send($message, $host = '127.0.0.1', $port = 9998, $timeout = 2.0) {
$socket = stream_socket_client("udp://$host:$port", $errno, $errstr, $timeout);
if (!$socket) {
throw new RuntimeException("連接失敗: $errstr ($errno)");
}
// 設置讀寫超時
stream_set_timeout($socket, $timeout);
// 發送數據(自動添加換行作為邊界)
fwrite($socket, $message . "\n");
// 讀取響應
$response = fread($socket, 8192);
$info = stream_get_meta_data($socket);
fclose($socket);
if ($info['timed_out']) {
throw new RuntimeException("讀取超時");
}
return rtrim($response, "\r\n");
}
// 測試
try {
$reply = udp_client_send('Hello World');
echo "服務端響應: $reply\n";
} catch (Exception $e) {
echo "錯誤: " . $e->getMessage() . "\n";
}
UDP 保護消息邊界,但單個數據報超過 MTU(通常 1500 字節)會被分片,超過 64KB 會被丟棄。應用層需自定義協議:定長、分隔符、長度前綴或序列化框架。示例採用換行符 \n 作為簡單分隔符。
單進程處理高併發 UDP 需 stream_set_blocking(0) + stream_select(),避免 recvfrom 阻塞導致無法處理其他客戶端。生產環境建議使用 Swoole、Workerman 等高性能框架。
UDP 不保證送達、不重傳、不排序。關鍵業務需在應用層實現:序號+ACK、重傳定時器、去重窗口、滑動窗口流控。
<?php
$server = new Swoole\Server('0.0.0.0', 9998, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
$server->set([
'worker_num' => 4,
'max_request' => 10000,
]);
$server->on('Packet', function ($server, $data, $clientInfo) {
$response = "$data -- " . date('c');
$server->sendto($clientInfo['address'], $clientInfo['port'], $response);
});
$server->start();
Swoole 以 C 扩展實現多進程 Reactor 模型,單機輕松支撐 10 萬+ 併發 UDP 連接,適合生產級高負載場景。
PHP 原生 Stream Socket 足以應對輕量級 UDP 通信需求。掌握 stream_socket_server/client、stream_select、消息幀協議設計,即可構建穩健的網絡服務。高併發、高可靠場景請轉向 Swoole/OpenSwoole/Workerman 等專業框架。