swoole的官网文档其实非常详细,基础知识可以直接上官网查看文档: https://wiki.swoole.com/wiki/page/1.html >[info] 本小册只针对swoole的常用特性以及实际应用做记录以及部分框架扩展源码分析,因为我们一般都是基于框架开发,而绝大多数框架都有对应的扩展包(就算没有,我们知道原理后也可以自己封装一个),我们这里是**拿thinkphp-swoole来分析其源码和使用场景**,弄清楚thinkphp-swoole的原理后,easyswoole等其他swoole框架都是大同小异。 ## 使用swoole要注意的 * 在`4.2.0`以前的版本不要在代码中执行`sleep`以及其他睡眠函数,这样会导致整个进程阻塞。**最新的`4.2.0`版本增加了对`sleep`函数的`Hook`**,底层替换了`sleep`、`usleep`、`time_nanosleep`、`time_sleep_until`四个函数。当调用这些睡眠函数时会自动切换为协程定时器调度。不会阻塞进程。注意要先开启 `\Swoole\Runtime::enableCoroutine();` * `exit/die`是危险的,会导致worker进程退出 * 可通过`register_shutdown_function`来捕获致命错误,在进程异常退出时做一些请求工作,具体参看[/wiki/page/305.html](https://wiki.swoole.com/wiki/page/305.html) * PHP代码中如果有异常抛出,必须在回调函数中进行`try/catch`捕获异常,否则会导致工作进程退出 * swoole不支持`set_exception_handler`,必须使用`try/catch`方式处理异常 * Worker进程不得共用同一个`Redis`或`MySQL`等网络服务客户端,Redis/MySQL创建连接的相关代码可以放到`onWorkerStart`回调函数中。原因是如果共用1个连接,那么返回的结果无法保证被哪个进程处理。持有连接的进程理论上都可以对这个连接进行读写,这样数据就发生错乱了。具体参考[/wiki/page/325.html](https://wiki.swoole.com/wiki/page/325.html) * 不能使用类的属性保存客户端连接信息,因为一个worker进程可以处理多个客户端连接,导致类属性数据错乱。常量则是可以的。 * 要注意 [进程隔离](https://wiki.swoole.com/wiki/page/1038.html) 问题,多进程要共用的变量数据建议通过用`Redis`来处理。 ## 知识点理解 1. 不管是http,还是WebSocket,都是继承于server。所以Server中方法httpserver和websocketServer都可以用。 2. 要理解Swoole是基于**事件回调**的,当作为Server时,[回调函数](https://wiki.swoole.com/wiki/page/41.html)有很多 * 进程启动时执行的:onStart、onManagerStart、onWorkerStart;onWorkerStop、onManagerStop、onShutdown;onWorkerError * 客户端交互时触发的:onReceive/onRequest/onPacket/onMessage、onOpen/onConnect、onClose * Task:onTask、onFinish * Timer:onTimer 3. 事件执行顺序 * 所有事件回调均在`$server->start`后发生 * 服务器关闭程序终止时最后一次事件是`onShutdown` * 服务器启动成功后,`onStart/onManagerStart/onWorkerStart`会在不同的进程内并发执行。 * `onReceive/onConnect/onClose/onTimer`在worker进程(包括task进程)中各自触发 * worker/task进程启动/结束时会分别调`用onWorkerStart/onWorkerStop` * `onTask`事件仅在task进程中发生 * `onFinish`事件仅在worker进程中发生 * `onStart/onManagerStart/onWorkerStart`3个事件的执行顺序是不确定的 * UDP协议下只有`onReceive`事件,没有`onConnect/onClose`事件 * 如果未设置`onPacket`回调函数,收到UDP数据包默认会回调`onReceive`函数 * `onOpen`事件回调是可选的:当WebSocket客户端与服务器建立连接并完成握手后会回调此函数 4. 理解其进程和线程模型 * `Master`进程是一个多线程进程,其中有一组非常重要的线程,叫做`Reactor`线程(组),每当一个客户端连接上服务器的时候,都会由Master进程从已有的Reactor线程中,根据一定规则挑选一个,专门负责向这个客户端提供维持链接、处理网络IO与收发数据等服务。分包拆包等功能也是在这里完成。 * `Manager`进程,某种意义上可以看做一个代理层,它本身并不直接处理业务,其主要工作是将Master进程中收到的数据转交给Worker进程,或者将Worker进程中希望发给客户端的数据转交给Master进程进行发送。同时还负责监控Worker进程,如果Worker进程因为某些意外挂了,Manager进程会重新拉起新的Worker进程,有点像Supervisor的工作。而这个特性,也是最终实现热重载的核心机制。 * `Worker`进程其实就是处理各种业务工作的进程,Manager将数据包转交给Worker进程,然后Worker进程进行具体的处理,并根据实际情况将结果反馈给客户端 ![](https://i.loli.net/2019/04/17/5cb6d34f4d29a.jpg) 5. `Manager`进程下的`TaskWorker进程` * 接受由`Worker`进程通过`swoole_server->task/taskwait`方法投递的任务 * 处理任务,并将结果数据返回(使用`swoole_server->finish`)给`Worker`进程 * 完全是**同步阻塞**模式 * `TaskWorker`以多进程的方式运行 6. Reactor、Worker、TaskWorker的关系 * 可以理解为`Reactor`就是`nginx`,`Worker`就是`php-fpm`。`Reactor`线程异步并行地处理网络请求,然后再转发给`Worker`进程中去处理。`Reactor`和`Worker`间通过`UnixSocket`进行通信。 * 在`php-fpm`的应用中,经常会将一个任务异步投递到`Redis`等队列中,并在后台启动一些`php`进程异步地处理这些任务。`Swoole`提供的`TaskWorker`是一套更完整的方案,将任务的投递、队列、`php`任务处理进程管理合为一体。通过底层提供的`API`可以非常简单地实现异步任务的处理。另外`TaskWorker`还可以在任务执行完成后,再返回一个结果反馈到`Worker`。 * `Swoole`的`Reactor`、`Worker`、`TaskWorker`之间可以紧密的结合起来,提供更高级的使用方式。一个更通俗的比喻,假设`Server`就是一个工厂,那`Reactor`就是销售,接受客户订单。而`Worker`就是工人,当销售接到订单后,`Worker`去工作生产出客户要的东西。而`TaskWorker`可以理解为行政人员,可以帮助`Worker`干些杂事,让`Worker`专心工作。