【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】

前言

上节课主要讲了非阻塞模型的实现,stream_set_blocking主要影响fwrite和fread,不会影响accept连接;然后说了stream_select函数,查询可读可写的套接字,默认可读;这个函数主要是实现多路复用模型的,以及多路复用模型的问题。

长连接和多路复用的疑问

暴力实现长连接:主要是通过服务端不主动关闭连接,客户端通过不断轮询的方式实现的。

下面是用阻塞IO模型暴力实现长连接的代码及演示结果:

Worker.php

namespace zjl\io\Blocking;

 

/**

* 用户连接服务

*/

class Worker

{

/**

* 自定义服务的事件注册函数

* 闭包函数

* @var null

*/

public $onReceive = null;

public $onConnect = null;

public $onClose = null;

//连接

public $socket = null;

 

public function __construct($socket_address)

{

$this->socket = stream_socket_server($socket_address);

echo $socket_address."\n";

}

 

/**

* 处理事情

*/

public function accept()

{

//接收连接,处理事情

while(true)

{

$this->debug("accept start");//这里只是打印信息,做断点

//监听的过程是阻塞的

$client = @stream_socket_accept($this->socket);

//判断是不是闭包

if(is_callable($this->onConnect)){

//执行闭包函数

($this->onConnect)($this,$client);

}

$data = fread($client,65535);

if(is_callable($this->onReceive)){

//执行闭包函数

($this->onReceive)($this,$client,$data);

}

$this->debug("accept end");

//处理完关闭连接

//fclose($client);

}

}

public function send($conn,$data)

{

$header = "HTTP/1.1 200 OK\r\n";

$header .= "Content-Type:text/html;charset=UTF-8\r\n";

$header .= "Connection:keep-alive\r\n";

$header .= "Content-length:".strlen($data)."\r\n\r\n";

$data =$header.$data;

fwrite($conn,$data);

}

public function debug($data,$flag = false)

{

if($flag){

var_dump($data);

} else {

echo "==== >>>> :".$data."\n";

}

}

//启动服务

public function start()

{

$this->accept();

}

}

server.php

require __DIR__."/../../vendor/autoload.php";

use zjl\io\Blocking\Worker;

 

$host = "tcp://0.0.0.0:9501";

$server = new Worker($host);

 

//连接服务

$server->onConnect = function ($server,$client){

echo "有一个客户端连接进来\n";

var_dump($client);

};

//接收和处理信息

$server->onReceive = function ($server,$client,$data){

echo "给客户端连接发送信息\n";

$server->send($client,"hello world client \n");

};

$server->start();

client.php

//建立连接

$client = stream_socket_client("tcp://127.0.0.1:9501");

$new = time();

while(true)

{

echo "===》准备发送信息\n";

fwrite($client,"hello world");

echo "===》信息发送成功\n";

echo "===》准备接收信息\n";

var_dump(fread($client,65535));

echo "===》信息接收成功\n";

//sleep(2);

}

//关闭连接

//fclose($client);

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第1张图片

疑问:实现的长连接,客户端是通过轮询的方式,为什么就只循环一次呢?

因为这是个阻塞IO模型。第一次循环,需要创建主worker连接,还没有与客户建立连接,是可用的,不阻塞;第二次循环,没有新的连接进来,上一次的连接在忙,不可用,就会一直阻塞在那里。

通常长连接都是通过多路复用IO模型实现的,因为多路复用模型是非阻塞的,且是多路复用的。这里服务端就采用多路复用的代码。

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第2张图片

但是多路复用是串行的,单线程执行程序,并不是异步的。

信号模型

代码初体验:

namespace zjl\io\Signal;

 

/**

* 用户连接服务

*/

class Worker

{

/**

* 自定义服务的事件注册函数

* 闭包函数

* @var null

*/

public $onReceive = null;

public $onConnect = null;

public $onClose = null;

//连接

public $socket = null;

 

public function __construct($socket_address)

{

$this->socket = stream_socket_server($socket_address);

echo $socket_address."\n";

}

 

/**

* 处理事情

*/

public function accept()

{

//接收连接,处理事情

while(true)

{

$this->debug("accept start");

//监听的过程是阻塞的

$client = @stream_socket_accept($this->socket);

//当这个链接建立好了之后,需要对这个链接设置个信号,

pcntl_signal(SIGIO,$this->sigHandle($client));

posix_kill(posix_getpid(),SIGIO);

 

pcntl_signal_dispatch();

$this->debug("accept end");

//处理完关闭连接

//fclose($client);

}

}

public function sigHandle($client)

{

return function($sig) use ($client){

//判断是不是闭包

if(is_callable($this->onConnect)){

//执行闭包函数

($this->onConnect)($this,$client);

}

$data = fread($client,65535);

if(is_callable($this->onReceive)){

//执行闭包函数

($this->onReceive)($this,$client,$data);

}

};

}

public function send($conn,$data)

{

$header = "HTTP/1.1 200 OK\r\n";

$header .= "Content-Type:text/html;charset=UTF-8\r\n";

$header .= "Connection:keep-alive\r\n";

$header .= "Content-length:".strlen($data)."\r\n\r\n";

$data =$header.$data;

fwrite($conn,$data);

}

public function debug($data,$flag = false)

{

if($flag){

var_dump($data);

} else {

echo "==== >>>> :".$data."\n";

}

}

//启动服务

public function start()

{

$this->accept();

}

}

这种模型不会大量的使用,因为缺点。

缺点

信号I/O在大量IO操作时可能会因为信号队列溢出导致没法通知信号驱动I/O尽管对于处理UDP套接字来说有用,即这种信号通知意味着到达一个数据报,或者返回一个异步错误。但是,对于TCP而言,信号驱动的I/O方 式近乎无用,因为导致这种通知的条件为数众多,每一个来进行判别会消耗很大资源,与前几种方式相比优势尽失

event安装

异步IO模型:用户空间设置了一个事件,内核空间只要到了事件点就会触发。类似于 web前端 jquery 点击鼠标移动事件。这个事件可以有 连接 、发送、写的 事件。

那php 究竟如何操作 linux事件呢?

可以通过event类来操作。

php7 =》event 类

php5 =》 libevent 类

event 类:用于监听socket 写 、读、超时、信号发生。

事件标志类型:

Event::READ 该标志指示一个事件,当提供的文件描述符(通常是流资源或套接字)可以读取时,该事件变为活动状态。

Event::WRITE 标志表示当提供的文件描述符(通常是流资源或套接字)准备好写入时变为活动的事件。

Event::SIGNAL 用于实现信号检测。请参阅下面的“构造信号事件”。

Event::TIMEOUT 表示事件在超时后变为活动状态。Event::TIMEOUT 构造事件时,将忽略该 标志:添加事件时可以设置超时,也可以不设置超时 。 发生超时时,在回调函数的$ what参数中设置它 。

在php手册里 下载安装包event-2.3.0.tgz,解压,编译安装,然后在php.ini添加扩展。然后查看是否安装成功。

event理解

use \Event as Event;

use \EventBase as EventBase;

 

$eventBase = new EventBase();

$event = new Event($eventBase,-1,Event::PERSIST | Event::TIMEOUT,function (){

echo "hello world event \n";

});

$event1 = new Event($eventBase,-1,Event::PERSIST | Event::TIMEOUT,function (){

echo "hello world event --02 \n";

});

$event1->add(0.2);//里面是参数不传也是可以的

$event->add(0.1);

$eventBase->loop();//调用设置在eventBase中的事件 相当于 swoole中start启动事件

 

//event是一个事件 =》add 添加一个事件

//eventBase 是事件库 =》存储所创建的事件

//通过add 将创建的事件 存储在eventBase库中,然后通过eventBase库 去执行loop事件

//Event::PERSIST 表示事件循环执行 =》只针对回调函数使用,不针对event对象

//Event::TIMEOUT 表示间隔多久执行 =》只针对回调函数使用,不针对event对象

// | 表示多个累加

//$fd

// -1:计时器

// 信号:信号的标识 SIGIO ,SIGHUP

// socket:传递socket资源

 

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第3张图片

那event如何与socket结合使用呢?

简单体验演示:

服务端:

use \Event as Event;

use \EventBase as EventBase;

 

$socket_address = "tcp://0.0.0.0:9501";

$server = stream_socket_server($socket_address);

 

$eventBase = new EventBase();

 

$event = new Event($eventBase,$server,Event::PERSIST | Event::READ | Event::WRITE,function ($socket){

//这里注意 回调函数里的参数,Event内部会处理,自动将$server 传进回调函数中,相当于$server是实参,$socket是形参

//在闭包函数中function ($socket)中的$socket就是在构造函数中传递的$server这个属性

echo "连接开启\n";

$client = stream_socket_accept($socket);

var_dump(fread($client,65535));

fwrite($client,"hello world\n");

fclose($client);

echo "连接关闭\n";

});

 

