Swoole 中使用 TCP 異步服務器、TCP 協程服務器、TCP 同步客戶端、TCP 協程客戶端


TCP 異步風格服務器

異步風格服務器通過監聽事件的方式來編寫程序。當對應的事件發生時底層會主動回調指定的函數。

由於默認開啟協程化,在回調函數內部會自動創建協程,遇到 IO 會產生協程調度,異步風格服務器無法保證調度順序,所以在遇到並發時無法保證事件執行順序。

# server.php

// 創建 TCP 服務器對象,監聽 0.0.0.0:9501端口
$serv = new Swoole\Server("0.0.0.0", 9501); 

// 設置服務器運行參數
$serv->set(array(
    'daemonize'     => 1,  // 作為守護進程運行,需同時設置log_file
    'log_file'      => '/www/logs/swoole.log',  // 指定標准輸出和錯誤日志寫入的文件
));

// 監聽連接進入事件
// $fd 為客戶端連接的唯一標識符
$serv->on('Connect', function ($serv, $fd) {
    echo "Client: Connect: {$fd} .\n";
});

// 監聽數據接收事件
$serv->on('Receive', function ($serv, $fd, $from_id, $data) {
    // 向指定的客戶端連接發送數據
    $serv->send($fd, "Server: " . $data);
});

// 監聽連接關閉事件
$serv->on('Close', function ($serv, $fd) {
    echo "Client: Close: {$fd} .\n";
});

// 監聽服務器正常關閉事件
// 使用 kill -15 發送 SIGTREM 信號到主進程正常關閉時觸發
// kill -9 或 Ctrl+C 不會觸發該事件
$serv->on('Shutdown', function ($serv) {
    echo "服務器正常關閉.\n";
});

// 啟動服務器
$serv->start(); 

運行並測試 TCP 異步風格服務器

# 如果程序已經運行,先結束進程
kill -9 11590

# 在 cli 命令行環境運行服務端
php server.php

# 查看服務器監聽的端口
netstat -an | grep 9501

# 使用Telnet測試連接服務端
telnet 127.0.0.1 9501
# 發送數據
hello
# 接收數據
Server: hello

TCP 協程風格服務器

協程風格服務器處理連接的過程是完全同步的,程序可以順序處理 ConnectReceiveClose 事件,可以保證事件執行順序。

由於 TCP 協程風格服務器不支持設置工作進程數,服務器每次只能處理一個連接請求,如果請求中包含耗時邏輯,會嚴重影響並發性能,所以需要借助進程池實現多核CPU的利用。

# server.php

// 在進程池中創建兩個進程
$pool = new Swoole\Process\Pool(2);

$pool->set([
    'enable_coroutine' => true,  // 讓每個 OnWorkerStart 回調都自動創建一個協程
    'daemonize'     => 1,  // 作為守護進程運行,需同時設置log_file
    'log_file'      => '/www/logs/swoole.log',  // 指定標准輸出和錯誤日志寫入的文件
]);

// 進程綁定工作進程啟動事件
$pool->on('workerStart', function ($pool, $id) {
    // 每個進程都監聽9501端口,不使用ssl,開啟端口重用
    $server = new Swoole\Coroutine\Server('0.0.0.0', '9501', false, true);

    // 接收 kill -15 信號關閉服務器
    Swoole\Process::signal(SIGTERM, function () use ($server) {
        echo "服務器正常關閉.\n";
        $server->shutdown();
    });

    // 設置連接處理函數,接收到新的連接請求並自動創建一個協程並執行回調函數
    // 回調函數會在協程空間中執行
    $server->handle(function(Swoole\Coroutine\Server\Connection $conn) {
        echo "Client: Connect.\n";
        
        // 循環接收和返回數據
        while (true) {
            // 接收數據
            $data = $conn->recv();
            if (empty($data)) {
                // 關閉連接
                $conn->close();
                break;
            }

            //發送數據
            $conn->send("Server: " . $data);
            \Co::sleep(1);
        }
    });

    // 啟動服務器
    $server->start();
});
$pool->start();

由於TCP 協程風格服務器同一時間只能處理一個連接,為了充分利用CPU多核、處理多個連接,需要用到Swoole中的多進程模型+端口重用。

TCP 同步阻塞客戶端

// 同步阻塞客戶端可以用於 PHP-FPM 環境下
$client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);
if (!$client->connect('127.0.0.1', 9501, -1)) {
    exit("connect failed. Error: {$client->errCode}\n");
}

$client->send("hello world\n");

// 同步阻塞等待服務端返回內容
echo $client->recv();

$client->close();

TCP 協程客戶端

// 設置要 Hook 的函數的范圍
Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]);

// 協程客戶端,底層自動使用協程調度實現異步IO,用於代替異步客戶端
Co\run(function(){
  $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
  
  $client->set(array(
    'timeout' => 1.5,          //總超時,包括連接、發送、接收所有超時
    'connect_timeout' => 1.0,  //連接超時,會覆蓋第一個總的 timeout
    'write_timeout' => 2.0,    //發送超時,會覆蓋第一個總的 timeout
    'read_timeout' => 0.5,     //接收超時,會覆蓋第一個總的 timeout
  ));
  
  if (!$client->connect('127.0.0.1', 9501, 0.5)) {
      exit("connect failed. Error: {$client->errCode}\n");
  }
  
  $client->send("hello world.\n");
  
  echo $client->recv();
  
  $client->close();
});


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM