0%

Swoole

胜,不妄喜;败,不遑馁;胸有激雷而面如平湖者,可拜上将军。 —— 春秋战国·孙武 《孙子兵法》

一、基础

      Swoole就是披着PHP外衣的C程序,类似于Java等语言里面的高阶功能,如持久连接、异步通信、Websocket、多线程等。

      Swoole是由C语言开发的PHP扩展类,由于有着C语言的优势,Swoole在内存管理、数据结构、通信协议解析等方面优势明显。它是一个面向生产环境的PHP异步网络通信引擎,使PHP开发人员可以编写高性能的异步并发TCP、UDP、Unix Socket、HTTP、WebSocket等服务。Swoole可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。

二、实战

  1. 安装

    • 下载源码包至/usr/local/src目录下
    • 解压 sudo tar -zxvf swoole-4.4.3.tgz
    • 切换目录 cd swoole-4.4.3
    • 运行sudo /usr/local/php/7.3/bin/phpize命令,生成configure可执行文件
    • 运行配置sudo ./configure --with-php-config=/usr/local/php/7.3/bin/php-config
    • 安装扩展sudo make && sudo make install
      • 报错:configure: error: C compiler cannot create executables,解决sudo xcodebuild -license accept
    • 修改php.ini文件,增加extension=/path/swoole.so
    • 重启php-fpm
    • 查看是否安装成功php7.3 -m,显示swoole则成功
  2. 方法列表/your_path/swoole-4.4.3/ext-src/stubs/php_swoole_server.stub.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
namespace Swoole {
final class Server {
public function __construct(string $host = '0.0.0.0', int $port = 0, int $mode = SWOOLE_BASE, int $sock_type = SWOOLE_SOCK_TCP) {}
public function __destruct() {}
public function set(array $settings): bool {}
public function on(string $event_name, callable $callback): bool {}
public function getCallback(string $event_name): \Closure|string|null|array {}
public function listen(string $host, int $port, int $sock_type): false|Server\Port {}
public function sendMessage(mixed $message, int $dst_worker_id): bool {}
public function addProcess(\Swoole\Process $process): int {}
public function addCommand(string $name, int $accepted_process_types, callable $callback): bool {}
public function start(): bool {}
public function stop(int $workerId = -1, bool $waitEvent = false): bool {}
public function send(int|string $fd, string $send_data, int $serverSocket = -1): bool {}
public function sendfile(int $conn_fd, string $filename, int $offset = 0, int $length = 0): bool {}
public function stats(): array {}
public function bind(int $fd, int $uid): bool {}
public function sendto(string $ip, int $port, string $send_data, int $server_socket = -1): bool {}
public function sendwait(int $conn_fd, string $send_data): bool {}
public function exists(int $fd): bool {}
public function protect(int $fd, bool $is_protected = true): bool {}
public function close(int $fd, bool $reset = false): bool {}
public function pause(int $fd): bool {}
public function resume(int $fd): bool {}
public function task(mixed $data, int $taskWorkerIndex = -1, ?callable $finishCallback = null): int|false {}
public function taskwait(mixed $data, float $timeout = 0.5, int $taskWorkerIndex = -1): mixed {}
public function taskWaitMulti(array $tasks, float $timeout = 0.5): false|array {}
public function taskCo(array $tasks, float $timeout = 0.5): array|false {}
public function finish(mixed $data): bool {}
public function reload(bool $only_reload_taskworker = false): bool {}
public function shutdown(): bool {}
public function heartbeat(bool $ifCloseConnection = true): false|array {}
public function command(string $name, int $process_id, int $process_type, mixed $data, bool $json_decode = true): false|string {}
public function getClientList(int $start_fd = 0, int $find_count = 10): false|array {}
public function getClientInfo(int $fd, int $reactor_id = -1, bool $ignoreError = false): false|array {}
public function getWorkerId(): int|false {}
public function getWorkerPid(int $worker_id = -1): int|false {}
public function getWorkerStatus(int $worker_id = -1): int|false {}
public function getManagerPid(): int {}
public function getMasterPid(): int {}
#ifdef SWOOLE_SOCKETS_SUPPORT
public function getSocket(int $port = 0): false|\Socket {}
#endif
public function getLastError(): int {}
}
}

