如期而至,中的应用百家了乐八大技巧:

 百家乐-操作     |      2020-04-29 10:02

TCP 请求中间件

  • 全局中间件

配置于 app/bean.php:

    /** @see SwoftTcpServerTcpDispatcher */
    'tcpDispatcher' => [
        'middlewares' => [
            AppTcpMiddlewareGlobalTcpMiddleware::class
        ],
    ],
  • 作用于控制器的
/**
 * Class DemoController
 *
 * @TcpController(middlewares={DemoMiddleware::class})
 */
class DemoController
{
    // ....
}

date: 2017-12-14 21:34:51title: swoole 在 swoft 中的应用

更新记录

升级提示:

  • SwooleWebSocketServer::push 第四个参数 $finish 在 swoole 4.4.12 后改为了 int 类型。
  • tcp server 的 TcpServerEvent::CONNECT 事件参数保持跟receive, close一致。 $fd, $server 互换位置。

修复(Fixed)

  • 修复 config 注入时,没有找到值也会使用对应类型的默认值覆盖属性,导致属性默认值被覆盖 d84d50a7
  • 修复 ws server 中使用message调度时,没有过滤空数据,导致多发出一个响应。避免方法swoft-cloud/swoft#1002 d84d50a7
  • 修复 tcp server 中使用message调度时,没有过滤空数据,导致多发出一个响应。07a01ba1
  • 修复 独立使用console组件时缺少 swoft/stdlib 库依赖 c569c81a
  • 修复 ArrayHelper::get 传入key为 integer 时,报参数错误 a44dcad
  • 修复 console 渲染使用table,有int值时,计算宽度报类型错误 74a835ab
  • 修复 error 组件中用户无法自定义设置默认的错误处理级别 4c78aeb
  • 修复 启用和禁用 组件设置 isEnable() 不生效的问题 da8c51e56
  • 修复 在 cygwin 环境使用 uniqid() 方法必须将第二个参数设置为 true c7f688f
  • 修复 在 cygwin 环境不能够设置进程title而导致报错 c466f6a
  • 修复 使用 http response->delCookie() 无法删除浏览器的cookie数据问题 8eb9241
  • 修复 ws server消息调度时,接收到的ext数据不一定是数组导致报错 ff45b35
  • 修复 日志文件按时间拆分问题c195413
  • 修复 日志 JSON 格式小问题a3fc6b9
  • 修复 rpc 服务提供者 getList 调用两次问题fd03e71
  • 修复 redis cluster 不支持 auth 参数7a678f
  • 修复 模型查询 json 类型, 不支持 array 6023a9
  • 修复 redis multi 操作没有及时是否连接 e5f698
  • 修复 redis 不支持 expireAtgeoRadius 749241
  • 修复 crontab 时间戳检测偏差问题 eb08a46

更新(Update):

  • 更新 console 在渲染 help信息之前也会发出事件 ConsoleEvent::SHOW_HELP_BEFORE d3f7bc3
  • 简化和统一 http, ws, tcp, rpc server管理命令逻辑 f202c826
  • 更新 ws 和 tcp Connection类添加 newFromArray 和 toArray 方法,方便通过第三方存储(redis)时导出信息和恢复连接 a8b0b7c
  • 优化 server 添加统一的 swoole pipe message 事件处理,在 ws, tcp 中使用swoft事件来处理进程间消息 1c51a8c

增强(Enhancement)

  • 现在 tcp 请求支持添加全局或对应的方法中间件,流程和使用跟http中间件类似。仅当使用系统调度时有用 6b593877
  • 现在 websocket message 请求支持添加全局或对应的方法中间件,流程和使用跟http中间件类似。仅当使用系统调度时有用 9739815
  • 事件管理允许设置 destroyAfterFire 在每次事件调度后清理事件中携带的数据 50bf43d3
  • 数据库错误异常新增 code 返回fd306f4
  • 协程文件操作 writeFile 新增写失败异常08c4244
  • RPC 新增参数验证8646fc5

