【Swoole系列4.1】Swoole协程系统

2022年8月15日 284点热度 0人点赞 0条评论

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'9501false);
    $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'9501false);
    $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'9501false);
    $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

81810【Swoole系列4.1】Swoole协程系统

这个人很懒,什么都没留下

文章评论