namespace Swoole\Connection {
final class Iterator {
public function __construct() {}
public function __destruct() {}
public function rewind(): void {}
public function next(): void {}
public function current(): mixed {}
public function key(): mixed {}
public function valid(): bool {}
public function count(): int {}
public function offsetExists(mixed $fd): bool {}
public function offsetGet(mixed $fd): mixed {}
public function offsetSet(mixed $fd, mixed $value): void {}
public function offsetUnset(mixed $fd): void {}
}
}

namespace Swoole\Server {
final class Task {
public function finish(mixed $data): bool {}
public static function pack(mixed $data): string|false {}
public static function unpack(string $data): mixed {}
}
}
  1. 实战一:简易聊天室(面向过程)

    • server.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <?php   
    use Swoole\WebSocket\Server;

    // old version, unsupported
    // $ws_server = new swoole_websocket_server('192.168.1.169', 9502);
    $ws_server = new Server("0.0.0.0", 9501, SWOOLE_BASE);

    $ws_server->set([
    'daemonize' => true
    ]);

    $ws_server->on('open', function ($ws, $request) {
    // file_put_contents( __DIR__ .'/log.txt' , $request->fd);
    $ws->push($request->fd, "From Server!\n");
    });

    $ws_server->on('message', function ($ws, $frame) {
    pushMessage($ws, $frame);
    });

    $ws_server->on('close', function ($ws, $fd) {
    echo "client-{$fd} is closed\n";
    });

    $ws_server->start();

    function pushMessage($ws, $frame){
    // $data = $frame->data;
    // $msg = file_get_contents( __DIR__ .'/log.txt');
    // for ($i=1; $i<= $msg ; $i++) {
    // $ws->push($fd, $frame->fd . ':' . $data);
    // }
    foreach ($ws->connections as $fd) {
    $ws->push($fd, $frame->fd . ':' . $frame->data);
    }
    }
    • client.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <html>
    <head>
    <title></title>
    <meta charset="UTF-8">
    <script type="text/javascript">
    if(window.WebSocket){
    var webSocket = new WebSocket("ws://localhost:9501");
    webSocket.onopen = function (event) {
    webSocket.send("From Client!");
    };
    webSocket.onmessage = function (event) {
    var content = document.getElementById('content');
    content.innerHTML = content.innerHTML.concat(
    '<p style="margin-left:20px;height:20px;line-height:20px;">uid:'+event.data+'</p>'
    );
    }
    var sendMessage = function(){
    var data = document.getElementById('message').value;
    webSocket.send(data);
    }
    }else{
    console.log("您的浏览器不支持WebSocket");
    }
    </script>
    </head>
    <body>
    <div style="width:600px;margin:0 auto;border:1px solid #ccc;">
    <div id="content" style="overflow-y:auto;height:300px;"></div>
    <hr/>
    <div style="height:40px">
    <input type="text" id="message" style="margin-left:10px;height:25px;width:450px;">
    <button onclick="sendMessage()" style="height:28px;width:75px;">发送</button>
    </div>
    </div>
    </body>
    </html>
  2. 实战二:简易聊天室(面向对象)

    • server.php
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <?php
    use Swoole\WebSocket\Server;

    class Chat
    {
    const HOST = '0.0.0.0';
    const PART = 9052;
    private $server = null;

    public function __construct() {
    // old version, unsupported
    // $this->server = new swoole_websocket_server(self::HOST, self::PART);
    $this->server = new Server("0.0.0.0", 9502, SWOOLE_BASE);

    // $ref = new ReflectionClass($this->server);
    // $data =$ref->getMethods();
    // file_put_contents("a.json", json_encode($data));
    // $data = $ref->getProperties();
    // file_put_contents("b.json", json_encode($data));

    $this->server->on('open', [$this, 'onOpen']);
    $this->server->on('message', [$this, 'onMessage']);
    $this->server->on('close', [$this, 'onClose']);
    $this->server->start();
    }

    public function onOpen($server, $request) {
    print_r($server->connection_info($request->fd));
    echo $request->fd . '连接了' . PHP_EOL;//打印到我们终端
    }

    public function onMessage($server, $frame) {
    echo $frame->fd . '来了,说:' . $frame->data . PHP_EOL;
    foreach ($server->connections as $fd) {
    $server->push($fd, json_encode(['no' => $frame->fd, 'msg' => $frame->data]));
    }
    echo "当前服务器共有".count($server->connections). "个连接\n";

    }

    public function onClose($server, $fd) {
    echo $fd . '走了' . PHP_EOL;//打印到我们终端
    }
    }

    $obj = new Chat();
    • client.html
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>聊天室</title>
    <script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
    </head>

    <body>

    <textarea class="log" style="width: 300px; height: 300px;">
    ===聊天室===
    </textarea><br/>
    <input type="button" value="连接" onClick="connect()">
    <input type="button" value="断开" onClick="disconnect()">
    <input type="input" id="text">
    <input type="button" value="发送" onClick="send()">

    <script>
    function connect() {
    var url = 'ws://localhost:9502';
    socket = new WebSocket(url);
    socket.onopen = function () {
    log1('连接成功')
    }
    socket.onmessage = function (msg) {
    log(msg.data);
    }
    socket.onclose = function () {
    log1('断开连接')
    }
    }

    function disconnect() {
    socket.close();
    socket = null;
    }

    function log1(var1) {
    $('.log').append(var1 + '\r\n');
    }
    function log(var1) {
    var v = $.parseJSON(var1)
    $('.log').append('uid:'+v['no']+',msg:'+v['msg']+ '\r\n');
    }

    function send() {
    // var json = $('#text').val();
    var json = JSON.stringify({
    'type': 'php',
    'msg': $('#text').val()
    })
    console.log(json)
    socket.send(json);
    }
    </script>
    </body>
    </html>
  3. 常见问题

