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