用 PHP 编写 http 服务器
2018-06-29 16:25 掸尘 阅读(6178) 评论(0) 编辑 收藏 举报概述
众所周知,我们一般使用 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 倍 。