首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP Socket 編程實戰:基於 UDP 協議的客戶端/服務端通信完整示例

     編輯:PHP 綜合知識

為什麼選擇 UDP?

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";
}

關鍵技術點解析

1. 消息邊界處理

UDP 保護消息邊界,但單個數據報超過 MTU(通常 1500 字節)會被分片,超過 64KB 會被丟棄。應用層需自定義協議:定長、分隔符、長度前綴或序列化框架。示例採用換行符 \n 作為簡單分隔符。

2. 非阻塞與多路複用

單進程處理高併發 UDP 需 stream_set_blocking(0) + stream_select(),避免 recvfrom 阻塞導致無法處理其他客戶端。生產環境建議使用 Swoole、Workerman 等高性能框架。

3. 可靠性補償

UDP 不保證送達、不重傳、不排序。關鍵業務需在應用層實現:序號+ACK、重傳定時器、去重窗口、滑動窗口流控。

4. 安全考量

  • 驗證源 IP/端口,防止偽造數據包
  • 限制單包大小,防止緩衝區溢出
  • 敏感數據啟用 DTLS 或應用層加密

擴展:基於 Swoole 的高性能 UDP 服務

<?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 等專業框架。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved