前言
上节课主要讲到了基础的5大io模型,以及阻塞io模型的代码体验。通过curl http://127.0.0.1:9501可以访问请求,但是通过浏览器访问就访问不了了,这是咋回事呢?
暴力方式实现长连接
对于浏览器访问不了的情况是因为,浏览器是http协议,而服务是tcp协议,那如何让tcp协议和http协议直接进行通信呢?
对于浏览器来说,重要的一点主要是响应的头部。
当一个网站访问不到,会是因为哪些原因呢?
端口(检测端口,防火墙开放的端口),是否有异常(日志查看),协议是否一致。
对于上面浏览器不能访问的问题,我们可以在服务端发送给客户端数据时,添加响应头信息来解决。
代码片段:
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);
}
还有一个问题是,当前的这个阻塞io模型,不能支持长连接,如何实现呢?
在服务端,将主动关闭连接的注释掉,客户端以轮询的方式不断向服务端发送数据。这种粗暴的方式去简单实现长连接(不会在实际代码中使用)。当然我们也可以通过心跳检测机制去实现长连接。
非阻塞模型
问题演示
stream_set_blocking 这个函数主要是用来设置阻塞或非阻塞模型,非阻塞影响的是fwrite和fread,不影响stream_socket_accept。
代码演示:
worker.php 还是阻塞模型
namespace zjl\io\NoBlocking;
/**
* 用户连接服务
*/
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)
{
$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);
}
//处理完关闭连接
fclose($client);
}
}
public function send($conn,$data)
{
fwrite($conn,$data);
}
//启动服务
public function start()
{
$this->accept();
}
}
server.php 做一个延迟效果
require __DIR__."/../../vendor/autoload.php";
use zjl\io\NoBlocking\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";*/
sleep(4);
$server->send($client,"hello world client \n");
};
$server->start();
客户端 发送一条数据 看效果
//建立连接
$client = stream_socket_client("tcp://127.0.0.1:9501");
$new = time();
fwrite($client,"hello world");
var_dump(fread($client,65535));
echo "其他业务\n";
echo time() - $new."\n";
这个时候发现,客户端必须等待服务端执行完之后,才能继续下面的程序,这样阻塞的情况很不好,我们可以在客户端设置成 非阻塞的,看看会出现什么情况?
客户端代码:
//建立连接
$client = stream_socket_client("tcp://127.0.0.1:9501");
//设置成非阻塞模式
stream_set_blocking($client,0);
$new = time();
fwrite($client,"hello world");
var_dump(fread($client,65535));
echo "其他业务\n";
echo time() - $new."\n";
客户端不阻塞,但是没用数据。这个时候再客户端定时访问内核空间是否存在数据
//建立连接
$client = stream_socket_client("tcp://127.0.0.1:9501");
//设置成非阻塞模式
stream_set_blocking($client,0);
$new = time();
fwrite($client,"hello world");
echo "其他业务\n";
echo time() - $new."\n";
while(!feof($client))
{
var_dump(fread($client,65535));
sleep(1);
}
这就实现了非阻塞IO模型,只需要在客户端设置一下stream_set_blocking($client,0);
缺点:非阻塞模型 需要不断轮询内核,就会造成资源的消耗。
stream_select
通过轮询的方式帮助我们去查找socket是否为可用的状态。因为io多路复用模型是有多个socket连接,但不知道哪个连接是可用的状态还是忙的状态,这就需要stream_select来检测哪个socket连接是可用的。
由于stream_select函数里的参数$read,$write,$except都是使用统一变量地址引用&,所以检测出可用的socket连接时,都会保存在对应的变量里。由于stream_select函数的返回值不可靠,所以直接遍历变量参数,获取可用的socket连接。
演示:
客户端
$client = stream_socket_client("tcp://127.0.0.1:9501");
//设置成非阻塞模式
stream_set_blocking($client,0);
$new = time();
fwrite($client,"hello world");
echo "其他业务\n";
echo time() - $new."\n";
$read = $write = $except = null;
while(!feof($client))
{
$read[] = $client;
fread($client,65535);
//var_dump(fread($client,65535));
sleep(1);
echo "检测socket";
var_dump(stream_select($read,$write,$except,1));
}
stream_select 会根据数据去检测可用的socket
0表示可用,1表示忙碌
可用的socket会保存在$read数组里
通常会foreach遍历这个$read数组
多路复用IO模型
思路:
1.stream_select 可用检测 socket_client,socket_server,socket_accept
$scokets = [];记录保存很多个socket
$read = $sockets;
2.stream_select函数中的参数$read返回可用socket连接这样的一个数组
$read存的为可用的socket连接
多路复用io模型的实现,就会有多个socket,在进行数据读取时,若没有数据时,就会出现阻塞的情况,这个就需要stream_set_blocking()函数来设置成非阻塞的,来解决阻塞问题。
多路复用模型本质也是实现非阻塞模型。
代码实现:
Worker.php
namespace zjl\io\Multi;
/**
* 用户连接服务
*/
class Worker
{
/**
* 自定义服务的事件注册函数
* 闭包函数
* @var null
*/
public $onReceive = null;
public $onConnect = null;
public $onClose = null;
public $sockets = [];
//连接
public $socket = null;
public function __construct($socket_address)
{
$this->socket = stream_socket_server($socket_address);
//这里需要设置为非阻塞的状态
stream_set_blocking($this->socket,0);
//这里描述符(int)$this->socket是唯一的
//server也有忙的时候,所以也要放在池子里
$this->sockets[(int)$this->socket] = $this->socket;
echo $socket_address."\n";
}
/**
* 处理事情
*/
public function accept()
{
//接收连接,处理事情
while(true)
{
$this->debug('这是stream_select检测之前的$read');
$this->debug($this->sockets,true);
$read = $this->sockets;
//校验池子是否有可用的连接
//stream_select返回值不可靠,所以就直接遍历$read
stream_select($read,$w,$e,1);
$this->debug('这是stream_select检测之后的$read');
$this->debug($read,true);
sleep(1);
foreach ($read as $socket)
{
//$socket 可能为 主worker
//也可能是通过stream_socket_accept 创建的连接
if($socket === $this->socket)
{
//创建与客户端连接
$this->createSocket();
}else{
//发送信息
$this->sendMessage($socket);
}
}
}
}
//创建连接
public function createSocket()
{
$client = @stream_socket_accept($this->socket);
//判断是不是闭包
if(is_callable($this->onConnect)){
//执行闭包函数
($this->onConnect)($this,$client);
}
$this->sockets[(int)$client] = $client;
//return $client;
}
public function sendMessage($client)
{
$data = fread($client,65535);
if($data === '' || $data = false)
{
//关闭连接
fclose($client);
unset($this->sockets[(int)$client]);
}
if(is_callable($this->onReceive)){
//执行闭包函数
($this->onReceive)($this,$client,$data);
}
return $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();
}
}
Server.php
require __DIR__."/../../vendor/autoload.php";
use zjl\io\Multi\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();
多路复用IO模型的问题
yum -y install httpd-tools 安装压力测试工具 ab
ab :是一个轻量级的压力测试工具,模拟多少请求并发
安装方式:yum -y install httpd-tools
-n 请求量 , -c 客户数量 ,-k保持连接
ab -n 10000 -c 1000 -k http://127.0.0.1:9501/
ulimit -a
ulimit -n 202048 设置最大socket连接数
信号函数使用
信号 主要是指linux状态
手机 =》设置闹钟 =》闹钟把你叫醒的这个声音,这个声音就是信号
抗战神剧=》八路军 =》号角 ,号角就是一个信号
linux中,程序执行过程就会有一些信号
程序被kill的信号
php中处理信号的一个函数 :pcntl_signal ()安装一个信号处理器
SIGIO:IO处理信号
//安装一个信号
pcntl_signal(SIGIO,"sig_handle");
function sig_handle($sig)
{
sleep(2);
echo "这是测试信号的一个测试类\n";
}
//是一个安装信号的操作
//pid => 进程id ,设置信号
//根据进程设置信号
//posix_getpid()获取进程id
posix_kill(posix_getpid(),SIGIO);
//如有其他操作,请在分发前操作
echo "其他操作\n";
//分发信号
pcntl_signal_dispatch();
信号有很多,如 哨子:集合,命令:向左看向右看,下课铃声。pcntl_signal 就是安装这些信号,表示信号的作用。posix_kill 就是告诉哪些人 这些信号干嘛的 ,pcntl_signal_dispatch就是实战了。