【六星教育-swoole-1911 swoole进阶-06网络IO模型-阻塞模型】

前言

系统在处理一些事情的时候,有的选择进程处理,有的选择线程处理,至于怎么选择是根据平台和编程语言来决定的。接下来我们需要理解什么是网络IO模型?

stream_socket_server初体验

系统的QQ等客户端在和linux系统内核进行通信是通过socket实现的,那如何去创建socket呢?

php有2个函数可以创建socket套接字,分别是stream_socket_server()和socket_create(),这2个函数都是创建一个套接字,区别是stream_socket_server是有点点封装函数,socket_create是原生的。本次只体验stream_socket_server函数。

接下来我们将用原生的stream_socket_server这个函数去创建一个服务(相对于swoole创建服务,swoole就是封装好的),然后去连接和请求这个服务。

原生实现代码初体验:

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

//创建socket服务

$server = stream_socket_server($host);

echo $host."\n";

//建立与客户端的连接

//开启服务之后,服务就处于一个挂起的状态,等待连接进来然后创建连接

//stream_socket_accept是阻塞的

//长时间没有连接进来,就会报出一个连接超时警告异常,可以在前面加@来抑制异常

$client = @stream_socket_accept($server);

var_dump($client);

socket连接处理的阻塞状态

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

//创建socket服务

$server = stream_socket_server($host);

echo $host."\n";

//建立与客户端的连接

//开启服务之后,服务就处于一个挂起的状态,等待连接进来然后创建连接

//stream_socket_accept是阻塞的

//长时间没有连接进来,就会报出一个连接超时警告异常,可以在前面加@来抑制异常

//监听连接,当一个连接进来之后,就结束了,那通过一个死循环while来实现一直重复监听连接

while(true){

$client = @stream_socket_accept($server);

var_dump($client);

}

开启服务之后,当有一个客户端连接进来之后,服务端是收到连接的,连接之后又继续监听等待连接;而客户端也一直等待服务端的信息返回,若长时间未收到服务端的数据,就会断开重置。

curl: (56) Recv failure: Connection reset by peer

当多个连接进来时,后面的连接会把前面未收到服务端返回信息的连接挤掉

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

//创建socket服务

$server = stream_socket_server($host);

echo $host."\n";

//建立与客户端的连接

//开启服务之后,服务就处于一个挂起的状态,等待连接进来然后创建连接

//stream_socket_accept是阻塞的

//长时间没有连接进来,就会报出一个连接超时警告异常,可以在前面加@来抑制异常

//监听连接,当一个连接进来之后,就结束了,那通过一个死循环while来实现一直重复监听连接

while(true){

//建立与客户端的连接

$client = @stream_socket_accept($server);

//读取客户端的信息

$data = @fread($client,1024);

//发送信息给客户端

@fwrite($client,'abc');

//关闭连接

@fclose($client);

var_dump($client);

}

这里是服务端读取客户端的信息和发送信息给客户端

创建一个客户端:

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

//创建一个客户端的连接,与服务端accept进行信息交互

$client = stream_socket_client($host);

//给socket通道发送信息

fwrite($client,"hello world");

//接收读取socket通道发送来的信息

var_dump(fread($client,65535));

//关闭连接

fclose($client);

在tcp创建连接之后,假设需要做个耗时操作,sleep(3)一下,看看多个客户端同时连接进来会发生什么情况?

服务端:

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

//创建socket服务

$server = stream_socket_server($host);

echo $host."\n";

//建立与客户端的连接

//开启服务之后,服务就处于一个挂起的状态,等待连接进来然后创建连接

//stream_socket_accept是阻塞的

//长时间没有连接进来,就会报出一个连接超时警告异常,可以在前面加@来抑制异常

//监听连接,当一个连接进来之后,就结束了,那通过一个死循环while来实现一直重复监听连接

while(true){

//建立与客户端的连接

$client = @stream_socket_accept($server);

sleep(3);

//读取客户端的信息

$data = @fread($client,1024);

//发送信息给客户端

@fwrite($client,'abc');

//关闭连接

@fclose($client);

var_dump($data);

}

客户端:

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

//创建一个客户端的连接,与服务端accept进行信息交互

$client = stream_socket_client($host);

$time = time();

//给socket通道发送信息

fwrite($client,"hello world");

//接收读取socket通道发送来的信息

var_dump(fread($client,65535));

//关闭连接

fclose($client);

echo "\n".time()-$time;

这个时候发现第2个客户端必须等待第1个客户端完成之后才会连接,这是为什么呢?

这主要是 stream_socket_server是单线程处理连接的,必须等待前面的连接处理之后再处理后面的,这种状态叫做阻塞调用。那如何解决呢?

swoole 通过Reactor模式解决这个问题,php-fpm通过多进程模式解决这个问题,这些模式统称为网络IO模型

网络IO模型就好比 socket连接处理的框架或架构,比如:原生PHP=》laravel,thinkphp,yii。

5大IO模型

IO管理连接模型

内核:可以理解为 帮助我们去处理这份代码,如何去读,写,关闭,输出。

用户空间:可以理解为去告诉内核处理一些事情,代码就在用户空间里写。

5大io模型:

  • 阻塞I/O(blocking IO):用户空间一直等待内核处理完之前未处理完的事情,且在等待时间内不会去做其他的事情。效率是最低的。
  • 非阻塞I/O(noblocking IO):用户空间先检查内核是否有未处理完的事情,若有,就去做其他的事情,且每隔一段时间检查一次内核是否处理完,这种就是轮询的方式。
  • I/O多路复用    (IO multiplexing ):开辟多个内核,用户空间根据哪个内核空闲就将信息发送给该内核去处理。
  • 信号驱动I/O (signal driven IO):用户空间设置一个信息号就会去做其他的事情(对用户空间来说是非阻塞的),内核处理完未处理的事情之后会处理这个事情,处理完这个事情之后会返回该信号,表示处理完这个事情。触发回调函数。
  • 异步I/O (asynchronous IO):预定模式 ,用户空间一般用事件event=》事先今天发送一个信息给内核,并告诉内核在指定的时间节点去处理这件事情,(对于用户空间来说是非阻塞的),触发回调函数。通常事件发送完之后还需要事件清理(原因是内核处理完一件事情后,在下一个时间节点有可能在做其他的事情了)。

为什么要理解5大IO模型?对我们来说有什么意义?

通过理解网络编程的一些技术知识,为了更好的理解学习swoole。

简单IO架构(代码层面)

应用composer命名空间

io

src

SignalDriven

worker.php

test

signal-driver

server.php

client.php

先创建io目录,打开cmd,cd到io目录下,输入composer init,回车,创建属于自己的命名空间;

配置一下Package name ,Auth,Package Type,License

就会在io目录下生成一个composer.json文件

然后创建一下目录 ,src(存放自己原生写的封装的类),test目录(测试用的目录包)。然后再composer.json文件里配置一下命名空间,添加配置如下:

"autoload":{

"psr-4":{

"zjl\\io\\":"./src/"

}

},

测试一下:

在src目录下创建Index.php,编写测试代码如下:

然后composer update一下

这时候会生成vendor目录及文件

然后在io目录下创建一个test.php。(可以参照workerman的架构)

然后再cmd 上运行一下 php test.php ,这样就可以了。

阻塞IO模型

Worker.php

<?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)

{

$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

<?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();

测试

php server.php

curl http://127.0.0.1:9501

猜你喜欢

转载自www.cnblogs.com/fish-minuet/p/12108198.html
今日推荐