标题:【Swoole系列4.1】Swoole协程系统

文章目录
    分类:PHP 标签:Swoole

    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

    视频链接

    微信文章地址:https://mp.weixin.qq.com/s/6VhloFezWEs5bEdMVRGiLA

    B站视频地址:https://www.bilibili.com/video/BV1J8411x7yD

    微信视频地址:https://mp.weixin.qq.com/s/AdY2AgApq1pvnyR6jL0utw

    搜索
    关注