IO复用
- 概念介绍
- 阻塞与非阻塞
- select
比较尴尬的是,socket_select只支持socket类型的资源,而不支持stream类型的资源,所以这里需要使用socket_create创建socket资源.
<?php
defined("PORT") or define("PORT", isset($argv[1]) ? $argv[1] : 8090);
/*=======================select model=======================*/
$stream = stream_socket_server("tcp://0.0.0.0:" . PORT, $errorNo, $errorMsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, stream_context_create([]));
stream_set_blocking($stream, 0);
$socket = socket_import_stream($stream);
@socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
@socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($stream, 0);
}
$read = $clients = $write = $except = array();
for ($i = 0; $i < 4; $i++) {
$pid = pcntl_fork();
if ($pid < 0) {
exit(0);
} elseif ($pid == 0) {
while (true) {
$read = array_merge($clients, array($stream));
$num = stream_select($read, $write, $except, 0, 100000000);
if ($num <= 0) {
continue;
}
if (in_array($stream, $read)) {
$client = stream_socket_accept($stream);
stream_set_blocking($client, 0);
$clients[] = $client; //此处必须把$client 另存到$clients数组中,不能直接放到$read 中,否则其他客户端连上,之前连上的客户端会全部断掉
unset($read[array_search($stream, $read)]);
}
foreach ($clients as $key => $value) {
$string = '';
$data = @fread($value, 65535);
if ($data === '' || $data === false) {
if ((feof($value) || !is_resource($value) || $data === false)) {
fclose($value);
}
}
$string .= $data;
if ($string) {
echo $string . "\n";
fwrite($value, "hello\n");
}
}
}
}
}
exit(0);
select虽然可以监听多个连接,但是它最多只能监听1024个连接。这虽然在poll中得到了改进,但是select和poll本质上都是通过轮询的方式进行监听,这意味着当监听了上万连接时,就算只有一个连接是活动的,依然要把上万连接都遍历一次。显然,这无疑是极大的性能浪费,而epoll的出现彻底地解决了这个问题
epoll
epoll并不是只有一个函数来实现,而是多个函数。我们这里并不讨论epoll相关的函数,因为PHP并不提供相关的函数,但它提供了基于libevent库的libevent扩展,以及基于libevent库的event扩展。libevent库实现了Reactor模型,关于Reactor模型,这里只作简单的介绍
Reactor模型,包含了几个组件:句柄,事件分发器,事件处理器。
句柄,就是文件描述符,在Socket编程中,就是使用socket_create创建的socket资源.
事件分发器, 通过事件循环,事件循环是通过诸如epoll``Select``Poll等IO复用技术实现的,监听句柄期待的事件是否发生,发生了则将事件分发给事件处理器.
事件处理器,当事件发生时,处理相关的逻辑.
而libevent库已经实现了Reactor模型,我们可以开箱即用。下面,我们将通过libevent对我们的WEB服务器再次改造,使它的处理并发的能力再次提高
在此之前,我们需要安装event扩展,安装event扩展必须安装libevent库
sudo apt-get update
sudo apt-get install php5-dev
cd
wget https://github.com/downloads/libevent/libevent/libevent-2.0.20-stable.tar.gz
注意:若遇到链接无法访问的情况,请尝试使用这个地址:http://labfile.oss-cn.aliyuncs.com/courses/987/libevent-2.0.20-stable.tar.gz
解压并编译
tar -xvf libevent-2.0.20-stable.tar.gz
cd libevent-2.0.20-stable
./configure
make
sudo make install
再通过pecl方式安装event,安装过程中,在询问要不要支持openssl时,输入no,其它都是默认直接回车
sudo pecl install event
安装完之后, sudo vim /etc/php5/cli/conf.d/20-event.ini
添加扩展extension=event.so,保存退出,运行以下代码进行验证是否安装成功
php -m | grep event
面向对象写法:
defined("PORT") or define("PORT", isset($argv[1]) ? $argv[1] : 8090);
/*=======================epoll model=======================*/
$stream = stream_socket_server("tcp://0.0.0.0:" . PORT, $errorNo, $errorMsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, stream_context_create([]));
stream_set_blocking($stream, 0);
$socket = socket_import_stream($stream);
@socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
@socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($stream, 0);
}
class myEvent
{
protected $eventBase;
protected $allEvents = [];
public function __construct()
{
if (!extension_loaded('event')) {
echo 'event extension is require' . PHP_EOL;
exit(250);
}
$this->eventBase = new \EventBase();
}
public function add($fd, $flag, $func)
{
$fd_key = (int) $fd;
$event = new \Event($this->eventBase, $fd, $flag | \Event::PERSIST, $func, $fd);
if (!$event || !$event->add()) {
return false;
}
$this->allEvents[$fd_key][$flag] = $event; // 此处有坑,必须把$event 存到不会销毁的变量中,否则事件轮询起不来
return true;
}
public function loop()
{
$this->eventBase->loop();
}
}
class myRead
{
public $event = null;
public function __construct($event)
{
$this->event = $event;
}
public function read($socket)
{
$string = '';
$data = @fread($socket, 65535);
if ($data === '' || $data === false) {
if ((feof($socket) || !is_resource($socket) || $data === false)) {
fclose($socket);
return ;
}
}
$string .= $data;
if ($string) {
echo $string . "\n";
fwrite($socket, "hello\n");
}
}
public function accept($socket)
{
$new_socket = @stream_socket_accept($socket, 0);
stream_set_blocking($new_socket, 0);
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($new_socket, 0);
}
$this->event->add($new_socket, \Event::READ, [$this, 'read'], $new_socket);
}
}
for ($i = 0; $i < 4; $i++) {
$pid = pcntl_fork();
if ($pid < 0) {
exit(0);
} elseif ($pid == 0) {
$event = new MyEvent();
$myRead = new myRead($event);
$event->add($stream, \Event::READ, [$myRead, 'accept'], $stream);
$event->loop();
}
}
exit(0);
面向过程写法
defined("PORT") or define("PORT", isset($argv[1]) ? $argv[1] : 8090);
/*=======================epoll model=======================*/
$stream = stream_socket_server("tcp://0.0.0.0:" . PORT, $errorNo, $errorMsg, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, stream_context_create([]));
stream_set_blocking($stream, 0);
$socket = socket_import_stream($stream);
@socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
@socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($stream, 0);
}
$events = [];
function accept($fd, $flag, $eventBase)
{
global $events;
$new_socket = @stream_socket_accept($fd, 0);
stream_set_blocking($new_socket, 0);
if (function_exists('stream_set_read_buffer')) {
stream_set_read_buffer($new_socket, 0);
}
$event = new Event($eventBase, $new_socket, Event::READ | Event::PERSIST, 'read', $new_socket);
$event->add();
$events[] = $event;
}
function read($fd)
{
$string = '';
$data = @fread($fd, 65535);
if ($data === '' || $data === false) {
if ((feof($fd) || !is_resource($fd) || $data === false)) {
fclose($fd);
return;
}
}
$string .= $data;
if ($string) {
echo $string . "\n";
fwrite($fd, "hello\n");
}
}
$eventBase = new EventBase();
$event = new Event($eventBase, $stream, Event::READ | Event::PERSIST, 'accept', $eventBase);
//把事件注册到事件循环器中
$event->add();
//开始事件循环
$eventBase->loop();
流程和创建Reactor模型一致
- 创建句柄
- 创建事件循环器
- 创建事件,并指定事件监听的事件类型及注册事件处理器
- 向循环器中添加事件
- 这里我们主要看Event类,看看它的构造函数原型:
public Event::__construct ( EventBase $base , mixed $fd , int $what , callable $cb [, mixed $arg = NULL ] )
- base: EventBase类的实例
- fd: 要监听的句柄
- what: 要监听的事件类型
- cb: 事件处理器,在PHP中就是回调函数
- arg: 事件处理器的参数列表