用 PHP 編寫 http 服務器

  概述

衆所周知,咱們通常使用 PHP 開發Web程序時須要使用到好比Apache或Nginx等Web服務器來支持,那麼有沒有辦法直接使用PHP開發HTTP服務器,答案固然是能夠的,最近看了一遍Workerman框架的源碼,因而本身仿照寫了一個簡易的HTTP服務器,學習爲主。本文涉及到知識點包括:php

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

  如何編寫 HTTP 服務器

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

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。github

php  simple_http_server.php

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

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

這種模式最大的問題是,進程/線程建立和銷燬的開銷很大。因此上面的模式沒辦法應用於很是繁忙的服務器程序shell

  高性能的服務器

其實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 服務器,結構圖以下:yii2

具體的代碼能夠到   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 倍 。

相關文章
相關標籤/搜索