用 PHP 編寫 http 服務器


  概述

眾所周知,我們一般使用 PHP 開發Web程序時需要使用到比如Apache或Nginx等Web服務器來支持,那么有沒有辦法直接使用PHP開發HTTP服務器,答案當然是可以的,最近看了一遍Workerman框架的源碼,於是自己仿照寫了一個簡易的HTTP服務器,學習為主。本文涉及到知識點包括:

  • PHP Socket編程
  • 網絡 IO 模型
  • PHP libevent
  • PHP 多進程
  • PHP 擴展信號

  如何編寫 HTTP 服務器

下面是一個簡易版HTTP服務器,HTTP 是應用層,其實底層用的是 TCP,在TCP 的基礎上包了一層 HTTP的協議。代碼如下:

require_once 'Http.php';
$socket = stream_socket_server("0.0.0.0:2345", $errno, $errstr);

if (!$socket) {
    echo "$errstr ($errno)<br />\n";
} else {
    while (true) {
        $conn = @stream_socket_accept($socket);
        if ($conn)
        {
            $data = Http::encode('Hi world');
            fwrite($conn, $data);
            fclose($conn);
        } else {
            echo "no newSocket\n";
        }
    }
}

幾行代碼就可以實現一個簡單的 web 服務器,在 shell 下面執行下面命令,在瀏覽器輸入:http://127.0.0.1:2345/ 即可看到 Hi world。

php  simple_http_server.php

上面那那種構架,阻塞模式,要等前一個處理完了,才能處理下一個。所以流量稍微大一點,就會處理不過來。那我們可以改進一下,變成多進程模式。

require_once 'Http.php';
$socket = stream_socket_server("0.0.0.0:2345", $errno, $errstr);

if (!$socket) {
    echo "$errstr ($errno)<br />\n";
} else {
    while (true) {
       
       if (pcntl_fork() == 0)
       {
            $conn = @stream_socket_accept($socket);
            
            if ($conn)
            {
                $data = Http::encode('Hi world');
                fwrite($conn, $data);
                fclose($conn);
            } else {
                echo "no newSocket\n";
            }
       }
    }
}

這種模式最大的問題是,進程/線程創建和銷毀的開銷很大。所以上面的模式沒辦法應用於非常繁忙的服務器程序

  高性能的服務器

其實IO復用的歷史和多進程一樣長,Linux很早就提供了 select 系統調用,可以在一個進程內維持1024個連接。后來又加入了poll系統調用,poll做了一些改進,解決了 1024 限制的問題,可以維持任意數量的連接。但select/poll還有一個問題就是,它需要循環檢測連接是否有事件。這樣問題就來了,如果服務器有100萬個連接,在某一時間只有一個連接向服務器發送了數據,select/poll需要做循環100萬次,其中只有1次是命中的,剩下的99萬9999次都是無效的,白白浪費了CPU資源。

直到Linux 2.6內核提供了新的epoll系統調用,可以維持無限數量的連接,而且無需輪詢,這才真正解決了 C10K 問題。現在各種高並發異步IO的服務器程序都是基於epoll實現的,比如Nginx、Node.js、Erlang、Golang。像 Node.js 這樣單進程單線程的程序,都可以維持超過1百萬TCP連接,全部歸功於epoll技術。

libevent是一個輕量級的基於事件驅動的高性能的開源網絡庫,並且支持多個平台,依據系統提供的select,poll和epoll方法來進行I/O復用,但是針對於多個系統平台上的不同的I/O復用實現方式,libevent進行了重新的封裝,並提供了統一的API接口。libevent在實現上使用了事件驅動這種機制。

我們通過 多進程 + libevent 來構架 web 服務器,結構圖如下:

具體的代碼可以到   demo, 執行 php demo.php start 即可。

  壓力測試

 硬件是自己 Mac pro,依據 1000 並發重復 100次進行測試:

 先測試一個 Nginx + fpm ,siege -c 1000 -r 100 http://yii2.localhost/  結果如下:

再測試自己寫的服務器 siege -c 1000 -r 100 http://127.0.0.0:2345

自己寫的服務器成功率幾乎是 Nginx + fpm 的 2 倍 。


免責聲明!

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



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