三、参考

  1. 参考一
  2. 参考二
  3. 参考三

Swoole版本对比

一、版本总览表

版本 最低 PHP 核心定位 重大特性 破坏性变更 适用场景
Swoole 1.x PHP5.3~7.0 异步回调时代 基础 Server(TCP/HTTP/WebSocket)、异步回调、Task、Timer、Table 无协程,全回调编程 老项目、传统异步服务,现已淘汰
Swoole 2.x PHP7.0+ 协程诞生 引入 C 栈协程,go() 关键字,协程 TCP 客户端 协程仅客户端,Server 还是回调;API 不稳定 早期协程尝鲜,不推荐生产
Swoole 4.x PHP7.1~8.1 协程黄金版(生产最广泛) 1. 全协程 Server:Http/Http2/WebSocket/TCP 都支持协程
2. 一键协程化 Swoole\Runtime::enableCoroutine()
3. 协程 MySQL/Redis/PostgreSQL 客户端、协程文件、协程 DNS
4. ProcessPool、多 Reactor 线程、max_concurrency、协程取消 Co::cancel
5. Swoole\Table 增强
4.0 起不再支持 PHP5;4.7 Table 不再支持数组式访问 $table[$key] Hyperf、EasySwoole 等主流框架;绝大多数线上业务,存量主力
Swoole 5.x PHP8.1+ 精简重构版 1. 强类型声明,全 API 参数返回值类型
2. swoole-cli 独立二进制(无需系统 PHP)
3. 协程性能优化、内存优化
4. 支持 io_uring
重大移除:删除内置协程 MySQL/Redis/PostgreSQL 客户端,不再维护;废弃大量旧 API;移除旧异步回调客户端;不再支持 PHP7 新项目,愿意使用原生 PDO/Redis 扩展 + Runtime 协程化;需要独立 swoole-cli
Swoole 6.x PHP8.2~8.4 现代化新版本 1. 原生 Fiber 支持(PHP8.1+ zend fiber)
2. io_uring 成熟、多线程池重构
3. 完善协程调试、异常链路优化
4. 更好 PHP8 新特性兼容
继续清理遗留旧 API,逐步弱化 C 栈协程,主推 PHP Fiber 新项目、追求 PHP 原生特性、io_uring 高性能场景

二、分版本详细核心升级 & 区别

✅ Swoole 1.x(回调时代,古董)

  • 编程模型:事件回调,没有协程,写代码是 onReceive/onRequest 回调,遇到 IO 必须异步非阻塞。
  • 能力:TCP/UDP/HTTP/WebSocket 服务器、Task 异步任务、毫秒定时器、Swoole\Table 共享内存。
  • 痛点:回调地狱,代码难维护;阻塞 IO 直接卡死 Reactor;PHP 版本老旧。

