Swoole 支持 TCP、UDP、WebSocket、HTTP 等服务器。创建服务 $server 后,可通过 $server->on() 绑定事件。以 TCP 服务器为例,在 Arch Linux 环境下,运行以下程序 test.php

#!/usr/bin/php
<?php
Swoole\Runtime::enableCoroutine(true);

echo posix_getpid() . PHP_EOL;

$server = new swoole_server('127.0.0.1', 8080);
$server->on('connect', function ($server, $fd) {
    echo posix_getpid() . PHP_EOL;
    echo "open: $fd" . PHP_EOL;
});
$server->on('receive', function ($server, $fd, $reactor_id, $data) {
    echo posix_getpid() . PHP_EOL;
    if (!empty($data)) {
        sleep(5); // 模拟处理任务
        $server->send($fd, "data: $data");
    } else {
        $server->close($fd);
    }
});
$server->on('close', function ($server, $fd) {
    echo "close: {$fd}" . PHP_EOL;
});
$server->start();

查看 htop,搜索相关进程和线程,内容如下(绿色部分为线程)。

swoole

执行

$ nc 127.0.0.1:8080

同一 $fd 输出的 pid 相同,先后发送两次数据,等待一次的响应时间后即能先后得到返回数据。

由于 Swoole 新版本增加了对 sleep() 的 hook,因此此处相当于调用 Coroutine::sleep(),通过协程调度器实现,对当前协程 yield 让出时间片,在定时结束后恢复运行。

若去掉首行的 Swoole\Runtime::enableCoroutine(true),原生的 sleep() 会使用系统调用阻塞整个进程,先后发送两个数据后,需要等待两次响应时间。阻塞针对整个进程,而非针对单个的协程。


Swoole 现在使用 godeferchan 等 Go 语言中关键字的形式提供类似的协程模式。使用 go() 启动一个协程,协程间通过生产者消费者队列 chan() 通信,在 function 退出前根据 LIFO 的顺序执行 defer() 任务。 示例如下。

#!/usr/bin/php
<?php
$c = new chan(10);
for ($i = 0; $i < 10; $i++) {
    @go (function () use ($i, $c) {
        @defer (function () use ($c) {
            $rand = rand(0, 100);
            $c->push($rand);
            echo " Push: $rand" . PHP_EOL;
        });
        echo '$i';
    });
}
echo PHP_EOL;
for ($i = 0; $i < 10; $i++) {
    echo 'Pop: ' . $c->pop() . PHP_EOL;
}

输出的结果为

0 Push: 60
1 Push: 1
2 Push: 83
3 Push: 97
4 Push: 81
5 Push: 70
6 Push: 91
7 Push: 6
8 Push: 8
9 Push: 32

Pop: 60
Pop: 1
Pop: 83
Pop: 97
Pop: 81
Pop: 70
Pop: 91
Pop: 6
Pop: 8
Pop: 32