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协程系统的主要内容,如果未能解决你的问题,请参考以下文章

Swoole系列4.7协程服务客户端

Swoole系列3.2Swoole 异步进程服务系统

Swoole系列4.8一键协程化

Swoole系列4.8一键协程化

Swoole系列4.5协程并发调度

Swoole系列3.1进程线程协程,面试你被问了吗?