一句话:没有协程的 Swoole,现在几乎没人用。

✅ Swoole 2.x:协程初试

  • 里程碑:首次引入协程(ucontext C 栈协程),go(function(){})。
  • 限制:Server 本身仍然是回调模式,协程只可以在回调内部创建,只能用协程 TCP 客户端;没有一键协程化。
  • 坑:协程生态不全,不稳定,很多阻塞函数无法挂起。

一句话:协程客户端可用,Server 不能全协程,属于过渡版本。

✅ Swoole 4.x(最重要,生产首选)

分水岭版本,PHP 协程生态成熟,Hyperf、EasySwoole、Mix 所有主流 Swoole 框架基于 4.4~4.8

  1. 全协程服务器:HttpServer 可以直接在 onRequest 里写同步风格代码,底层自动协程调度,告别回调地狱。
  2. Runtime 一键协程化 Swoole\Runtime::enableCoroutine():原生 file_get_contents、PDO、mysqli、stream 全部自动变成协程非阻塞,不用改业务代码。
  3. 内置全套协程客户端:Swoole\Coroutine\MySQL / Redis / PostgreSQL,高性能。
  4. 新组件:ProcessPool、连接池配套能力、Http2、WebSocket 协程、协程 DNS、协程文件读写。
  5. 4.7 关键变更:Swoole\Table 不能再用数组写法 $table['key'],只能用 get/set;新增 Co::cancel() 协程取消。
  6. 4.8:SSL 握手协程化,解决 TLS 握手阻塞 Reactor 问题。

4.x 内部小版本差异

  • 4.0:协程 HttpServer 正式稳定
  • 4.4:Runtime 一键协程化稳定,Hyperf 起点版本
  • 4.6:支持 PHP8
  • 4.7:Table 数组访问废弃、协程取消
  • 4.8:TLS 协程化、大量稳定性修复,4.8 是 4.x 系列最终稳定推荐版

一句话:4.x = 协程能力最强、生态最完善、文档/案例最多;缺点:内置协程客户端在 5.0 被砍掉。

✅ Swoole 5.x:破坏性精简重构

设计理念:不再维护自研数据库协程客户端,交给 PHP 原生扩展 + Runtime 协程化

  1. 移除:Swoole\Coroutine\MySQL、Redis、PostgreSQL 内置协程客户端(重大 break change!)

    业务改用原生 pdo/redis 扩展 + Runtime 自动协程化

  2. 强类型:所有 API 增加严格参数/返回值类型,PHP8 强类型体系对齐
  3. swoole-cli:独立静态二进制,打包 PHP+Swoole,不依赖宿主机 PHP 环境,部署很方便
  4. 内核优化:内存泄漏修复、协程栈优化、支持 io_uring
  5. 最低 PHP 8.1,彻底砍掉 PHP7 支持

升级大坑:4.x 业务如果大量直接使用 Coroutine\Redis/MySQL,迁移 5.x 必须改代码,重写连接池。

✅ Swoole 6.x:Fiber 时代

  1. 支持 PHP 原生 Zend Fiber 作为协程后端(替代老的 ucontext C 栈协程),可以自由切换协程实现。
  2. io_uring 成熟,提升高并发 IO 性能。
  3. 协程调试、异常栈、Xdebug 支持大幅改善。
  4. 持续清理遗留废弃 API,进一步简化内核。

三、重要分支说明:OpenSwoole(原 Swoole fork)

很多人会搞混:OpenSwoole 是社区 fork,版本号独立(22/24/26),不是 swoole-src 原版。

  • 原版 Swoole:swoole/swoole-src,版本 1/2/4/5/6,swoole PECL 包
  • OpenSwoole:openswoole/openswoole,版本 22+,pecl 包名叫 openswoole,命名空间 OpenSwoole\
  • 两者 API 大体相似,但内核、废弃策略、PHP 支持不一样,不要混用。

四、选型建议(生产环境)

  1. 老项目 / Hyperf、EasySwoole 存量:Swoole 4.8,生态成熟稳定,不轻易升 5
  2. 新项目,PHP8.1+,不想依赖 swoole 内置数据库客户端:Swoole 5.x
  3. 追求 PHP 原生 Fiber、io_uring、最新 PHP8.3/8.4:Swoole 6.x
  4. 新项目尽量不要用 OpenSwoole,除非明确需要它的特殊特性,生态资料少

