众所周知,咱们通常使用 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 倍 。