(文/开源中国)    

服务器开发涉及到的相关技术领域的知识非常多, 不日积月累打好基础, 是很难真正做好的. 所以我建议:

更多

  • GitHub: 

  • Gitee: 

  • 官网:https://www.swoft.org

  • 文档:

  • 传入配置 server 配置信息, new 一个 swoole server
  • 设置事件监听, 这一步需要大家对 swoole 的进程模型非常熟悉, 一定要看懂下面 2 张图
  • 启动服务器

Websocket消息中间件

  • 全局中间件

配置于 app/bean.php:

    /** @see SwoftWebSocketServerWsMessageDispatcher */
    'wsMsgDispatcher' => [
        'middlewares' => [
            AppWebSocketMiddlewareGlobalWsMiddleware::class
        ],
    ],
  • 作用于控制器的
/**
 * Class HomeController
 *
 * @WsController(middlewares={DemoMiddleware::class})
 */
class TestController
{}

swoole 在 swoft 中的应用:

什么是 Swoft ?

Swoft 是一款基于 Swoole 扩展实现的 PHP 微服务协程框架。Swoft 能像 Go 一样,内置协程网络服务器及常用的协程客户端且常驻内存,不依赖传统的 PHP-FPM。有类似 Go 语言的协程操作方式,有类似 Spring Cloud 框架灵活的注解、强大的全局依赖注入容器、完善的服务治理、灵活强大的 AOP、标准的 PSR 规范实现等等。

Swoft 通过长达三年的积累和方向的探索,把 Swoft 打造成 PHP 界的 Spring Cloud, 它是 PHP 高性能框架和微服务治理的最佳选择。

  • SwooleHttpServer
  • SwooleHttpRequest
  • SwooleHttpResponse

Http Session

通过 Composer 安装 swoft/session 组件

  • 在项目 composer.json 所在目录执行 composer require swoft/session
  • 将 SwoftHttpSessionSessionMiddleware 中间件加入到全局中间件

在配置文件 app/bean.php 里:

    'httpDispatcher'    => [
        // Add global http middleware
        'middlewares'      => [
            SwoftHttpSessionSessionMiddleware::class,
        ],
    ],

默认是基于本地文件驱动,保存在 runtime/sessions 目录

更在驱动只需要配置对应 handler 类,例如配置 Redis 驱动:

'sessionHandler' => [
    'class'    => RedisHandler::class,
    // Config redis pool
    'redis' => bean('redis.pool')
],

PSR-7: HTTP message interfaces

Swoft v2.0.7

2.0.7 在 2.0.6 上继续扬帆,已在大量的生产业务中使用,得到很多用户的肯定和支持。正式版本我们做了许多改进和优化,拥有了更好的性能。

  • 新增 Http Session 功能组件,提供http会话管理, 支持多种存储驱动
  • 增强 TCP server 请求支持添加全局或对应的方法中间件
  • 增强 Websocket server 消息请求支持添加全局或对应的方法中间件
  • SwooleServer: swoole2.0 协程 Server

  • SwooleHttpServer: swoole2.0 协程 http Server, 继承自 SwooleServer

  • SwooleCoroutineClient: 协程客户端, swoole 封装了 tcp / http / redis / mysql

  • SwooleCoroutine: 协程工具集, 获取当前协程id,反射调用等能力

  • SwooleProcess: 进程管理模块, 可以在 SwooleServer 之外扩展更多功能

  • SwooleAsync: 异步文件 IO

  • SwooleTimer: 基于 timerfd + epoll 实现的异步毫秒定时器,可完美的运行在 EventLoop 中

  • SwooleEvent: 直接操作底层 epoll/kqueue 事件循环(EventLoop)的接口

  • SwooleLock: 在 PHP 代码中可以很方便地创建一个锁, 用来实现数据同步

  • SwooleTable: 基于共享内存实现的超高性能数据结构