五、核心差异一句话总结

  • 1.x:只有异步回调,无协程
  • 2.x:协程来了,但只有客户端协程,Server 还是回调
  • 4.x:协程黄金时代,全协程 Server + 内置全套协程数据库客户端 + Runtime 一键协程化,框架生态爆发
  • 5.x:砍掉内置数据库协程客户端,拥抱原生 PHP 扩展,强类型,swoole-cli
  • 6.x:支持 PHP 原生 Fiber,io_uring,逐步淘汰旧 C 栈协程

Windows11 + PHP + Go + Node开发环境

Windows11_PHP_Go_Node_开发环境推荐架构


PHP之Swoole和Go对比

Swoole与Go学习路径知识重叠对照

学习路径

  • 从 PHP → Swoole:你的业务代码、Composer 生态、Laravel 经验全部复用,两周能开始产出。
  • 从 PHP → Go:3–6 个月沉没成本,而且你得同时重学类型系统、错误处理(Go 没有异常,只有 error 返回值,这个思维转换对 PHP 程序员是最难的一关)、构建工具链。​你沉淀了多年的中控系统业务知识(代理账号、余额划拨、快手平台对接)全部无法迁移。
  • 从 Swoole → Go:一周。因为 Reactor、协程调度器、Channel、多进程模型这套东西是共享的,Swoole 6.x 的协程 API 和 Go 的 goroutine + channel 在心智模型上是同一套,只是语法糖不同。

Swoole有而Go给不了的东西:Swoole\Process多进程模型+Swoole\Table跨进程共享内存。

  • Go 的 goroutine 是单线程内的协程,要跨线程共享状态你得自己用 sync.Map 加锁,或者引入 etcd / Redis 做外部状态。
  • Swoole 的 Table 是零锁、跨进程、内核内存映射的,用起来就是像操作数组一样简单。

Swoole学习(Windows+WSL)

一、流程

  1. 安装wsl
  • wsl –install
  • wsl –set-default-version 2
  1. 安装Ubuntu24:wsl –install -d Ubuntu-24.04
  2. 进入Ubuntu24
  3. 同步代码
1
2
3
mkdir -p ~/dev
rsync -a --info=progress2 "/mnt/c/Users/Administrator/WorkBuddy/2026-10-15-17-58-08/swoole-lab/" ~/dev/swoole-lab/
cd ~/dev/swoole-lab && df -hT . | tail -1 # 确认显示 ext4
  1. 安装运行环境:bash setup-swoole.sh
  2. 验证版本
1
2
php -v | head -1        # 应显示 PHP 8.4.x
php --ri swoole | grep -iE 'version|curl hook|coroutine' | head -5
  1. 验证:php week1/01-sync-vs-co.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
【同步】20 个请求各等 1 秒
已完成 5/20
已完成 10/20
已完成 15/20
已完成 20/20
【同步】总耗时 20.06 秒

【协程】同样 20 个请求,但并发等待
协程 1 完成,单个耗时 1 秒
协程 2 完成,单个耗时 1 秒
协程 4 完成,单个耗时 1 秒
协程 8 完成,单个耗时 1 秒
协程 9 完成,单个耗时 1 秒
协程 5 完成,单个耗时 1 秒
协程 10 完成,单个耗时 1 秒
协程 11 完成,单个耗时 1 秒
协程 3 完成,单个耗时 1 秒
协程 6 完成,单个耗时 1 秒
协程 12 完成,单个耗时 1 秒
协程 7 完成,单个耗时 1.01 秒
协程 19 完成,单个耗时 1.01 秒
协程 20 完成,单个耗时 1.01 秒
协程 16 完成,单个耗时 1.01 秒
协程 13 完成,单个耗时 1.01 秒
协程 15 完成,单个耗时 1.01 秒
协程 18 完成,单个耗时 1.01 秒
协程 17 完成,单个耗时 1.01 秒
协程 14 完成,单个耗时 1.01 秒
【协程】总耗时 1.01 秒
单个请求平均耗时 1.01 秒 ← 没变快,只是重叠了

结论:协程提升的是「吞吐量」,不是「单次响应速度」。
20 个请求从 20 秒压到约 1 秒,靠的是等待期间不占 CPU。