Swoole系列4.1Swoole协程系统
Posted 码农老张Zy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swoole系列4.1Swoole协程系统相关的知识,希望对你有一定的参考价值。
Swoole协程系统
总算到协程了,大家期待还是兴奋还是又期待又兴奋呢?不管怎么说,协程现在都是最流行的开发方式,没有之一。即使是 Java 它们提出的 纤程 ,其实在概念上也是大差不差的。协程的特点,我们在进阶篇 Swoole进程 相关的第一篇文章就已经说过了。相信大家都已经有了一个初步的概念。接下来,我们就看看在 Swoole 中如何应用协程服务。
异步服务的问题
对于异步来说,我们需要监听事件,并且监听的进程是并发的,所以会有一个问题,那就是无法保证前后顺序。
$serv = new Swoole\\Server("0.0.0.0", 9501);
//监听连接进入事件
$serv->on('Connect', function ($serv, $fd)
Swoole\\Coroutine\\System::sleep(5);//此处sleep模拟connect比较慢的情况,这种sleep()是不阻塞的
echo "onConnect", php_EOL;
);
//监听数据接收事件
$serv->on('Receive', function ($serv, $fd, $reactor_id, $data)
echo "onReceive", PHP_EOL;
);
//监听连接关闭事件
$serv->on('Close', function ($serv, $fd)
echo "Client: Close.\\n";
);
//启动服务器
$serv->start();
在这个例子中,我们通过在 Connect 事件中暂停5秒,来模拟 connect 可能出现连接比较慢的问题,然后再用 telnet 测试,就会发现 Receive 事件被先输出了出来。
[root@localhost source]# php 3.3Swoole协程系统.php
onReceive
onConnect
照理说,我们的 Connect 应该是先于 Receive 执行的,毕竟要建立起连接才有数据的接收,但是由于并行多进程的执行,这一步有可能是在并行一起完成的,而回调函数中的逻辑代码则会受到各种因素的影响出现阻塞,这时候,Receive 中的回调函数先于 Connect 回调函数执行也是非常有可能出现的严重问题。
对于协程来说,它不需要监听事件,代码也是顺序执行的。这一点我们在讲协程概念的时候就说过了,它就是个函数,也没有并行性质是并发的,工作在线程之上,同一个线程内也是顺序执行的,自然也就不会有这些问题。不记得 并行 和 并发 区别的小伙伴要回去补补课了哦 【Swoole系列3.1】进程、线程、协程,面试你被问了吗?https://mp.weixin.qq.com/s/GM_oGeVYOADcsKf43BN38A 。
协程 Http 服务
使用协程来提供 Http 服务非常简单,甚至比异步方式更简单,因为我们不需要去记住那么多的事件名称了。
Swoole\\Coroutine\\run (function ()
$server = new Swoole\\Coroutine\\Http\\Server('0.0.0.0', 9501, false);
$server->handle('/', function ($request, $response)
$response->end("<h1>Index</h1>");
);
$server->handle('/test', function ($request, $response)
$response->end("<h1>Test</h1>");
);
$server->handle('/stop', function ($request, $response) use ($server)
$response->end("<h1>Stop</h1>");
$server->shutdown();
);
$server->start();
);
我们需要先建立一个协程容器,也就是这个 Swoole\\Coroutine\\run() 方法,这是一种开启协程容器的方式,其它的方式我们后面聊到了再说。这个协程容器是什么意思呢?它就像是一个 C 或者 Java 中的 main() 函数,提供程序的入口。
在协程服务中,我们真的不需要去监听事件了,只需要在这个协程容器的回调函数中实例化一个 Swoole\\Coroutine\\Http\\Server 对象,然后通过它的 handle() 方法获得请求路径的内容,并交给回调函数进行处理即可。这里的回调函数中的参数与异步的 onRequest 监听中的回调参数是一样的,一个请求参数,一个响应参数。
协程 TCP 服务
对于 TCP 服务来说实现协程服务端也非常简单方便,和上面的 Http 服务类似,我们还是通过在协程容器中创建 TCP 服务对象并使用 handle() 方法操作连接数据。
Swoole\\Coroutine\\run (function ()
$server = new Swoole\\Coroutine\\Server('0.0.0.0', 9501, false);
$server->handle(function(Swoole\\Coroutine\\Server\\Connection $conn)
$data = $conn->recv();
echo $data, PHP_EOL;
$conn->send("协程 TCP :" . $data);
);
$server->start();
);
注意我们这里实例化的 Server 对象是不带 Http 命名空间的。同时,在它的 handle() 方法中,也不用路径参数了,直接就是一个回调函数。这个回调函数的参数,是一个 Swoole\\Coroutine\\Server\\Connection 对象,它返回的实际上就是建立好的 TCP 连接对象,在这个对象中,有 recv() 和 send() 方法,分别就是接收和发送数据的两个方法。
没有 UDP 的?确实没有,如果要实现 UDP 的协程服务端,需要单独使用 Socket 方式。
Swoole\\Coroutine\\run(function ()
$socket = new Swoole\\Coroutine\\Socket(AF_INET, SOCK_DGRAM, 0);
$socket->bind('0.0.0.0', 9501);
while (true)
$peer = null;
$data = $socket->recvfrom($peer);
echo "[Server] recvfrom[$peer['address']:$peer['port']] : $data\\n";
$socket->sendto($peer['address'], $peer['port'], "Swoole: $data");
);
这个 Socket 相关的内容,在后面我专门讲 Socket 对象的时候再说。
协程 WebSocket 服务
WebScoket 的代码比较长,但其实还是基于的是 Http 服务。
Swoole\\Coroutine\\run(function ()
$server = new Swoole\\Coroutine\\Http\\Server('0.0.0.0', 9501, false);
$server->handle('/websocket', function (Swoole\\Http\\Request $request, Swoole\\Http\\Response $ws)
$ws->upgrade();
while (true)
$frame = $ws->recv();
if ($frame === '')
$ws->close();
break;
else if ($frame === false)
echo 'errorCode: ' . swoole_last_error() . "\\n";
$ws->close();
break;
else
if ($frame->data == 'close' || get_class($frame) === Swoole\\WebSocket\\CloseFrame::class)
$ws->close();
break;
$ws->push("Hello $frame->data!");
$ws->push("How are you, $frame->data?");
);
$server->handle('/', function (Swoole\\Http\\Request $request, Swoole\\Http\\Response $response)
$response->end(<<<html
<h1>Swoole WebSocket Server</h1>
<script>
var wsServer = 'ws://192.168.56.133:9501/websocket';
var websocket = new WebSocket(wsServer);
websocket.onopen = function (evt)
console.log("Connected to WebSocket server.");
websocket.send('hello');
;
websocket.onclose = function (evt)
console.log("Disconnected");
;
websocket.onmessage = function (evt)
console.log('Retrieved data from server: ' + evt.data);
;
websocket.onerror = function (evt, e)
console.log('Error occured: ' + evt.data);
;
</script>
HTML
);
);
$server->start();
);
在这个示例代码中,我们其实主要操作的是 Response 对象,通过它的 upgrade() 方法向客户端发送 WebSocket 握手消息,然后循环监听消息的接收和发送。在循环体内部,通过 recv() 和 push() 方法接收和发送信息。
另外一个路径其实就是一个前端页面,为了方便测试。这个例子也是官网上的例子,当然,你也可以拿我们之前在基础阶段的静态测试页面来进行测试。
总结
通过今天的内容,我们简单地了解到了使用协程和异步方式搭起的服务器有什么不同。另外也顺便就搭起了 Http/TCP/WebSocket 的服务端程序,其中 UDP 是个特殊情况,官方并没有直接的 UDP 服务器,需要我们通过 Socket 的方式自己搭建一个。关于 Socket 的内容,我们在后面的学习中还会再讲到。
测试代码:
https://github.com/zhangyue0503/swoole/blob/main/4.Swoole%E5%8D%8F%E7%A8%8B/source/4.1Swoole%E5%8D%8F%E7%A8%8B%E6%9C%8D%E5%8A%A1.php
参考文档:
https://wiki.swoole.com/#/server/co_init
以上是关于Swoole系列4.1Swoole协程系统的主要内容,如果未能解决你的问题,请参考以下文章