使用 swoole server 十分简单:

SwooleHttpResponse

SwooleHttpResponse 也是支持常见功能:

// SwooleHttpResponse $response$response->header($key, $value); // -> header("$key: $valu", $httpCode)$response->cookie(); // -> setcookie()$response->status(); // http 状态码

当然, swoole 还提供了常用的功能:

$response->sendfile(); // 给客户端发送文件$response->gzip(); // nginx + fpm 的场景, nginx 处理掉了这个$response->end(); // 返回数据给客户端$response->write(); // 分段传输数据, 最后调用 end() 表示数据传输结束

phper 注意下这里的 write()end(), 这里有一个 http chunk 的知识点. 需要返回大量数据给客户端时, 需要分段进行发送. 所以先用 write() 发送数据, 最后用 end() 表示结束. 数据量不大时, 直接调用 end 返回就可以了.

在框架具体实现上, 和上面一样, laravel 依旧用的 SymfonyResponse, swoft 也是实现 PSR-7 定义的接口, 对 SwooleHttpResponse 进行封装.

swoft 使用 SwooleServer 来实现 RPC 服务, 其实在上面的多端口监听, 也是为了开启 RPC 服务. 注意一下单独启用中回调函数的区别:

// SwoftServerRpcServerpublic function start(){ // rpc server $this->server = new Server($this->tcpSetting['host'], $this->tcpSetting['port'], $this->tcpSetting['model'], $this->tcpSetting['type']); // 设置回调函数 $listenSetting = $this->getListenTcpSetting(); $setting = array_merge($this->setting, $listenSetting); $this->server->set; $this->server->on('start', [$this, 'onStart']); $this->server->on('workerStart', [$this, 'onWorkerStart']); $this->server->on('managerStart', [$this, 'onManagerStart']); $this->server->on('task', [$this, 'onTask']); $this->server->on('finish', [$this, 'onFinish']); $this->server->on('connect', [$this, 'onConnect']); $this->server->on('receive', [$this, 'onReceive']); $this->server->on('pipeMessage', [$this, 'onPipeMessage']); // 接收管道信息时触发的回调函数 $this->server->on('close', [$this, 'onClose']); // before start $this->beforeStart(); $this->server->start();}

swoole 自带的协程的客户端, swoft 都封装进了连接池, 用来提高性能. 同时, 为了业务使用方便, 既有协程连接, 也有同步连接, 方便业务使用时无缝切换.

同步/协程连接的实现代码:

// RedisConnect -> 使用 swoole 协程客户端public function createConnect(){ // 连接信息 $timeout = $this->connectPool->getTimeout(); $address = $this->connectPool->getConnectAddress(); list($host, $port) = explode(":", $address); // 创建连接 $redis = new SwooleCoroutineRedis(); $result = $redis->connect($host, $port, $timeout); if ($result == false) { App::error("redis连接失败,host=" . $host . " port=" . $port . " timeout=" . $timeout); return; } $this->connect = $redis;}// SyncRedisConnect -> 使用 Redis 同步客户端public function createConnect(){ // 连接信息 $timeout = $this->connectPool->getTimeout(); $address = $this->connectPool->getConnectAddress(); list($host, $port) = explode(":", $address); // 初始化连接 $redis = new Redis(); $redis->connect($host, $port, $timeout); $this->connect = $redis;}

swoft 中实现连接池的代码在 src/Pool 下实现, 由三部分组成:

  • Connect: 即上面代码中的连接
  • Balancer: 负载均衡器, 目前实现了 随机/轮询 2 种方式
  • Pool: 连接池, 调用 Balancer, 返回 Connect

详细内容可以参考之前的 blog - swoft 源码解读

作为首个使用 Swoole2.0 原生协程的框架, swoft 希望将协程的能力扩展到框架的核心设计中. 使用 SwoftBaseCoroutine 进行封装, 方便整个应用中使用:

