Swoole

Swoole:PHP 协程框架

Swoole 使 PHP 开发人员可以编写高性能高并发的 TCP、UDP、Unix Socket、HTTP、 WebSocket 等服务,让 PHP 不再局限于 Web 领域。Swoole4 协程的成熟将 PHP 带入了前所未有的时期, 为性能的提升提供了独一无二的可能性。Swoole 可以广泛应用于互联网、移动通信、云计算、 网络游戏、物联网(IOT)、车联网、智能家居等领域。使用 PHP + Swoole 可以使企业 IT 研发团队的效率大大提升,更加专注于开发创新产品。

官网地址:https://www.swoole.com/

Swoole 特性

Swoole 使用 C/C++ 语言编写,提供了 PHP 语言的异步多线程服务器、异步 TCP/UDP 网络客户端、异步 MySQL、异步 Redis、数据库连接池、AsyncTask、消息队列、毫秒定时器、异步文件读写、异步 DNS 查询。 Swoole 内置了 Http/WebSocket 服务器端/客户端、Http2.0 服务器端。

除了异步 IO 的支持之外,Swoole 为 PHP 多进程的模式设计了多个并发数据结构和 IPC 通信机制,可以大大 简化多进程并发编程的工作。其中包括了原子计数器、Table、Channel、Lock、进程间通信 IPC 等丰富的功能特性。

Swoole4.0 支持了类似 Go 语言的协程,可以使用完全同步的代码实现异步程序。PHP 代码无需额外增加任何 关键词,底层自动进行协程调度,实现异步 IO。

环境依赖

环境依赖

  • 仅支持 Linux、FreeBSD、MacOS 三种操作系统
  • 在Windows平台,可使用CygWin或WSL(Windows Subsystem for Linux)
  • Linux 内核版本 2.3.32 以上
  • gcc-4.8或更高版本,依赖C++11
  • 编译为 libswoole.so 作为 C/C++ 库时需要使用 cmake-2.4 或更高版本

PHP版本依赖

  • Swoole-1.x需要 PHP-5.3.10 或更高版本
  • Swoole-4.x需要 PHP-7.0.0 或更高版本
  • 不依赖 PHP 的 stream、sockets、pcntl、posix、sysvmsg 等扩展,PHP 只需安装最基本的扩展即可

安装swoole

1、安装php7.2

详见文章:CentOS7 下 Yum 安装 LNMP PHP7及其扩展

2、安装pecl

默认安装php7.2没有pecl

yum install php72w-devel
yum install php72w-pear

3、安装swoole

pecl install swoole

如果显示如下错误:

configure: error: in `/var/tmp/pear-build-rootlJXhmg/swoole-4.4.12':
configure: error: C++ preprocessor "/lib/cpp" fails sanity check
See `config.log' for more details
ERROR: `/var/tmp/swoole/configure --with-php-config=/usr/bin/php-config --enable-sockets=no --enable-openssl=no --enable-http2=no --enable-mysqlnd=no' failed

说明没有c++库,swoole依赖gcc-4.8或更高版本,依赖C++11

yum install glibc-headers
yum install gcc-c++

4、修改配置

php.ini 中加入

extension=swoole.so

5、验证是否安装成功

php -m | grep swoole
php --ri swoole

开发案例

并发调用

使用子协程(go)+ 通道(channel) 实现并发请求。

参考官网地址:https://wiki.swoole.com/#/coroutine/multi_call

实现原理

  • 在 onRequest 中需要并发两个 HTTP 请求,可使用 go 函数创建 2 个子协程,并发地请求多个 URL
  • 并创建了一个 chan,使用 use 闭包引用语法,传递给子协程
  • 主协程循环调用 chan->pop,等待子协程完成任务,yield 进入挂起状态
  • 并发的两个子协程其中某个完成请求时,调用 chan->push 将数据推送给主协程
  • 子协程完成 URL 请求后退出,主协程从挂起状态中恢复,继续向下执行调用 $resp->end 发送响应结果

代码示例

<?php
$serv = new Swoole\Http\Server("127.0.0.1", 9503, SWOOLE_BASE);
$serv->on('request', function ($req, $resp)
{
    print_r($req->post);
    # 数据验证
    $result = [];
    if (isset($req->post['signlist'])) {
        $signList = urldecode($req->post['signlist']);
        $signList = json_decode($signList, true);
        if (is_array($signList) || $signList) {
            # 接口验签数组
            $commonPostData = $req->post;
            unset($commonPostData['signlist']);
            $request_url = '****.com';
            $chan_count = count($signList);
            $chan = new chan($chan_count);
            foreach ($signList as $item) {
                print_r($item);
                go(function () use ($chan, $request_url, $commonPostData, $item) {
                    $cli = new Swoole\Coroutine\Http\Client($request_url, 80);
                    $cli->set(['timeout' => 10]);
                    $cli->setHeaders([
                        'User-Agent' => "swoole"
                    ]);

                    $postData = array_merge($commonPostData, $item);
                    $ret = $cli->post('/sign/sign/contract_sign_cert', $postData);
                    $chan->push([$item['contract_id'] => $cli->body]);
                });
            }

            # 返回结果数据组装
            for ($i = 0; $i < $chan_count; $i++) {
                $result += $chan->pop();
            }
        }
    }
    $resp->end(json_encode($result));
});

$serv->start();

Websocket

<?php
$server = new Swoole\Websocket\Server('127.0.0.1', 9601, SWOOLE_SOCK_TCP);
$server->on('start', function ($server) {
    echo "Websocket Server is started at ws://127.0.0.1:9601\n";
});
$server->on('open', function($server, $req) {
   //echo "connection open: {$req->fd}\n";
});
$server->on('message', function($server, $frame)
{
   $data_source = $frame->data;
   $data = json_decode($data_source, true);
   if ($data && isset($data['type']) && isset($data['wscode'])) {
     switch ($data['type']) {
         case 'login':
             $msg = 'type: login, wscode: '.$data['wscode'];
             file_put_contents('logs/login_'.$data['wscode'], $frame->fd);
             break;
         case 'send':
             $msg = 'type: send, wscode: '.$data['wscode'];
             file_put_contents('logs/send_'.$data['wscode'], $data_source);
             $frame_id = file_get_contents('logs/login_'.$data['wscode']);
             if ($frame_id && isset($data['result'])) {
                 $server->push($frame_id, json_encode($data['result']));
             }
             break;
         default:
             $msg = 'type: '.$data['type'];
     }
     echo date('Y-m-d H:i:s').' '.$msg."\n";
   }
});

$server->on('close', function($server, $fd) {
   //echo "connection close: {$fd}\n";
});

$server->start();