$event->add();

$eventBase->loop();//调用设置在eventBase中的事件 相当于 swoole中start启动事件

客户端

curl http://127.0.0.1:9501

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第4张图片

 

但是由于异步回调函数中的程序是同步的,那么是不是可以将回调函数中的读和写也放在异步回调函数中呢?下面演示一下:

 

use \Event as Event;

use \EventBase as EventBase;

 

$socket_address = "tcp://0.0.0.0:9501";

$server = stream_socket_server($socket_address);

 

$eventBase = new EventBase();

 

$event = new Event($eventBase,$server,Event::PERSIST | Event::READ | Event::WRITE,function ($socket) use ($eventBase){

//这里注意 回调函数里的参数,Event内部会处理,自动将$server 传进回调函数中,相当于$server是实参,$socket是形参

//在闭包函数中function ($socket)中的$socket就是在构造函数中传递的$server这个属性

echo "连接开启\n";

$client = stream_socket_accept($socket);

//这里嵌套了一个事件event

$event1 = new Event($eventBase,$client,Event::PERSIST | Event::READ | Event::WRITE,function ($socket){

var_dump(fread($socket,65535));

fwrite($socket,"hello world\n");

fclose($socket);

});

$event1->add();

echo "连接关闭\n";

});

$event->add();

$eventBase->loop();//调用设置在eventBase中的事件 相当于 swoole中start启动事件

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第5张图片

发现运行失败,这是为啥呢?

eventSocket.php

require "e.php";

use \Event as Event;

use \EventBase as EventBase;

 

$socket_address = "tcp://0.0.0.0:9501";

$server = stream_socket_server($socket_address);

 

$eventBase = new EventBase();

 

//记录创建的事件,让eventBase可以找得到这个事件

$count = [];//变量没有要求 随便定义

 

$event = new Event($eventBase,$server,Event::PERSIST | Event::READ | Event::WRITE,function ($socket) use ($eventBase,&$count){

//这里注意 回调函数里的参数,Event内部会处理,自动将$server 传进回调函数中,相当于$server是实参,$socket是形参

//在闭包函数中function ($socket)中的$socket就是在构造函数中传递的$server这个属性

echo "连接开启\n";

$client = stream_socket_accept($socket);

(new e($eventBase,$client,$count))->handler();

echo "连接关闭\n";

});

$event->add();

$count[(int)$server][Event::PERSIST | Event::READ | Event::WRITE] = $event;

$eventBase->loop();//调用设置在eventBase中的事件 相当于 swoole中start启动事件

 

e.php

use \Event as Event;

class e

{

protected $socket;

protected $eventBase;

protected $count;

function __construct($eventBase,$client,&$count)

{

$this->socket = $client;

$this->eventBase = $eventBase;

$this->count = $count;

}

public function handler()

{

$event = new Event($this->eventBase,$this->socket,Event::PERSIST | Event::READ | Event::WRITE,function ($socket){

var_dump(fread($socket,65535));

fwrite($socket,"hello world\n");

fclose($socket);

});

$event->add();

$this->count[(int)$this->socket][Event::PERSIST | Event::READ | Event::WRITE] = $event;

}

}

为啥在同一个文件里不能这么做呢?

Event在同一个文件中不能嵌套定义,而转为引用方式的嵌套

event:因为在同一个作用域中,如果嵌套可能会存在覆盖的问题,因此不支持。

 

但是多个客户端连接时,后面的连接连上后,前面的连接就会不能用了。

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第6张图片

这个时候还需要把event给 free()一下

use \Event as Event;

class e

{

protected $socket;

protected $eventBase;

protected $count;

function __construct($eventBase,$client,&$count)

{

$this->socket = $client;

$this->eventBase = $eventBase;

$this->count = $count;

}

public function handler()

{

$event = new Event($this->eventBase,$this->socket,Event::PERSIST | Event::READ | Event::WRITE,function ($socket){

var_dump(fread($socket,65535));

fwrite($socket,"hello world\n");

fclose($socket);

($this->count[(int)$this->socket][Event::PERSIST | Event::READ | Event::WRITE])->free();

});

$event->add();

$this->count[(int)$this->socket][Event::PERSIST | Event::READ | Event::WRITE] = $event;

}

}

【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】_第7张图片

你可能感兴趣的:(【六星教育-swoole-1911 swoole进阶-08信号驱动模型与event事件】)