public static function id(){ $cid = SwCoroutine::getuid(); // swoole 协程 $context = ApplicationContext::getContext(); if ($context == ApplicationContext::WORKER || $cid !== -1) { return $cid; } if ($context == ApplicationContext::TASK) { return Task::getId(); } if($context == ApplicationContext::CONSOLE){ return Console::id(); } return Process::getId();}

如同这段代码所示, Swoft 希望将方便易用的协程的能力, 扩展到 Console/Worker/Task/Process 等等不同的应用场景中

原生的 call_user_func() / call_user_func_array() 中无法使用协程 client, 所以 swoole 在协程组件中也封装的了相应的实现, swoft 中也有使用到, 请自行阅读源码.

进程管理模块, 适合处理和 Server 比较独立的常驻进程任务, 在 swoft 中, 在以下场景中使用到:

  • 协程定时器 CronTimerProcess
  • 协程执行命令 CronExecProcess
  • 热更新进程 ReloadProcess

swoft 使用 SwoftProcessSwooleProcess 进行了封装:

// SwoftProcesspublic static function create( AbstractServer $server, string $processName, string $processClassName) { ... // 创建进程 $process = new SwooleProcess(function (SwooleProcess $process) use ($processClass, $processName) { // reload BeanFactory::reload(); $initApplicationContext = new InitApplicationContext(); $initApplicationContext->init(); App::trigger(AppEvent::BEFORE_PROCESS, null, $processName, $process, null); PhpHelper::call([$processClass, 'run'], [$process]); App::trigger(AppEvent::AFTER_PROCESS); }, $iout, $pipe); // 启动 SwooleProcess 并绑定回调函数即可 return $process;}

swoft 在日志场景下使用 SwooleAsync 来提高性能, 同时保留了原有的同步方式, 方便进行切换

// SwoftLogFileHandlerprivate function aysncWrite(string $logFile, string $messageText){ while  { $result = SwooleAsync::writeFile($logFile, $messageText, null, FILE_APPEND); // 使用起来很简单 if ($result == true) { break; } }}

服务器出于性能考虑, 通常都是 常驻内存 的, 传统的 php-fpm 也是, 修改了配置需要 reload 服务器才能生效. 也因为此, 服务器领域出现了新的需求 -- 热更新. swoole 在进程管理上已经做了很多优化, 这里摘抄部分 wiki 内容:

Swoole提供了柔性终止/重启的机制SIGTERM: 向主进程/管理进程发送此信号服务器将安全终止SIGUSR1: 向主进程/管理进程发送SIGUSR1信号,将平稳地restart所有worker进程

目前大家采用的, 比较常见的方案, 是基于 Linux Inotify 特性, 通过监测文件变更来触发 swoole server reload. PHP 中有 Inotify 扩展, 方便使用, 具体实现在 SwoftBaseInotify 中:

public function run(){ $inotify = inotify_init(); // 设置为非阻塞 stream_set_blocking($inotify, 0); $tempFiles = []; $iterator = new RecursiveDirectoryIterator($this->watchDir); $files = new RecursiveIteratorIterator($iterator); foreach ($files as $file) { $path = dirname; // 只监听目录 if (!isset($tempFiles[$path])) { $wd = inotify_add_watch($inotify, $path, IN_MODIFY | IN_CREATE | IN_IGNORED | IN_DELETE); $tempFiles[$path] = $wd; $this->watchFiles[$wd] = $path; } } // swoole Event add $this->addSwooleEvent;}private function addSwooleEvent{ // swoole Event add Event::add($inotify, function  { // 使用 SwooleEvent // 读取有事件变化的文件 $events = inotify_read; if  { $this->reloadFiles($inotify, $events); } }, null, SWOOLE_EVENT_READ);}

swoft 在 CircuitBreaker 中的 HalfOpenState 使用到了, 并且这块的实现比较复杂, 推荐阅读源码:

// CircuitBreakerpublic function init(){ // 状态初始化 $this->circuitState = new CloseState; $this->halfOpenLock = new SwooleLock(SWOOLE_MUTEX); // 初始化互斥锁}// HalfOpenStatepublic function doCall($callback, $params = [], $fallback = null){ // 加锁 $lock = $this->circuitBreaker->getHalfOpenLock(); $lock->lock(); list($class ,$method) = $callback; .... // 释放锁 $lock->unlock(); ...}

锁的使用, 难点主要在了解各种不同锁使用的场景, 目前 swoole 支持:

  • 文件锁 SWOOLE_FILELOCK
  • 读写锁 SWOOLE_RWLOCK
  • 信号量 SWOOLE_SEM
  • 互斥锁 SWOOLE_MUTEX
  • 自旋锁 SWOOLE_SPINLOCK

定时器基本都会使用到, phper 用的比较多的应该是 crontab 了. 基于这个考虑, swoft 对 Timer 进行了封装, 方便 phper 用 熟悉的姿势 继续使用.

swoft 对 SwooleTimer 进行了简单的封装, 代码在 BaseTimer 中:

// 设置定时器public function addTickTimer(string $name, int $time, $callback, $params = []){ array_unshift($params, $name, $callback); $tid = SwooleTimer::tick($time, [$this, 'timerCallback'], $params); $this->timers[$name][$tid] = $tid; return $tid;}// 清除定时器public function clearTimerByName(string $name){ if (!isset($this->timers[$name])) { return true; } foreach ($this->timers[$name] as $tid => $tidVal) { SwooleTimer::clear; } unset($this->timers[$name]); return true;}

SwooleTable 是在内存中开辟一块区域, 实现类似关系型数据库表这样的数据结构, 关于 SwooleTable 的实现原理, rango 写过专门的文章 swoole_table 实现原理剖析, 推荐阅读.

SwooleTable 在使用上需要注意以下几点:

  • 类似关系型数据库, 需要提前定义好 表结构
  • 需要预先判断数据的大小
  • 注意内存, swoole 会更根据上面 2 个定义, 在调用 SwooleTable->create() 时分配掉这些内存

swoft 中则是使用这一功能, 来实现 crontab 方式的任务调度:

private $originTable;private $runTimeTable;private $originStruct = [ 'rule' => [SwooleTable::TYPE_STRING, 100], 'taskClass' => [SwooleTable::TYPE_STRING, 255], 'taskMethod' => [SwooleTable::TYPE_STRING, 255], 'add_time' => [SwooleTable::TYPE_STRING, 11],];private $runTimeStruct = [ 'taskClass' => [SwooleTable::TYPE_STRING, 255], 'taskMethod' => [SwooleTable::TYPE_STRING, 255], 'minte' => [SwooleTable::TYPE_STRING, 20], 'sec' => [SwooleTable::TYPE_STRING, 20], 'runStatus' => [SwooleTABLE::TYPE_INT, 4],];// 使用 SwooleTableprivate function createOriginTable(): bool{ $this->setOriginTable(new SwooleTable('origin', self::TABLE_SIZE, $this->originStruct)); return $this->getOriginTable()->create();}

老生常谈了, 很多人吐槽 swoole 坑, 文档不好. 说句实话, 要敢于直面自己服务器开发能力不足的现实. 我经常提的一句话:

要把 swoole 的 wiki 看 3 遍.

写这篇 blog 的初衷是给大家介绍一下 swoole 在 swoft 中的应用场景, 帮助大家尝试进行 swoole 落地. 希望这篇 blog 能对你有所帮助, 也希望你能多多关注 swoole 社区, 关注 swoft 框架, 能感受到服务器开发带来的乐趣.

swoft 官网:

swoft 源码解读:

号外号外, 欢迎大家 star, 我们开发组定了一个 star 1000+ 就线下聚一次的小目标