Swoole
Swoole 是一个使用 C++ 语言编写的基于异步事件驱动和协程的并行网络通信引擎,为 PHP 提供协程、高性能网络编程支持。提供了多种通信协议的网络服务器和客户端模块,可以方便快速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通讯、游戏、微服务等,使 PHP 不再局限于传统的 Web 领域。
Swoole 类图
可以直接点击链接到对应的文档页
├── demo.gif
├── Swoole4.tgz
├── src
├── composer.json
├── README.md
├── composer.lock
└── run.php
├── dash-demo.png
├── .gitignore
├── Swoole.docset
├── icon.png
├── ._icon.png
├── icon@2x.png
├── ._icon@2x.png
└── Contents
│ ├── Resources
│ ├── docSet.dsidx
│ └── Documents
│ │ ├── ._css
│ │ ├── ._index.html
│ │ ├── css
│ │ ├── ._default.css
│ │ ├── ._noframe.css
│ │ ├── ._Word2Chm.css
│ │ ├── ._bootstrap.css
│ │ ├── noframe.css
│ │ └── Word2Chm.css
│ │ ├── client_init.html
│ │ ├── server12tcp_init.html
│ │ ├── getting_started12extension.html
│ │ ├── server12pipemessage_class.html
│ │ ├── start12start_server.html
│ │ ├── server12taskresult_class.html
│ │ ├── server12event_class.html
│ │ ├── other12config.html
│ │ ├── start12start_udp_server.html
│ │ ├── server12statusinfo_class.html
│ │ ├── other12discussion.html
│ │ ├── coroutine12proc_open.html
│ │ ├── server12init.html
│ │ ├── server12packet_class.html
│ │ ├── start12start_task.html
│ │ ├── coroutine12barrier.html
│ │ ├── server12co_init.html
│ │ ├── start12start_ws_server.html
│ │ ├── start12start_tcp_server.html
│ │ ├── coroutine12wait_group.html
│ │ ├── coroutine12multi_call.html
│ │ ├── blog_list.html
│ │ ├── library.html
│ │ ├── start12start_http_server.html
│ │ ├── question12swoole.html
│ │ ├── other12issue.html
│ │ ├── version12supported.html
│ │ ├── start12start_mqtt.html
│ │ ├── coroutine12gdb.html
│ │ ├── learn_other.html
│ │ ├── case.html
│ │ ├── server12server_port.html
│ │ ├── index.html
│ │ ├── other12sysctl.html
│ │ ├── coroutine.html
│ │ ├── coroutine_client12init.html
│ │ ├── server12task_class.html
│ │ ├── process12process_manager.html
│ │ ├── version12bc.html
│ │ ├── other12alias.html
│ │ ├── CONTRIBUTING.html
│ │ ├── coroutine12http_server.html
│ │ ├── memory12lock.html
│ │ ├── start12coroutine.html
│ │ ├── server12port.html
│ │ ├── coroutine12scheduler.html
│ │ ├── consts.html
│ │ ├── server12properties.html
│ │ ├── coroutine12conn_pool.html
│ │ ├── coroutine12ws_server.html
│ │ └── coroutine12channel.html
│ └── Info.plist
└── README.md
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/demo.gif
--------------------------------------------------------------------------------
/Swoole4.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole4.tgz
--------------------------------------------------------------------------------
/src/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "erusev/parsedown": "^1.7"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/dash-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/dash-demo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | src/vendor/
2 | src/docset.sql
3 | src/html/
4 | src/md/
5 | src/result.log
6 |
7 |
8 | /vendor/
9 |
--------------------------------------------------------------------------------
/Swoole.docset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/icon.png
--------------------------------------------------------------------------------
/Swoole.docset/._icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/._icon.png
--------------------------------------------------------------------------------
/Swoole.docset/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/icon@2x.png
--------------------------------------------------------------------------------
/Swoole.docset/._icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/._icon@2x.png
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/docSet.dsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/docSet.dsidx
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/._css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/Documents/._css
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/._index.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/Documents/._index.html
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/css/._default.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/Documents/css/._default.css
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/css/._noframe.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/Documents/css/._noframe.css
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/css/._Word2Chm.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/Documents/css/._Word2Chm.css
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/css/._bootstrap.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/HEAD/Swoole.docset/Contents/Resources/Documents/css/._bootstrap.css
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # swoole-chinese-docset
2 |
3 | Swoole Chinese Docset for Dash
4 |
5 | [generation method](./src/README.md)
6 |
7 | [Swooke Wiki](http://wiki.swoole.com/)
8 |
9 | 
10 |
11 | 
12 |
13 |
14 | ## CHANGELOG
15 | - Added `swoole 4 is supported` [20240901]
16 |
17 | [Kapeli/Dash-User-Contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/Swoole_Chinese)
18 |
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Generate Swoole.docset
3 |
4 |
5 | ### fetch markdown && generate html
6 | ```
7 | > php ./run.php
8 | ```
9 |
10 | ### import database
11 |
12 | ```
13 | > tar xzvf Swoole.tgz
14 | > cd /path/to/project/Swoole.docset/Contents/Resources
15 | > sqlite3 docSet.dsidx
16 | > .read docset.sql
17 |
18 | ```
19 |
20 | ### copy html to document
21 | ```
22 | > cp -R /path/to/project/src/html/* Swoole.docset/Contents/Resources/Documents
23 | ```
24 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
此节包含所有Swoole提供的客户端,包括同步阻塞客户端和协程客户端,Swoole4不再支持异步客户端,相应的需求完全可以用协程客户端代替,参考。
18 | 19 | -------------------------------------------------------------------------------- /Swoole.docset/Contents/Resources/Documents/server12tcp_init.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |此节包含Swoole\Server类的全部方法、属性、配置项以及所有的事件。Swoole\Server类是所有异步风格服务器的基类,后面章节的Swoole\Http\Server、Swoole\WebSocket\Server、Swoole\Redis\Server都是它的子类。
由于某些跟踪调试的PHP扩展大量使用了全局变量,可能会导致Swoole协程发生崩溃。请关闭以下相关扩展:
Swoole协程无法运行在 phalcon 框架中)
24 |
25 | 从 5.1 版本开始可直接使用 xdebug 扩展来调试 Swoole 程序,通过命令行参数或者修改 php.ini 启用。
swoole.enable_fiber_mock=On
28 | 或者
29 |php -d swoole.enable_fiber_mock=On your_file.php这里是对Swoole\Server\PipeMessage的详细介绍。
18 |
返回数据来源方的worker进程id,该属性是一个int类型的整数。
Swoole\Server\PipeMessage->source_worker_id
24 |
25 | 返回该请求数据到达时间dispatch_time,该属性是一个double类型。
Swoole\Server\PipeMessage->dispatch_time
28 |
29 | 返回该连接携带的数据data,该属性是一个string类型的字符串。
Swoole\Server\PipeMessage->data
32 |
33 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/start12start_server.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Swoole的绝大部分功能只能用于cli命令行环境,请首先准备好Linux Shell环境。可使用Vim、Emacs、PhpStorm或其他编辑器编写代码,并在命令行中通过如下指令执行程序。
php /path/to/your_file.php
19 | 成功执行Swoole服务器程序后,如果你的代码中没有任何echo语句,屏幕不会有任何输出,但实际上底层已经在监听网络端口,等待客户端发起连接。可使用相应的客户端工具和程序连接到服务器,进行测试。
20 |
默认情况下,启动Swoole的服务后,通过启动的窗口CTRL+C就可以结束服务,但此时如果窗口退出会有问题,需要后台启动,详情参考守护进程化。
23 | !> 简单示例中的示例大部分都是异步风格的编程模式,用协程风格同样可以做到示例中的功能,参见服务端 (协程风格)。
24 | !> Swoole提供的绝大的部分模块只能用于cli命令行终端。目前只有同步阻塞客户端可以用于PHP-FPM环境下。
这里是对Swoole\Server\TaskResult的详细介绍。
18 |
返回所在的Reactor线程id,该属性是一个int类型的整数。
Swoole\Server\TaskResult->task_id
24 |
25 | 返回该执行结果来自哪个task进程,该属性是一个int类型的整数。
Swoole\Server\TaskResult->task_worker_id
28 |
29 | 返回该连接携带的数据data,该属性是一个?string类型的字符串。
Swoole\Server\TaskResult->dispatch_time
32 |
33 | 返回该连接携带的数据data,该属性是一个string类型的字符串。
Swoole\Server\StatusInfo->data
36 |
37 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/server12event_class.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 这里是对Swoole\Server\Event的详细介绍。
18 |
返回所在的Reactor线程id,该属性是一个int类型的整数。
Swoole\Server\Event->reactor_id
24 |
25 | 返回该连接的文件描述符fd,该属性是一个int类型的整数。
Swoole\Server\Event->fd
28 |
29 | 返回该请求数据到达时间dispatch_time,该属性是一个double类型。只有在onReceive事件里该属性才不为0。
Swoole\Server\Event->dispatch_time
32 |
33 | 返回该客户端发送的数据data,该属性是一个string类型的字符串。只有在onReceive事件里该属性不为null。
Swoole\Server\Event->data
36 |
37 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/other12config.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | | 配置 | 21 |默认值 | 22 |作用 | 23 |
|---|---|---|
| swoole.enable_coroutine | 28 |On | 29 |On, Off 开关内置协程,详见。 |
30 |
| swoole.display_errors | 33 |On | 34 |开启/关闭Swoole错误信息。 |
35 |
| swoole.unixsock_buffer_size | 38 |8M | 39 |设置进程间通信的Socket缓存区尺寸,等价于socket_buffer_size。 |
40 |
| swoole.use_shortname | 43 |On | 44 |是否启用短别名,详见。 | 45 |
| swoole.enable_preemptive_scheduler | 48 |Off | 49 |可防止某些协程死循环占用CPU时间过长(10ms的CPU时间)导致其它协程得不到调度,示例。 | 50 |
| swoole.enable_library | 53 |On | 54 |开启/关闭扩展内置的library | 55 |
请将以下代码写入udpServer.php。
20 |$server = new Swoole\Server('127.0.0.1', 9502, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
21 | //监听数据接收事件。
22 | $server->on('Packet', function ($server, $data, $clientInfo) {
23 | var_dump($clientInfo);
24 | $server->sendto($clientInfo['address'], $clientInfo['port'], "Server:{$data}");
25 | });
26 | //启动服务器
27 | $server->start();
28 | UDP服务器与TCP服务器不同,UDP没有连接的概念。启动Server后,客户端无需Connect,直接可以向Server监听的9502端口发送数据包。对应的事件为onPacket。
29 |$clientInfo是客户端的相关信息,是一个数组,有客户端的IP和端口等内容。$server->sendto 方法向客户端发送数据。
32 | !> Docker 默认使用 TCP 协议来通信,如果你需要使用 UDP 协议,你需要通过配置 Docker 网络来实现。
33 | docker run -p 9502:9502/udp <image-name>
34 |
35 | php udpServer.php
37 | UDP服务器可以使用 netcat -u 来连接测试。
netcat -u 127.0.0.1 9502
39 | hello
40 | Server: hello这里是对Swoole\Server\StatusInfo的详细介绍。
18 |
返回当前worker进程id,该属性是一个int类型的整数。
Swoole\Server\StatusInfo->worker_id
24 |
25 | 返回当前worker进程父进程id,该属性是一个int类型的整数。
Swoole\Server\StatusInfo->worker_pid
28 |
29 | 返回进程状态status,该属性是一个int类型的整数。
Swoole\Server\StatusInfo->status
32 |
33 | 返回进程退出状态码exit_code,该属性是一个int类型的整数,范围是0-255。
Swoole\Server\StatusInfo->exit_code
36 |
37 | 进程退出的信号signal,该属性是一个int类型的整数。
Swoole\Server\StatusInfo->signal
40 |
41 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/other12discussion.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 最快响应:GitHub Issue区 (在此提问交流请尊重问题模板和GitHub社区规则) 20 | 查看如何提交错误报告 21 |
22 |有问题可以先去问答系统:wenda.swoole.com 中搜索相似问题,如得不到解决请统一在问答系统当中发帖询问。
24 | 发帖后会经过人工审核,有新的回复时,右上角我的问答中会有所提示。
25 | 为了确保问有所答,由Swoole开发组进行轮值策略,每人负责一天,专职来解决问答中的问题。
26 |
由于微信群无法直接加入,故可先加下方二维码好友,并声明:加入Swoole微信交流群,同意添加好友之后会邀请加入群聊。
40 |
41 |
微信搜索或扫描下方二维码,关注Swoole官方公众号,可以及时得到项目最新进展、版本更新、活动与聚会、周边开源项目、产品案例等信息。
44 | 
由于在协程空间内fork进程会带着其他协程上下文,因此底层禁止了在Coroutine中使用Process模块。可以使用
System::exec()或Runtime Hook+shell_exec实现外面程序运行Runtime Hook+proc_open实现父子进程交互通信
21 |
22 | use Swoole\Runtime;
26 | use function Swoole\Coroutine\run;
27 | Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
28 | run(function () {
29 | $descriptorspec = array(
30 | 0 => array("pipe", "r"),
31 | 1 => array("pipe", "w"),
32 | 2 => array("file", "/tmp/error-output.txt", "a")
33 | );
34 | $process = proc_open('php ' . __DIR__ . '/read_stdin.php', $descriptorspec, $pipes);
35 | $n = 10;
36 | while ($n--) {
37 | fwrite($pipes[0], "hello #$n \n");
38 | echo fread($pipes[1], 8192);
39 | }
40 | fclose($pipes[0]);
41 | proc_close($process);
42 | });
43 |
44 | while(true) {
46 | $line = fgets(STDIN);
47 | if ($line) {
48 | echo $line;
49 | } else {
50 | break;
51 | }
52 | }方便的创建一个异步服务器程序,支持TCP、UDP、unixSocket 3 种socket类型,支持IPv4和IPv6,支持SSL/TLS单向双向证书的隧道加密。使用者无需关注底层实现细节,仅需要设置网络事件的回调函数即可,示例参考快速启动。
18 | !> 只是Server端的风格是异步的(即所有事件都需要设置回调函数),但同时也是支持协程的,开启了enable_coroutine之后就支持协程了(默认开启),协程下所有的业务代码都是同步写法。
19 | 前往了解:
20 | Server 的三种运行模式介绍
21 | Process、ProcessPool、UserProcess的区别是什么
22 | Master进程、Reactor线程、Worker进程、Task进程、Manager进程的区别与联系
23 |
29 | 
这里是对Swoole\Server\Packet的详细介绍。
18 |
返回服务端文件描述符fd,该属性是一个int类型的整数。
Swoole\Server\Packet->server_socket
24 |
25 | 返回服务端监听端口server_port,该属性是一个int类型的整数。
Swoole\Server\Packet->server_port
28 |
29 | 返回该请求数据到达时间dispatch_time,该属性是一个double类型。
Swoole\Server\Packet->dispatch_time
32 |
33 | 返回客户端地址address,该属性是一个string类型的字符串。
Swoole\Server\Packet->address
36 |
37 | 返回客户端监听端口port,该属性是一个int类型的整数。
Swoole\Server\Packet->port
40 |
41 | 返回客户端的传递的数据data,该属性是一个string类型的字符串。
Swoole\Server\Packet->data
44 |
45 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/start12start_task.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 在Server程序中如果需要执行很耗时的操作,比如一个聊天服务器发送广播,Web服务器中发送邮件。如果直接去执行这些函数就会阻塞当前进程,导致服务器响应变慢。 18 | Swoole提供了异步任务处理的功能,可以投递一个异步任务到TaskWorker进程池中执行,不影响当前请求的处理速度。 19 |
20 |基于第一个TCP服务器,只需要增加onTask和onFinish 2个事件回调函数即可。另外需要设置task进程数量,可以根据任务的耗时和任务量配置适量的task进程。 22 | 请将以下代码写入task.php。
23 |$serv = new Swoole\Server('127.0.0.1', 9501);
24 | //设置异步任务的工作进程数量。
25 | $serv->set([
26 | 'task_worker_num' => 4
27 | ]);
28 | //此回调函数在worker进程中执行。
29 | $serv->on('Receive', function($serv, $fd, $reactor_id, $data) {
30 | //投递异步任务
31 | $task_id = $serv->task($data);
32 | echo "Dispatch AsyncTask: id={$task_id}\n";
33 | });
34 | //处理异步任务(此回调函数在task进程中执行)。
35 | $serv->on('Task', function ($serv, $task_id, $reactor_id, $data) {
36 | echo "New AsyncTask[id={$task_id}]".PHP_EOL;
37 | //返回任务执行的结果
38 | $serv->finish("{$data} -> OK");
39 | });
40 | //处理异步任务的结果(此回调函数在worker进程中执行)。
41 | $serv->on('Finish', function ($serv, $task_id, $data) {
42 | echo "AsyncTask[{$task_id}] Finish: {$data}".PHP_EOL;
43 | });
44 | $serv->start();
45 | 调用$serv->task()后,程序立即返回,继续向下执行代码。onTask回调函数Task进程池内被异步执行。执行完成后调用$serv->finish()返回结果。
46 | !> finish操作是可选的,也可以不返回任何结果,如果在onTask事件中通过return返回结果时,相当于调用Swoole\Server::finish()操作。
在 Swoole Library 中底层提供了一个更便捷的协程并发管理工具:Coroutine\Barrier 协程屏障,或者叫协程栅栏。基于 PHP 引用计数和 Coroutine API 实现。
18 | 相比于Coroutine\WaitGroup,Coroutine\Barrier使用更简单一些,只需通过参数传递或者闭包的use语法,引入子协程函数上即可。
19 | !> Swoole 版本 >= v4.5.5 时可用。
20 |
use Swoole\Coroutine\Barrier;
23 | use Swoole\Coroutine\System;
24 | use function Swoole\Coroutine\run;
25 | use Swoole\Coroutine;
26 | run(function () {
27 | $barrier = Barrier::make();
28 | $count = 0;
29 | $N = 4;
30 | foreach (range(1, $N) as $i) {
31 | Coroutine::create(function () use ($barrier, &$count) {
32 | System::sleep(0.5);
33 | $count++;
34 | });
35 | }
36 | Barrier::wait($barrier);
37 |
38 | assert($count == $N);
39 | });
40 |
41 | Barrier::make()创建了一个新的协程屏障use语法传递屏障,增加引用计数Barrier::wait($barrier),这时会自动挂起当前协程,等待引用该协程屏障的子协程退出$barrier对象的引用计数,直到为0$barrier对象引用计数为0,在$barrier对象析构函数中底层会自动恢复挂起的协程,从Barrier::wait($barrier)函数中返回
48 | Coroutine\Barrier 是一个比 WaitGroup 和 Channel 更易用的并发控制器,大幅提升了 PHP 并发编程的用户体验。Swoole\Coroutine\Server 与 异步风格 的服务端不同之处在于,Swoole\Coroutine\Server 是完全协程化实现的服务器,参考 完整例子。
$serv = new Swoole\Server("127.0.0.1", 9501);
23 | //监听连接进入事件
24 | $serv->on('Connect', function ($serv, $fd) {
25 | $redis = new Redis();
26 | $redis->connect("127.0.0.1",6379);//此处OnConnect的协程会挂起
27 | Co::sleep(5);//此处sleep模拟connect比较慢的情况
28 | $redis->set($fd,"fd $fd connected");
29 | });
30 | //监听数据接收事件
31 | $serv->on('Receive', function ($serv, $fd, $reactor_id, $data) {
32 | $redis = new Redis();
33 | $redis->connect("127.0.0.1",6379);//此处onReceive的协程会挂起
34 | var_dump($redis->get($fd));//有可能onReceive的协程的redis连接先建立好了,上面的set还没有执行,此处get会是false,产生逻辑错误
35 | });
36 | //监听连接关闭事件
37 | $serv->on('Close', function ($serv, $fd) {
38 | echo "Client: Close.\n";
39 | });
40 | //启动服务器
41 | $serv->start();
42 | 上述异步风格的服务器,无法保证事件的顺序,即无法保证onConnect执行结束后才进入onReceive,因为在开启协程化后,onConnect和onReceive回调都会自动创建协程,遇到IO会产生协程调度,异步风格的无法保证调度顺序,而协程风格的服务端没有这个问题。
start()被调用之后就什么也干不了了,而协程风格的可以动态开启关闭服务。reload功能需要自己监听信号来做逻辑。请将以下代码写入websocketServer.php。
20 |//创建WebSocket Server对象,监听0.0.0.0:9502端口。
21 | $ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
22 | //监听WebSocket连接打开事件。
23 | $ws->on('Open', function ($ws, $request) {
24 | $ws->push($request->fd, "hello, welcome\n");
25 | });
26 | //监听WebSocket消息事件。
27 | $ws->on('Message', function ($ws, $frame) {
28 | echo "Message: {$frame->data}\n";
29 | $ws->push($frame->fd, "server: {$frame->data}");
30 | });
31 | //监听WebSocket连接关闭事件。
32 | $ws->on('Close', function ($ws, $fd) {
33 | echo "client-{$fd} is closed\n";
34 | });
35 | $ws->start();
36 | onMessage事件回调。$server->push()向某个客户端(使用$fd标识符)发送消息。
39 |
40 | php websocketServer.php
42 | 可以使用Chrome浏览器进行测试,JS代码为:
43 |var wsServer = 'ws://127.0.0.1:9502';
44 | var websocket = new WebSocket(wsServer);
45 | websocket.onopen = function (evt) {
46 | console.log("Connected to WebSocket server.");
47 | };
48 | websocket.onclose = function (evt) {
49 | console.log("Disconnected");
50 | };
51 | websocket.onmessage = function (evt) {
52 | console.log('Retrieved data from server: ' + evt.data);
53 | };
54 | websocket.onerror = function (evt, e) {
55 | console.log('Error occured: ' + evt.data);
56 | };
57 |
58 | WebSocket服务器除了提供WebSocket功能之外,实际上也可以处理HTTP长连接。只需要增加onRequest事件监听即可实现Comet方案HTTP长轮询。 60 | !> 详细使用方法参考Swoole\WebSocket。
请将以下代码写入tcpServer.php。
20 |//创建Server对象,监听 127.0.0.1:9501 端口。
21 | $server = new Swoole\Server('127.0.0.1', 9501);
22 | //监听连接进入事件。
23 | $server->on('Connect', function ($server, $fd) {
24 | echo "Client: Connect.\n";
25 | });
26 | //监听数据接收事件。
27 | $server->on('Receive', function ($server, $fd, $reactor_id, $data) {
28 | $server->send($fd, "Server: {$data}");
29 | });
30 | //监听连接关闭事件。
31 | $server->on('Close', function ($server, $fd) {
32 | echo "Client: Close.\n";
33 | });
34 | //启动服务器
35 | $server->start();
36 | 这样就创建了一个TCP服务器,监听本机9501端口。它的逻辑很简单,当客户端Socket通过网络发送一个 hello 字符串时,服务器会回复一个 Server: hello 字符串。
37 | Server是异步服务器,所以是通过监听事件的方式来编写程序的。当对应的事件发生时底层会主动回调指定的函数。如当有新的TCP连接进入时会执行onConnect事件回调,当某个连接向服务器发送数据时会回调onReceive函数。
$fd就是客户端连接的唯一标识符。$server->send() 方法向客户端连接发送数据,参数就是$fd客户端标识符。$server->close() 方法可以强制关闭某个客户端连接。php tcpServer.php
46 | 在命令行下运行server.php程序,启动成功后可以使用 netstat 工具看到已经在监听9501端口。
47 | 这时就可以使用telnet/netcat工具连接服务器。
telnet 127.0.0.1 9501
49 | hello
50 | Server: hello
51 |
52 | Linux下,使用netstat -an | grep 端口,查看端口是否已经被打开处于Listening状态。127.0.0.1回环地址,则客户端只能使用127.0.0.1才能连接上。参考TCP数据包边界问题。
在Swoole4中可以使用Channel实现协程间的通信、依赖管理、协程同步。基于Channel可以很容易地实现Golang的sync.WaitGroup功能。
18 |
21 |78 | 79 | -------------------------------------------------------------------------------- /Swoole.docset/Contents/Resources/Documents/coroutine12multi_call.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |此功能是使用PHP编写的功能,并不是C/C++代码,实现源代码在 Library 当中
22 |23 |
77 |- 24 |
add方法增加计数- 25 |
done表示任务已完成- 26 |
wait等待所有任务完成恢复当前协程的执行- 76 |
WaitGroup对象可以复用,add、done、wait之后可以再次使用 27 | 28 |使用示例
29 |<?php 30 | use Swoole\Coroutine; 31 | use Swoole\Coroutine\WaitGroup; 32 | use Swoole\Coroutine\Http\Client; 33 | use function Swoole\Coroutine\run; 34 | run(function () { 35 | $wg = new WaitGroup(); 36 | $result = []; 37 | $wg->add(); 38 | //启动第一个协程 39 | Coroutine::create(function () use ($wg, &$result) { 40 | //启动一个协程客户端client,请求淘宝首页 41 | $cli = new Client('www.taobao.com', 443, true); 42 | $cli->setHeaders([ 43 | 'Host' => 'www.taobao.com', 44 | 'User-Agent' => 'Chrome/49.0.2587.3', 45 | 'Accept' => 'text/html,application/xhtml+xml,application/xml', 46 | 'Accept-Encoding' => 'gzip', 47 | ]); 48 | $cli->set(['timeout' => 1]); 49 | $cli->get('/index.php'); 50 | $result['taobao'] = $cli->body; 51 | $cli->close(); 52 | $wg->done(); 53 | }); 54 | $wg->add(); 55 | //启动第二个协程 56 | Coroutine::create(function () use ($wg, &$result) { 57 | //启动一个协程客户端client,请求百度首页 58 | $cli = new Client('www.baidu.com', 443, true); 59 | $cli->setHeaders([ 60 | 'Host' => 'www.baidu.com', 61 | 'User-Agent' => 'Chrome/49.0.2587.3', 62 | 'Accept' => 'text/html,application/xhtml+xml,application/xml', 63 | 'Accept-Encoding' => 'gzip', 64 | ]); 65 | $cli->set(['timeout' => 1]); 66 | $cli->get('/index.php'); 67 | $result['baidu'] = $cli->body; 68 | $cli->close(); 69 | $wg->done(); 70 | }); 71 | //挂起当前协程,等待所有任务完成后恢复 72 | $wg->wait(); 73 | //这里 $result 包含了 2 个任务执行结果 74 | var_dump($result); 75 | });
[//]: # (
18 | 此处删除了setDefer特性,因为支持setDefer的客户端都推荐用一键协程化了。
19 | )
20 | 使用子协程(go)+通道(channel)实现并发请求。
21 | !>建议先看概览,了解协程基本概念再看此节。
22 |
onRequest中需要并发两个HTTP请求,可使用go函数创建2个子协程,并发地请求多个URLchannel,使用use闭包引用语法,传递给子协程chan->pop,等待子协程完成任务,yield进入挂起状态chan->push将数据推送给主协程子协程完成URL请求后退出,主协程从挂起状态中恢复,继续向下执行调用$resp->end发送响应结果
31 |
$serv = new Swoole\Http\Server("127.0.0.1", 9503, SWOOLE_BASE);
34 | $serv->on('request', function ($req, $resp) {
35 | $chan = new Channel(2);
36 | go(function () use ($chan) {
37 | $cli = new Swoole\Coroutine\Http\Client('www.qq.com', 80);
38 | $cli->set(['timeout' => 10]);
39 | $cli->setHeaders([
40 | 'Host' => "www.qq.com",
41 | "User-Agent" => 'Chrome/49.0.2587.3',
42 | 'Accept' => 'text/html,application/xhtml+xml,application/xml',
43 | 'Accept-Encoding' => 'gzip',
44 | ]);
45 | $ret = $cli->get('/');
46 | $chan->push(['www.qq.com' => $cli->body]);
47 | });
48 | go(function () use ($chan) {
49 | $cli = new Swoole\Coroutine\Http\Client('www.163.com', 80);
50 | $cli->set(['timeout' => 10]);
51 | $cli->setHeaders([
52 | 'Host' => "www.163.com",
53 | "User-Agent" => 'Chrome/49.0.2587.3',
54 | 'Accept' => 'text/html,application/xhtml+xml,application/xml',
55 | 'Accept-Encoding' => 'gzip',
56 | ]);
57 | $ret = $cli->get('/');
58 | $chan->push(['www.163.com' => $cli->body]);
59 | });
60 |
61 | $result = [];
62 | for ($i = 0; $i < 2; $i++)
63 | {
64 | $result += $chan->pop();
65 | }
66 | $resp->end(json_encode($result));
67 | });
68 | $serv->start();
69 | !> 使用Swoole提供的WaitGroup功能,将更简单一些。
方便学习Swoole,此章节整理并汇总Swoole系列文章。 18 |
19 |Swoole 在 v4 版本后内置了 Library 模块,使用 PHP 代码编写内核功能,使得底层设施更加稳定可靠
18 | !> 该模块也可通过 composer 单独安装,单独安装使用时需要通过php.ini配置swoole.enable_library=Off关闭扩展内置的 library
19 | 目前提供了以下工具组件:
请将以下代码写入httpServer.php。
20 |$http = new Swoole\Http\Server('0.0.0.0', 9501);
21 | $http->on('Request', function ($request, $response) {
22 | $response->header('Content-Type', 'text/html; charset=utf-8');
23 | $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');
24 | });
25 | $http->start();
26 | HTTP服务器只需要关注请求响应即可,所以只需要监听一个onRequest事件。当有新的HTTP请求进入就会触发此事件。事件回调函数有2个参数,一个是$request对象,包含了请求的相关信息,如GET/POST请求的数据。
27 | 另外一个是response对象,对request的响应可以通过操作response对象来完成。$response->end()方法表示输出一段HTML内容,并结束此请求。
0.0.0.0 表示监听所有IP地址,一台服务器可能同时有多个IP,如127.0.0.1本地回环IP、192.168.1.100局域网IP、210.127.20.2 外网IP,这里也可以单独指定监听一个IP9501 监听的端口,如果被占用程序会抛出致命错误,中断执行。
31 |
32 | php httpServer.phphttp://127.0.0.1:9501查看程序的结果。ab工具对服务器进行压力测试。
36 |
37 | 使用Chrome浏览器访问服务器,会产生额外的一次请求,/favicon.ico,可以在代码中响应404错误。
$http->on('Request', function ($request, $response) {
40 | if ($request->server['path_info'] == '/favicon.ico' || $request->server['request_uri'] == '/favicon.ico') {
41 | $response->end();
42 | return;
43 | }
44 | var_dump($request->get, $request->post);
45 | $response->header('Content-Type', 'text/html; charset=utf-8');
46 | $response->end('<h1>Hello Swoole. #' . rand(1000, 9999) . '</h1>');
47 | });
48 |
49 | 应用程序可以根据$request->server['request_uri']实现路由。如:http://127.0.0.1:9501/test/index/?a=1,代码中可以这样实现URL路由。
$http->on('Request', function ($request, $response) {
52 | list($controller, $action) = explode('/', trim($request->server['request_uri'], '/'));
53 | //根据 $controller, $action 映射到不同的控制器类和方法。
54 | (new $controller)->$action($request, $response);
55 | });!> 本页面由 Swoole 开源项目创始人 Rango 编写,仅代表其个人观点。 18 |
19 |Swoole 项目最初的想法是来自于之前所做的一个企业软件项目。当时大概是2010年底,公司产品有一个需求是用户可以任意生成一个 email 地址,然后其他用户可以向这个email发邮件,后台能实时将邮件内容解析成数据,并主动通知用户。当时项目使用PHP开发的,在实现这个需求时遇到了难题,PHP只能依赖其他的SMTP服务器,通过pop3协议定时查收新邮件来完成,这样就不是实时的。如果要实现的实时系统必须自己写一个TCP Socket Server实现SMTP协议接收数据。当时PHP在这个领域几乎是空白,没有一套成熟的网络通信框架。为了实现需求,我从socket学起到TCP/IP、IO复用、libevent、多进程,最后终于实现了这套程序。做完这个项目后我就想把这套程序开源出来,希望能帮助其他PHPer解决在这个领域的难题。如果能有这样一个框架,那么PHP就能从单纯地做一个Web网站延伸到更大的空间。
21 |
还有一个重要的原因是PHP程序的性能问题,我最早是学Java出身的,工作后才转行成为一名PHP程序员。在使用PHP开发程序的过程中,我一直在思考的问题 PHP 和 Java 比最大的优势是什么?简单高效, PHP 在请求完成之后会释放所有资源和内存,无须担心内存泄漏。代码的质量无论高低一样运行的很流畅。但同时这也是 PHP 致命的缺点。一旦请求数量上升,并发很高的时候,快速创建资源,又马上释放,使得 PHP 程序运行效率急剧下降。另外一旦项目的功能的越来越复杂,代码增多后,对于 PHP 也会是灾难。这也是 PHP 的框架为什么没有被 PHP 程序员广泛接受,而 Java 不存在这个问题。再好的框架也会被这种低效的方式拖累,导致系统变慢。所以想到了使用 PHP 来开发 PHP 的应用服务器,让 PHP 的代码加载到内存后,拥有更长的生命周期,这样建立的数据库连接和其他大的对象,不被释放。每次请求只需要处理很少的代码,而这些代码只在第一次运行时,被 PHP 解析器编译,驻留内存。另外,之前 PHP 不能实现的,对象持久化、数据库连接池,缓存连接池都可以实现。系统的运行效率会大大提高。 24 | 经过一段时间研究,目前已经初步得到实现。使用 PHP 本身编写出 HTTP 服务器,以独立服务器方式运行,单个程序页面 ( 有对象生成,数据库连接、 smarty 模板操作 ) 的执行时间由原来的 0.0x 秒,下降到 0.00x 秒。使用 Apache AB 并发 100 测试。比传统 LAMP 方式, Request per Second 高出至少 10 倍。在我的测试机上 (Ubuntu10.04 Inter Core E5300 + 2G 内存 ) , Apache 只跑到 83RPS 。 Swoole Server 可以跑到 1150 多 RPS。 25 | 这个项目就是Swoole的雏形。这个版本一直持续维护了2年多,在这个过程中逐步有了一些经验积累,对这套技术方案的存在问题有了更深入的理解,比如性能差、限制较多无法直接调用操作系统接口、内存管理效率低下。 26 |
27 |2011年底我入职腾讯,负责朋友网的PHP平台开发工作。惊奇地发现朋友网的同事不光这样想了,他们直接做到了。朋友网团队已经在生产环境中使用了这套方案。朋友网有三架马车,第一个是PWS,这是一个纯PHP编写的WebServer,朋友网线上有600多台服务器运行在PWS上,完全没有使用Apache、PHP-FPM之类的程序。第二个是SAPS,这是使用纯PHP开发的一个分布式队列,当时大概由150台服务器的集群在跑,很多图片裁剪、头像处理、消息同时、数据同步等逻辑全部使用了SAPS做逻辑异步化。第三个是PSF,这是一个PHP实现的Server框架,朋友网很多逻辑层的服务器都是基于PSF实现的。大概有300台左右的集群在运行PSF服务器程序。在朋友网的这段时间,我学到了很多Linux底层、网络通信的知识,积累了很多大型集群高并发环境的网络通信跟踪、调试经验,为开发Swoole打下了一个很好的基础。 29 |
30 |在这期间也学习了解到了Node.js、Golang这些优秀的技术方案,得到了更多灵感。在2012年的时候就有了新的想法,决定使用C语言重新实现一个性能更强、功能更强大的版本。这就是现在的Swoole扩展。 32 | 现在Swoole已经被很多PHP技术团队用于实际项目的开发工作,国内国外都有。国内知名的有百度订单中心、百度地图、腾讯QQ公众号和企业QQ、战旗直播、360、当当网、穷游等。另外还有很多物联网、硬件、游戏项目也在使用Swoole 。另外基于Swoole的开源框架也越来越多,比如TSF、Blink、swPromise 等等,在GitHub上也能找到很多Swoole相关的项目和代码。 33 |
34 |Swoole这个名字不是一个英文单词,是由我创造的一个音近字。我最早想到的名字是叫做sword-server,寓意是为广大PHPer创造一把锋利的剑,后来联想到google也是凭空创造出来的,所以我就给它命名为swoole。
当你觉得发现了一个Swoole内核的BUG时,请提出报告。Swoole的内核开发者们或许还不知道问题的存在,除非你主动提出报告,否则BUG也许将很难被发现并修复,你可以在 GitHub的issue区 提出错误报告(即点击右上角绿色的New issue按钮),这里的错误报告将会被最优先解决。
20 | 请不要在邮件列表或私人信件中发送错误报告,GitHub的issue区同样可以提出对于Swoole的任何要求与建议。
21 | 在你提交错误报告之前,请先阅读以下的如何提交错误报告。
22 |
首先在创建issue的同时,系统将会给出如下模板,请你认真填写它,否则issue由于缺乏信息可能会被忽略:
25 |Please answer these questions before submitting your issue. Thanks!
26 | > 在提交Issue前请回答以下问题:
27 |
28 | 1. What did you do? If possible,provide a simple script for reproducing the error.
29 | > 请详细描述问题的产生过程,贴出相关的代码,最好能提供一份可稳定重现的简单脚本代码。
30 | 2. What did you expect to see?
31 | > 期望的结果是什么?
32 | 3. What did you see instead?
33 | > 实际运行的结果是什么?
34 | 4. What version of Swoole are you using (`php --ri swoole`)?
35 | > 你的版本? 贴出 `php --ri swoole` 所打印的内容
36 | 5. What is your machine environment used (including the version of kernel & php & gcc)?
37 | > 你使用的机器系统环境是什么(包括内核、PHP、gcc编译器版本信息)?
38 | > 可以使用`uname -a`,`php -v`,`gcc -v` 命令打印
39 | 其中,最为关键的是提供可稳定重现的简单脚本代码,否则你必须提供尽可能多的其它信息来帮助开发者判断错误原因 40 |
41 |更多时候,Valgrind比gdb更能发现内存问题,通过以下指令运行你的程序,直到触发BUG
43 |USE_ZEND_ALLOC=0 valgrind --log-file=/tmp/valgrind.log php your_file.php
44 | ctrl+c 退出,然后上传 /tmp/valgrind.log 文件以便于开发组定位BUG。
46 |
47 | 此外,在一种特殊情况下你可以使用调试工具来帮助开发者定位问题
49 |WARNING swManager_check_exit_status: worker#1 abnormal exit, status=0, signal=11
50 | 当如上提示出现在Swoole日志中(signal11),说明程序发生了核心转储,你需要使用跟踪调试工具来确定其发生位置
52 |使用
54 |gdb来跟踪swoole前,需要在编译时添加--enable-debug参数以保留更多信息 53 | 开启核心转储文件55 |ulimit -c unlimited触发BUG,核心转储文件会生成在 程序目录 或 系统根目录 或
57 |/cores目录下 (取决于你的系统配置) 56 | 键入以下命令进入gdb调试程序59 |gdb php core 58 | gdb php /tmp/core.1234紧接着输入
60 |bt并回车,就可以看到出现问题的调用栈61 |(gdb) bt可以通过键入
62 |f 数字来查看指定的调用栈帧64 |(gdb) f 1 63 | (gdb) f 0将以上信息都贴在issue中
65 |
| 分支 | 21 |PHP 版本 | 22 |开始时间 | 23 |积极支持截止时间 | 24 |安全维护截止时间 | 25 |
|---|---|---|---|---|
| v4.8.x | 30 |7.2 - 8.2 | 31 |2021-10-14 | 32 |2023-10-14 | 33 |2024-06-30 | 34 |
| v5.0.x | 37 |8.0 - 8.2 | 38 |2022-01-20 | 39 |2023-01-20 | 40 |2023-07-20 | 41 |
| v5.1.x | 44 |8.0 - 8.2 | 45 |2023-09-30 | 46 |2025-09-30 | 47 |2026-09-30 | 48 |
| v6.0.x | 51 |8.1 - 8.3 | 52 |2024-06-23 | 53 |2026-06-23 | 54 |2027-06-23 | 55 |
| 积极支持 | 58 |受到官方开发组的积极支持,已报告的错误和安全问题将会立即被修复,并按照常规流程发布正式的版本。 | 59 ||||
| -------- | 62 |---------------------------------------------------------------------------------------------- | 63 ||||
| 安全维护 | 66 |仅支持关键安全问题的修复,且仅在必要时发布正式的版本 | 67 |
!> 这些版本不再为官方所支持,仍在使用以下版本的用户应尽快升级,因为他们可能会遇到未修补的安全漏洞。
73 |v1.x (2012-7-1 ~ 2018-05-14)v2.x (2016-12-30 ~ 2018-05-23)v3.x (废弃)v4.0.x, v4.1.x, v4.2.x, v4.3.x (2018-06-14 ~ 2019-12-31)v4.4.x(2019-04-15 ~ 2020-04-30)v4.5.x (2019-12-20 ~ 2021-01-06)v4.6.x, v4.7.x(2021-01-06 ~ 2021-12-31)
81 |
82 | v1.x:异步回调模式。v2.x:基于setjmp/longjmp实现的单栈协程,底层仍然是异步回调实现,在回调事件触发后切换PHP调用栈。v4.0-v4.3:基于boost context asm实现的双栈协程,内核实现了全面的协程化,实现了基于EventLoop的协程调度器。v4.4-v4.8:实现了runtime coroutine hook,自动将PHP内置的同步阻塞函数替换为协程的异步非阻塞模式,使得Swoole协程可以兼容绝大部分PHP库。v5.0:全面协程化,移除非协程模块;强类型化,移除了大量历史包袱;提供全新的swoole-cli运行模式。v5.1:支持pdo_pgsql,pdo_oci,pdo_odbc,pdo_sqlite的协程化,增强Http\Server的性能。v6.0:支持多线程模式。通过设置open_mqtt_protocol选项,启用后会解析MQTT包头,Worker 进程的onReceive事件每次会返回一个完整的MQTT数据包。
18 | 可以使用 Swoole 作为 MQTT 服务端或客户端,实现一套完整物联网(IOT)解决方案。
20 |89 | 90 | -------------------------------------------------------------------------------- /Swoole.docset/Contents/Resources/Documents/coroutine12gdb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |完整的 MQTT 协议解析和协程客户端可以使用 simps/mqtt 21 |
22 |程序代码
23 |请将以下代码写入mqttServer.php
24 |88 |function decodeValue($data) 25 | { 26 | return 256 * ord($data[0]) + ord($data[1]); 27 | } 28 | function decodeString($data) 29 | { 30 | $length = decodeValue($data); 31 | return substr($data, 2, $length); 32 | } 33 | function mqttGetHeader($data) 34 | { 35 | $byte = ord($data[0]); 36 | $header['type'] = ($byte & 0xF0) >> 4; 37 | $header['dup'] = ($byte & 0x08) >> 3; 38 | $header['qos'] = ($byte & 0x06) >> 1; 39 | $header['retain'] = $byte & 0x01; 40 | return $header; 41 | } 42 | function eventConnect($header, $data) 43 | { 44 | $connect_info['protocol_name'] = decodeString($data); 45 | $offset = strlen($connect_info['protocol_name']) + 2; 46 | $connect_info['version'] = ord(substr($data, $offset, 1)); 47 | $offset += 1; 48 | $byte = ord($data[$offset]); 49 | $connect_info['willRetain'] = ($byte & 0x20 == 0x20); 50 | $connect_info['willQos'] = ($byte & 0x18 >> 3); 51 | $connect_info['willFlag'] = ($byte & 0x04 == 0x04); 52 | $connect_info['cleanStart'] = ($byte & 0x02 == 0x02); 53 | $offset += 1; 54 | $connect_info['keepalive'] = decodeValue(substr($data, $offset, 2)); 55 | $offset += 2; 56 | $connect_info['clientId'] = decodeString(substr($data, $offset)); 57 | return $connect_info; 58 | } 59 | $server = new Swoole\Server('127.0.0.1', 9501, SWOOLE_BASE); 60 | $server->set([ 61 | 'open_mqtt_protocol' => true, // 启用 MQTT 协议 62 | 'worker_num' => 1, 63 | ]); 64 | $server->on('Connect', function ($server, $fd) { 65 | echo "Client:Connect.\n"; 66 | }); 67 | $server->on('Receive', function ($server, $fd, $reactor_id, $data) { 68 | $header = mqttGetHeader($data); 69 | var_dump($header); 70 | if ($header['type'] == 1) { 71 | $resp = chr(32) . chr(2) . chr(0) . chr(0); 72 | eventConnect($header, substr($data, 2)); 73 | $server->send($fd, $resp); 74 | } elseif ($header['type'] == 3) { 75 | $offset = 2; 76 | $topic = decodeString(substr($data, $offset)); 77 | $offset += strlen($topic) + 2; 78 | $msg = substr($data, $offset); 79 | echo "client msg: {$topic}\n----------\n{$msg}\n"; 80 | //file_put_contents(__DIR__.'/data.log', $data); 81 | } 82 | echo "received length=" . strlen($data) . "\n"; 83 | }); 84 | $server->on('Close', function ($server, $fd) { 85 | echo "Client: Close.\n"; 86 | }); 87 | $server->start();
使用Swoole协程时,可以使用下面的方法进行调试
18 |
gdb php test.php
23 |
24 | (gdb) source /path/to/swoole-src/gdbinit
26 |
27 | 例如 co::sleep 函数
(gdb) b zim_swoole_coroutine_util_sleep
30 |
31 | (gdb) co_list
33 | coroutine 1 SW_CORO_YIELD
34 | coroutine 2 SW_CORO_RUNNING
35 |
36 | (gdb) co_bt
38 | coroutine cid:[2]
39 | [0x7ffff148a100] Swoole\Coroutine->sleep(0.500000) [internal function]
40 | [0x7ffff148a0a0] {closure}() /home/shiguangqi/php/swoole-src/examples/coroutine/exception/test.php:7
41 | [0x7ffff141e0c0] go(object[0x7ffff141e110]) [internal function]
42 | [0x7ffff141e030] (main) /home/shiguangqi/php/swoole-src/examples/coroutine/exception/test.php:10
43 |
44 | (gdb) co_bt 1
46 | [0x7ffff1487100] Swoole\Coroutine->sleep(0.500000) [internal function]
47 | [0x7ffff14870a0] {closure}() /home/shiguangqi/php/swoole-src/examples/coroutine/exception/test.php:3
48 | [0x7ffff141e0c0] go(object[0x7ffff141e110]) [internal function]
49 | [0x7ffff141e030] (main) /home/shiguangqi/php/swoole-src/examples/coroutine/exception/test.php:10
50 |
51 | (gdb) co_status
53 | stack_size: 2097152
54 | call_stack_size: 1
55 | active: 1
56 | coro_num: 2
57 | max_coro_num: 3000
58 | peak_coro_num: 2
59 |
60 | 遍历当前进程内的所有协程,并打印调用栈。
62 |Swoole\Coroutine::listCoroutines(): Swoole\Coroitine\Iterator
63 | !> 需要4.1.0或更高版本
foreach遍历,或使用iterator_to_array转为数组
66 | use Swoole\Coroutine;
67 | $coros = Coroutine::listCoroutines();
68 | foreach($coros as $cid)
69 | {
70 | var_dump(Coroutine::getBackTrace($cid));
71 | }
72 | 可以参考 Swoole微课程中的视频教程
网络编程中经常使用gethostbyname和getaddrinfo来实现域名解析,这两个C函数并未提供超时参数。实际上可以修改/etc/resolv.conf来设置超时和重试逻辑。
20 | !> 可参考man resolv.conf文档
21 |
nameserver 192.168.1.3
24 | nameserver 192.168.1.5
25 | option rotate
26 | 可配置多个nameserver,底层会自动轮询,在第一个nameserver查询失败时会自动切换为第二个nameserver进行重试。
27 | option rotate配置的作用是,进行nameserver负载均衡,使用轮询模式。
28 |
option timeout:1 attempts:2
31 | timeout:控制UDP接收的超时时间,单位为秒,默认为5秒attempts:控制尝试的次数,配置为2时表示,最多尝试2次,默认为5次
34 | 假设有2个nameserver,attempts为2,超时为1,那么如果所有DNS服务器无响应的情况下,最长等待时间为4秒(2x2x1)。
35 |
36 | 可使用strace跟踪确认。
38 | 将nameserver设置为两个不存在的IP,PHP代码使用var_dump(gethostbyname('www.baidu.com'));解析域名。
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
40 | connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.20.128.16")}, 16) = 0
41 | poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
42 | sendto(3, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
43 | poll([{fd=3, events=POLLIN}], 1, 1000
44 | ) = 0 (Timeout)
45 | socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 4
46 | connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.20.128.18")}, 16) = 0
47 | poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
48 | sendto(4, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
49 | poll([{fd=4, events=POLLIN}], 1, 1000
50 | ) = 0 (Timeout)
51 | poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
52 | sendto(3, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
53 | poll([{fd=3, events=POLLIN}], 1, 1000
54 | ) = 0 (Timeout)
55 | poll([{fd=4, events=POLLOUT}], 1, 0) = 1 ([{fd=4, revents=POLLOUT}])
56 | sendto(4, "\346\5\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
57 | poll([{fd=4, events=POLLIN}], 1, 1000
58 | ) = 0 (Timeout)
59 | close(3) = 0
60 | close(4) = 0
61 | 可以看到这里一共重试了4次,poll调用超时设置为1000ms(1秒)。
如果您在使用Swoole,欢迎您将信息提交至此列表,让更多的用户了解Swoole的实际使用场景,以建立更好的Swoole生态。 18 |
19 |在swoole-inc/report提交一个issue,包括以下信息:
| 名称 | 33 |地点 | 34 |联系方式 | 35 |业务场景 | 36 |
|---|---|---|---|
| 腾讯服务增值部 | 41 |中国上海漕河泾 | 42 |- | 43 |微服务、长连接 | 44 |
| 心动网络股份有限公司 | 47 |中国上海市静安区万荣路A2楼 | 48 |jonwang@xindong.com | 49 |游戏及App应用的管理后台、微服务管理系统及游戏内商城系统 | 50 |
| 好未来集团 学而思网校 | 53 |中国北京 | 54 |hantianfeng@100tal.com | 55 |WebAPI、微服务、中间件、基础架构 | 56 |
| 京东 | 59 |中国上海 | 60 |rsshuai74269@163.com | 61 |WebAPI、WebSocket | 62 |
| 神州顺利办 | 65 |中国北京 | 66 |yydick@sohu.com | 67 |RPC微服务 | 68 |
| 爱宠医生 | 71 |中国上海 | 72 |zhangwj@5ichong.com | 73 |WebAPI | 74 |
| KK 集团 | 77 |中国深圳东莞 | 78 |h@hyperf.io | 79 |Web API、微服务、基础架构、中间件 | 80 |
| Knowyourself | 83 |中国上海 | 84 |l@hyperf.io | 85 |Web API、微服务 | 86 |
| 萌推 | 89 |中国上海 | 90 |d@hyperf.io | 91 |电商、微服务 | 92 |
| Glu Mobile | 95 |875 Howard St #100, San Francisco, CA (美国旧金山) | 96 |demin.yin@glu.com | 97 |手机游戏的微服务、数据处理后台等 | 98 |
| 百度基础架构部 | 101 |中国北京 | 102 |王新华 | 103 |利用Swoole打造高性能端APP性能数据收集服务,目前服务支撑手机百度、好看视频、百度贴吧、百度网盘等百度各大产品线,高峰期qps: 50万 | 104 |
| 二三四五 | 107 |中国上海 | 108 |gaox@2345.com | 109 |基于Swoole的HTTP、TCP异步客户端,应用于PC端业务的实时数据推送,用户在线超过千万,推送速率达到10W条/秒 | 110 |
| 剑的传说 | 113 |中国北京 | 114 |微信:649947921 | 115 |微信小游戏"剑的传说",大型RPG角色扮演游戏服务端 | 116 |
| 甲乙丙丁(北京)电子商务股份有限公司 | 119 |中国北京 | 120 |sibowen@jybdshop.cn | 121 |商城微服务、crm webapi | 122 |
| 建材猫 | 125 |中国广东佛山 | 126 |381345509@qq.com | 127 |推送服务,NLP分词,短视频与直播 API,Swoole-ElasticSearch-Sql 商品搜索 | 128 |
这里是对Swoole\Server\Port的详细介绍。
18 |
返回监听的主机地址,该属性是一个string类型的字符串。
Swoole\Server\Port->host
24 |
25 | 返回监听的主机端口,该属性是一个int类型的整数。
Swoole\Server\Port->port
28 |
29 | 返回这组server类型。该属性是一个枚举,返回SWOOLE_TCP,SWOOLE_TCP6,SWOOLE_UDP,SWOOLE_UDP6,SWOOLE_UNIX_DGRAM,SWOOLE_UNIX_STREAM其中一个。
Swoole\Server\Port->type
32 |
33 | 返回监听的套接字,该属性是一个int类型的整数。
Swoole\Server\Port->sock
36 |
37 | 返回是否开启ssl加密,该属性是一个bool类型。
Swoole\Server\Port->ssl
40 |
41 | 返回对该端口的设置,该属性是一个array的数组。
Swoole\Server\Port->setting
44 |
45 | 返回连接该端口的全部连接,该属性是一个迭代器。
47 |Swoole\Server\Port->connections
48 |
49 | 用于设置Swoole\Server\Port运行时的各项参数,使用方式与Swoole\Server->set()一样。
Swoole\Server\Port->set(array $setting): void
54 |
55 | 用于设置Swoole\Server\Port回调函数,使用方式与Swoole\Server->on()一样。
Swoole\Server\Port->on(string $event, callable $callback): bool
58 |
59 | 返回设置的回调函数。
61 |Swoole\Server\Port->getCallback(string $name): ?callback
62 | string $name
66 | Socket对象表示操作成功,返回false表示操作失败。
83 | !> 注意,只有在编译Swoole的过程中开启了--enable-sockets,该函数才能使用。Swoole 是一个使用 C++ 语言编写的基于异步事件驱动和协程的并行网络通信引擎,为 PHP 提供协程、高性能网络编程支持。提供了多种通信协议的网络服务器和客户端模块,可以方便快速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通讯、游戏、微服务等,使 PHP 不再局限于传统的 Web 领域。
可以直接点击链接到对应的文档页
ulimit -n 要调整为 100000 甚至更大。 命令行下执行 ulimit -n 100000 即可修改。如果不能修改,需要设置 /etc/security/limits.conf,加入
* soft nofile 262140
21 | * hard nofile 262140
22 | root soft nofile 262140
23 | root hard nofile 262140
24 | * soft core unlimited
25 | * hard core unlimited
26 | root soft core unlimited
27 | root hard core unlimited
28 | 注意,修改limits.conf文件后,需要重启系统生效
29 |
Linux操作系统修改内核参数有 3 种方式:
/etc/sysctl.conf文件,加入配置选项,格式为key = value,修改保存后调用sysctl -p加载新配置sysctl命令临时修改,如:sysctl -w net.ipv4.tcp_mem="379008 505344 758016"/proc/sys/目录中的文件,如:echo "379008 505344 758016" > /proc/sys/net/ipv4/tcp_mem
36 | 37 |第一种方式在操作系统重启后会自动生效,第二和第三种方法重启后失效 38 |
39 |net.unix.max_dgram_qlen = 100
40 |swoole 使用 unix socket dgram 来做进程间通信,如果请求量很大,需要调整此参数。系统默认为 10,可以设置为 100 或者更大。或者增加 worker 进程的数量,减少单个 worker 进程分配的请求量。 41 |
42 |net.core.wmem_max
43 |修改此参数增加 socket 缓存区的内存大小
44 |51 | 52 |net.ipv4.tcp_mem = 379008 505344 758016 45 | net.ipv4.tcp_wmem = 4096 16384 4194304 46 | net.ipv4.tcp_rmem = 4096 87380 4194304 47 | net.core.wmem_default = 8388608 48 | net.core.rmem_default = 8388608 49 | net.core.rmem_max = 16777216 50 | net.core.wmem_max = 16777216net.ipv4.tcp_tw_reuse
53 |是否 socket reuse,此函数的作用是 Server 重启时可以快速重新使用监听的端口。如果没有设置此参数,会导致 server 重启时发生端口未及时释放而启动失败 54 |
55 |net.ipv4.tcp_tw_recycle
56 |使用 socket 快速回收,短连接 Server 需要开启此参数。此参数表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收,Linux 系统中默认为 0,表示关闭。打开此参数可能会造成 NAT 用户连接不稳定,请谨慎测试后再开启。 57 |
58 |消息队列设置
59 |当使用消息队列作为进程间通信方式时,需要调整此内核参数
60 |
设置内核参数
72 |kernel.core_pattern = /data/core_files/core-%e-%p-%t
73 | 通过 ulimit -c 命令查看当前 coredump 文件的限制
74 |ulimit -c
75 | 如果为 0,需要修改/etc/security/limits.conf,进行 limit 设置。
76 |77 |开启 core-dump 后,一旦程序发生异常,会将进程导出到文件。对于调查程序问题有很大的帮助 78 |
79 |其他重要配置
80 |
如:修改 net.unix.max_dgram_qlen = 100 后,通过
cat /proc/sys/net/unix/max_dgram_qlen
96 | 如果修改成功,这里是新设置的值。
本节介绍一些协程基本概念和常见问题,也可以通过 Swoole视频教程 观看。
18 | 从4.0版本开始Swoole提供了完整的协程(Coroutine)+ 通道(Channel)特性,带来全新的CSP编程模型。
PHP层协程框架,开发者不需要使用yield关键词来标识一个协程IO操作,所以不再需要对yield的语义进行深入理解以及对每一级的调用都修改为yield,这极大的提高了开发效率提供了各种类型完善的协程客户端,可以满足大部分开发者的需求。 24 |
25 |协程可以简单理解为线程,只不过这个线程是用户态的,不需要操作系统参与,创建销毁和切换的成本非常低,和线程不同的是协程没法利用多核CPU的,想利用多核CPU需要依赖Swoole的多进程模型。
27 |
Channel可以理解为消息队列,只不过是协程间的消息队列,多个协程通过push和pop操作队列中的生产消息和消费消息,用来发送或者接收数据进行协程之间的通讯。需要注意的是Channel是没法跨进程的,只能一个Swoole进程里的协程间通讯,最典型的应用是连接池和并发调用。
30 |
使用Coroutine::create或go()方法创建协程(参考别名小节),在创建的协程中才能使用协程API,而协程必须创建在协程容器里面,参考协程容器。
33 |
这里将尽量通俗的讲述什么是协程调度,首先每个协程可以简单的理解为一个线程,大家知道多线程是为了提高程序的并发,同样的多协程也是为了提高并发。
36 | 用户的每个请求都会创建一个协程,请求结束后协程结束,如果同时有成千上万的并发请求,某一时刻某个进程内部会存在成千上万的协程,那么CPU资源是有限的,到底执行哪个协程的代码?
37 | 决定到底让CPU执行哪个协程的代码的决断过程就是协程调度,Swoole的调度策略又是怎么样的呢?
首先,在执行某个协程代码的过程中发现这行代码遇到了Co::sleep()或者产生了网络IO,例如MySQL->query(),这肯定是一个耗时的过程,Swoole就会把这个MySQL连接的Fd放到EventLoop中。
然后让出这个协程的CPU给其他协程使用:即yield(挂起)
等待MySQL数据返回后再继续执行这个协程:即resume(恢复)
其次,如果协程的代码有CPU密集型代码,可以开启enable_preemptive_scheduler,Swoole会强行让这个协程让出CPU。 50 |
51 |优先执行子协程(即go()里面的逻辑),直到发生协程yield(Co::sleep()处),然后协程调度到外层协程。
use Swoole\Coroutine;
54 | use function Swoole\Coroutine\run;
55 | echo "main start\n";
56 | run(function () {
57 | echo "coro " . Coroutine::getcid() . " start\n";
58 | Coroutine::create(function () {
59 | echo "coro " . Coroutine::getcid() . " start\n";
60 | Coroutine::sleep(.2);
61 | echo "coro " . Coroutine::getcid() . " end\n";
62 | });
63 | echo "coro " . Coroutine::getcid() . " do not wait children coroutine\n";
64 | Coroutine::sleep(.1);
65 | echo "coro " . Coroutine::getcid() . " end\n";
66 | });
67 | echo "end\n";
68 | /*
69 | main start
70 | coro 1 start
71 | coro 2 start
72 | coro 1 do not wait children coroutine
73 | coro 1 end
74 | coro 2 end
75 | end
76 | */
77 | 协程使得原有的异步逻辑同步化,但是在协程间的切换是隐式发生的,所以在协程切换的前后不能保证全局变量以及static变量的一致性。
87 | 在 PHP-FPM 下可以通过全局变量获取到的请求参数,服务器的参数等,在 Swoole 内,无法 通过 $_GET/$_POST/$_REQUEST/$_SESSION/$_COOKIE/$_SERVER等以$_开头的变量获取到任何属性参数。
88 | 可以使用context用协程id做隔离,实现全局变量的隔离。
89 |
下列协程客户端是Swoole内置的类,其中标有 ⚠️ 标志的不推荐再继续使用,可以使用PHP原生的函数+一键协程化。
18 |所有的网络请求(建立连接,发送数据,接收数据)都有可能超时,Swoole协程客户端设置超时的方式有三种:
2、3设置)。Swoole协程客户端类的set()或setOption()方法设置超时,例如:
35 | $client = new Co\Client(SWOOLE_SOCK_TCP);
36 | //或
37 | $client = new Co\Http\Client("127.0.0.1", 80);
38 | //或
39 | $client = new Co\Http2\Client("127.0.0.1", 443, true);
40 | $client->set(array(
41 | 'timeout' => 0.5,//总超时,包括连接、发送、接收所有超时
42 | 'connect_timeout' => 1.0,//连接超时,会覆盖第一个总的 timeout
43 | 'write_timeout' => 10.0,//发送超时,会覆盖第一个总的 timeout
44 | 'read_timeout' => 0.5,//接收超时,会覆盖第一个总的 timeout
45 | ));
46 | //Co\Redis() 没有 write_timeout 和 read_timeout 配置
47 | $client = new Co\Redis();
48 | $client->setOption(array(
49 | 'timeout' => 1.0,//总超时,包括连接、发送、接收所有超时
50 | 'connect_timeout' => 0.5,//连接超时,会覆盖第一个总的 timeout
51 | ));
52 | //Co\MySQL() 没有 set 配置的功能
53 | $client = new Co\MySQL();
54 | //Co\Socket 通过 setOption 配置
55 | $socket = new Co\Socket(AF_INET, SOCK_STREAM, SOL_TCP);
56 | $timeout = array('sec'=>1, 'usec'=>500000);
57 | $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, $timeout);//接受数据超时时间
58 | $socket->setOption(SOL_SOCKET, SO_SNDTIMEO, $timeout);//连接超时和发送数据超时的配置
59 | !> 这种方式的影响只针对当前类生效,会被第1种方式覆盖,无视下面的第3种方式配置。
2种方式超时设置规则很麻烦且不统一,为了避免开发者需要处处谨慎设置,从v4.2.10版本开始所有协程客户端提供了全局统一超时规则设置,这种影响最大,优先级最低,如下:
61 | Co::set([
62 | 'socket_timeout' => 5,
63 | 'socket_connect_timeout' => 1,
64 | 'socket_read_timeout' => 1,
65 | 'socket_write_timeout' => 1,
66 | ]);-1:表示永不超时0:表示不更改超时时间其它大于0的值:表示设置相应秒数的超时定时器,最大精度为1毫秒,是浮点型,0.5代表500毫秒socket_connect_timeout:表示建立TCP连接超时时间,默认为1秒 ,从v4.5.x版本开始默认为2秒socket_timeout:表示TCP读/写操作超时时间,默认为-1 ,从v4.5.x版本开始默认为60秒 。如果想把读和写分开设置,参考下面的配置socket_read_timeout:v4.3版本加入,表示TCP读操作超时时间,默认为-1 ,从v4.5.x版本开始默认为60秒socket_write_timeout:v4.3版本加入,表示TCP写操作超时时间,默认为-1 ,从v4.5.x版本开始默认为60秒
75 | !> 即: v4.5.x之前的版本所有Swoole提供的协程客户端,如果没用前面的第1、2种方式设置超时,默认连接超时时间为1s,读/写操作则永不超时;v4.5.x版本开始默认连接超时时间为60秒,读/写操作超时时间为60秒;除了上述Swoole提供的协程客户端,在一键协程化里面使用的是原生PHP提供的方法,它们的超时时间受 default_socket_timeout 配置影响,开发者可以通过ini_set('default_socket_timeout', 60)这样来单独设置它,它的默认值是60。
这里是对Swoole\Server\Task的详细介绍。这个类很简单,但是你也不能够通过new Swoole\Server\Task()来获得一个Task对象,这种对象完全不包含任何服务端的信息,并且你执行Swoole\Server\Task任意的方法都会有一个致命错误。
Invalid instance of Swoole\Server\Task in /home/task.php on line 3
19 |
20 | worker进程传递给task进程的数据data,该属性是一个string类型的字符串。
Swoole\Server\Task->data
25 |
26 | 返回该数据到达task进程的时间dispatch_time,该属性是一个double类型。
Swoole\Server\Task->dispatch_time
29 |
30 | 返回该数据到达task进程的时间dispatch_time,该属性是一个int类型的整数。
Swoole\Server\Task->id
33 |
34 | 返回该数据来自哪一个worker进程,该属性是一个int类型的整数。
Swoole\Server\Task->worker_id
37 |
38 | 该异步任务的一些标志位信息flags,该属性是一个int类型的整数。
Swoole\Server\Task->flags
41 | ?> flags返回的结果是以下几种类型:
Worker进程发送给task进程的,此时如果在onTask事件中调用Swoole\Server::finish()的话,将会有一个警告发出。 Swoole\Server::finish()中最后一个回调函数不是null,onFinish事件将不会执行,而只会执行这个回调函数。 用于在 Task进程中通知Worker进程,投递的任务已完成。此函数可以传递结果数据给Worker进程。
Swoole\Server\Task->finish(mixed $data): boolmixed $data
56 | Swoole\Server\Task->finish函数必须为Server设置onFinish回调函数。此函数只可用于 Task进程的onTask回调中
71 |
72 | 将给定的数据序列化。
74 |Swoole\Server\Task->pack(mixed $data): string|falsemixed $data
78 | string $data
95 | 进程管理器,基于Process\Pool实现。可以管理多个进程。相比与Process\Pool,可以非常方便的创建多个执行不同任务的进程,并且可以控制每一个进程是否要处于协程环境。
18 |
| 版本号 | 24 |类名 | 25 |更新说明 | 26 |
|---|---|---|
| v4.5.3 | 31 |Swoole\Process\ProcessManager | 32 |- | 33 |
| v4.5.5 | 36 |Swoole\Process\Manager | 37 |重命名,ProcessManager 为 Manager 的别名 | 38 |
use Swoole\Process\Manager;
45 | use Swoole\Process\Pool;
46 | $pm = new Manager();
47 | for ($i = 0; $i < 2; $i++) {
48 | $pm->add(function (Pool $pool, int $workerId) {
49 | });
50 | }
51 | $pm->start();
52 |
53 | 构造方法。
57 |Swoole\Process\Manager::__construct(int $ipcType = SWOOLE_IPC_NONE, int $msgQueueKey = 0);
58 | int $ipcTypeProcess\Pool的$ipc_type一致【默认为0表示不使用任何进程间通信特性】0int $msgQueueKeykey,和Process\Pool的$msgqueue_key一致设置工作进程之间的通信方式。
72 |Swoole\Process\Manager->setIPCType(int $ipcType): self;callable $funcbool $enableCoroutine批量增加工作进程。
117 |Swoole\Process\Manager->addBatch(int $workerNum, callable $func, bool $enableCoroutine = false): selfServer默认运行模式为SWOOLE_BASEPHP版本要求提高到8.0PSR-0的类别名,仅保留命名空间风格类名,如swoole_server必须修改为Swoole\ServerSwoole\Coroutine\Redis和Swoole\Coroutine\MySQL标记为已废弃,请使用Runtime Hook+原生Redis/MySQL客户端
25 |
26 | BASE 模式下,onStart 回调将始终在第一个工作进程 (workerId 为 0) 启动时回调,先于 onWorkerStart 执行。在 onStart 函数中始终可以使用协程 API,Worker-0 出现致命错误重启时,会再次回调 onStart
28 | 在之前的版本中,onStart 在只有一个工作进程时,会在 Worker-0 中回调。有多个工作进程时,在 Manager 进程中执行。
29 |
30 | Table\Row,Table 不再支持以数组的方式读写
32 |
33 | session id的最大限制,不再重复pcntl_fork/pcntl_wait/pcntl_waitpid/pcntl_sigtimedwaitEvent::rshutdown() 标记为已弃用,请改用 Coroutine\run
39 |
40 | SWOOLE_HOOK_ALL 包括 SWOOLE_HOOK_CURLssl_method,支持ssl_protocols
43 |
44 | PHP官方保持一致, 不再支持PHP7.0 (@matyhtf)Serialize模块, 在单独的 ext-serialize 扩展中维护PostgreSQL模块,在单独的 ext-postgresql 扩展中维护Runtime::enableCoroutine不再会自动兼容协程内外环境, 一旦开启, 则一切阻塞操作必须在协程内调用 (@matyhtf)MySQL客户端驱动, 底层设计更加规范, 但有一些小的向下不兼容的变化 (详见 4.4.0更新日志)
56 |
57 | 62 |由于历史API设计存在问题导致的不可避免的不兼容变更
63 |
68 |实验特性 + 由于历史API设计存在问题导致的不可避免的不兼容变更
69 |
task_async配置项,替换为task_enable_coroutine
71 |
72 | onReceive和Server::getClientInfo对UDP客户端的支持
74 |
75 | swoole_http2_client, 请使用协程HTTP2客户端
77 |
78 | 此版本开始, 异步Http2\Client 将会触发 E_DEPRECATED 提示, 并在下个版本删除, 请使用 Coroutine\Http2\Client来代替
80 | Http2\Response 的 body 属性 重命名 为 data, 此修改是为了保证 request 和 response 两者的统一, 并且更符合HTTP2协议的帧类型名称
81 | 自该版本起, Coroutine\Http2\Client 拥有了相对完整的HTTP2协议支持, 能满足企业级的生产环境应用需求, 如grpc, etcd 等, 所以关于HTTP2的一系列改动是非常必要的
82 |
使 swoole_http2_response 和 swoole_http2_request 保持一致, 所有属性名修改为复数形式, 涉及以下属性
headerscookies
87 |
88 | 90 |由于底层实现过于复杂, 难以维护, 且用户经常对其使用产生误区, 故暂时删除以下API:
91 |
Coroutine\Channel::select
93 | 但同时增加了Coroutine\Channel->pop方法的第二参数为timeout来满足开发需求
94 |
95 | 97 |由于协程内核升级, 可以在任意函数任意地方调用协程, 无需做特殊处理, 故删除了以下API
98 |
Coroutine::call_user_funcCoroutine::call_user_func_array简化协程相关API的名称书写。可修改php.ini设置swoole.use_shortname=On/Off来开启/关闭短名,默认为开启。
20 | 所有的 Swoole\Coroutine 前缀的类名映射为Co。此外还有下面的一些映射:
21 |
//Swoole\Coroutine::create等价于go函数
24 | go(function () {
25 | Co::sleep(0.5);
26 | echo 'hello';
27 | });
28 | go('test');
29 | go([$object, 'method']);
30 |
31 | //Coroutine\Channel可以简写为chan
33 | $c = new chan(1);
34 | $c->push($data);
35 | $c->pop();
36 |
37 | //Swoole\Coroutine::defer可以直接用defer
39 | defer(function () use ($db) {
40 | $db->close();
41 | });
42 |
43 | !> 以下这种方式中go和defer,Swoole 版本 >= v4.6.3 可用
use function Swoole\Coroutine\go;
46 | use function Swoole\Coroutine\run;
47 | use function Swoole\Coroutine\defer;
48 | run(function () {
49 | defer(function () {
50 | echo "co1 end\n";
51 | });
52 | sleep(1);
53 | go(function () {
54 | usleep(100000);
55 | defer(function () {
56 | echo "co2 end\n";
57 | });
58 | echo "co2\n";
59 | });
60 | echo "co1\n";
61 | });
62 |
63 | 在4.4.4版本中系统操作相关的协程API从Swoole\Coroutine类中,迁移到了Swoole\Coroutine\System类中。独立为一个新模块。为了向下兼容,底层依然保留了在Coroutine类之上的别名方法。
Swoole\Coroutine::sleep对应Swoole\Coroutine\System::sleepSwoole\Coroutine::fgets对应Swoole\Coroutine\System::fgets
68 |
69 | | !> 推荐使用命名空间风格。 | 74 |下划线类名风格 | 75 |命名空间风格 | 76 |
|---|---|---|
| swoole_server | 81 |Swoole\Server | 82 ||
| swoole_client | 85 |Swoole\Client | 86 ||
| swoole_process | 89 |Swoole\Process | 90 ||
| swoole_timer | 93 |Swoole\Timer | 94 ||
| swoole_table | 97 |Swoole\Table | 98 ||
| swoole_lock | 101 |Swoole\Lock | 102 ||
| swoole_atomic | 105 |Swoole\Atomic | 106 ||
| swoole_atomic_long | 109 |Swoole\Atomic\Long | 110 ||
| swoole_buffer | 113 |Swoole\Buffer | 114 ||
| swoole_redis | 117 |Swoole\Redis | 118 ||
| swoole_error | 121 |Swoole\Error | 122 ||
| swoole_event | 125 |Swoole\Event | 126 ||
| swoole_http_server | 129 |Swoole\Http\Server | 130 ||
| swoole_http_client | 133 |Swoole\Http\Client | 134 ||
| swoole_http_request | 137 |Swoole\Http\Request | 138 ||
| swoole_http_response | 141 |Swoole\Http\Response | 142 ||
| swoole_websocket_server | 145 |Swoole\WebSocket\Server | 146 ||
| swoole_connection_iterator | 149 |Swoole\Connection\Iterator | 150 ||
| swoole_exception | 153 |Swoole\Exception | 154 ||
| swoole_http2_request | 157 |Swoole\Http2\Request | 158 ||
| swoole_http2_response | 161 |Swoole\Http2\Response | 162 ||
| swoole_process_pool | 165 |Swoole\Process\Pool | 166 ||
| swoole_redis_server | 169 |Swoole\Redis\Server | 170 ||
| swoole_runtime | 173 |Swoole\Runtime | 174 ||
| swoole_server_port | 177 |Swoole\Server\Port | 178 ||
| swoole_server_task | 181 |Swoole\Server\Task | 182 ||
| swoole_table_row | 185 |Swoole\Table\Row | 186 ||
| swoole_timer_iterator | 189 |Swoole\Timer\Iterator | 190 ||
| swoole_websocket_closeframe | 193 |Swoole\Websocket\Closeframe | 194 ||
| swoole_websocket_frame | 197 |Swoole\Websocket\Frame | 198 |
感谢以下为了Swoole文档更加优秀而提出贡献的同学。
sdfjklmin 📖 |
29 | baly2000 📖 |
30 | zhmm 📖 |
31 | 吴亲库里 📖 |
32 | ✨小透明・宸✨ 📖 |
33 | Lingjie Lin 📖 |
34 | Arun Fung 📖 |
35 |
jie295053415 📖 |
38 | huanghui 📖 |
39 | nhzex 📖 |
40 | Success 📖 |
41 | yuntian001 📖 |
42 | SETSESSION 📖 |
43 | baicai 📖 |
44 |
?> 完全协程化的HTTP服务器实现,Co\Http\Server由于HTTP解析性能原因使用C++编写,因此并非由PHP编写的Co\Server的子类。
18 | 与 Http\Server 的不同之处:
对连接的处理是在单独的子协程中完成,客户端连接的Connect、Request、Response、Close是完全串行的
23 | !> 需要v4.4.0或更高版本
24 | !> 若编译时开启HTTP2,则默认会启用HTTP2协议支持,无需像Swoole\Http\Server一样配置open_http2_protocol (注:v4.4.16以下版本HTTP2支持存在已知BUG, 请升级后使用)
25 |
Swoole\Coroutine\Http\Server::__construct(string $host, int $port = 0, bool $ssl = false, bool $reuse_port = false);
33 | 参数
36 |string $host
unix://tmp/your_file.sock的格式填写 】int $port
bool $ssl
SSL/TLS隧道加密bool $reuse_port
注册回调函数以处理参数$pattern所指示路径下的HTTP请求。
Swoole\Coroutine\Http\Server->handle(string $pattern, callable $fn): void
71 | !> 必须在 Server::start 之前设置处理函数
参数
76 |string $pattern
URL路径【如/index.html,注意这里不能传入http://domain】callable $fn
Swoole\Http\Server中的OnRequest回调,在此不再赘述function callback(Swoole\Http\Request $req, Swoole\Http\Response $resp) {
93 | $resp->end("hello world");
94 | }提示
99 |服务器在Accept(建立连接)成功后,会自动创建协程并接受HTTP请求
$fn是在新的子协程空间内执行,因此在函数内无需再次创建协程
客户端支持KeepAlive,子协程会循环继续接受新的请求,而不退出
108 |客户端不支持KeepAlive,子协程会停止接受请求,并退出关闭连接
注意
114 | !> -$pattern设置相同路径时,新的设置会覆盖旧的设置;
115 | -未设置/根路径处理函数并且请求的路径没有找到任何匹配的$pattern,Swoole将返回404错误;
116 | -$pattern使用字符串匹配的方法,不支持通配符和正则,不区分大小写,匹配算法是前缀匹配,例如:url是/test111会匹配到/test这个规则,匹配到即跳出匹配忽略后面的配置;
117 | -推荐设置/根路径处理函数,并在回调函数中使用$request->server['request_uri']进行请求路由。
118 |
?> 启动服务器。
121 |Swoole\Coroutine\Http\Server->start();
122 |
123 | ?> 终止服务器。
125 |Swoole\Coroutine\Http\Server->shutdown();
126 |
127 | use Swoole\Coroutine\Http\Server;
129 | use function Swoole\Coroutine\run;
130 | run(function () {
131 | $server = new Server('127.0.0.1', 9502, false);
132 | $server->handle('/', function ($request, $response) {
133 | $response->end("<h1>Index</h1>");
134 | });
135 | $server->handle('/test', function ($request, $response) {
136 | $response->end("<h1>Test</h1>");
137 | });
138 | $server->handle('/stop', function ($request, $response) use ($server) {
139 | $response->end("<h1>Stop</h1>");
140 | $server->shutdown();
141 | });
142 | $server->start();
143 | });
144 | PHP代码中可以很方便地创建一个锁Swoole\Lock,用来实现数据同步。Lock类支持5种锁的类型。多线程模式需要使用Swoole\Thread\Lock,除了命名空间不一样,其接口与 Swoole\Lock 完全一致。
24 | 锁类型 |
25 | 说明 | 26 |
|---|---|
| SWOOLE_MUTEX | 31 |互斥锁 | 32 |
| SWOOLE_RWLOCK | 35 |读写锁 | 36 |
| SWOOLE_SPINLOCK | 39 |自旋锁 | 40 |
| SWOOLE_FILELOCK | 43 |文件锁(废弃) | 44 |
| SWOOLE_SEM | 47 |信号量(废弃) | 48 |
!> 请勿在onReceive等回调函数中创建锁,否则内存会持续增长,造成内存泄漏。 52 |
53 |$lock = new Swoole\Lock(SWOOLE_MUTEX);
55 | echo "[Master]create lock\n";
56 | $lock->lock();
57 | if (pcntl_fork() > 0)
58 | {
59 | sleep(1);
60 | $lock->unlock();
61 | }
62 | else
63 | {
64 | echo "[Child] Wait Lock\n";
65 | $lock->lock();
66 | echo "[Child] Get Lock\n";
67 | $lock->unlock();
68 | exit("[Child] exit\n");
69 | }
70 | echo "[Master]release lock\n";
71 | unset($lock);
72 | sleep(1);
73 | echo "[Master]exit\n";
74 |
75 | !> 在协程中无法使用锁,请谨慎使用,不要在lock和unlock操作中间使用可能引起协程切换的API。
77 |
!> 此代码在协程模式下100%死锁 参考此文章
$lock = new Swoole\Lock();
81 | $c = 2;
82 | while ($c--) {
83 | go(function () use ($lock) {
84 | $lock->lock();
85 | Co::sleep(1);
86 | $lock->unlock();
87 | });
88 | }
89 |
90 | 构造函数。
94 |Swoole\Lock::__construct(int $type = SWOOLE_MUTEX, string $lockfile = '');
95 | !> 不要循环创建/销毁锁的对象,否则会发生内存泄漏。
96 |int $type
99 | SWOOLE_MUTEX【互斥锁】string $lockfile
105 | SWOOLE_FILELOCK时必须传入】$lock->lock_read()。另外除文件锁外,其他类型的锁必须在父进程内创建,这样fork出的子进程之间才可以互相争抢锁。
110 |
111 | 加锁操作。如果有其他进程持有锁,那这里将进入阻塞,直到持有锁的进程unlock()释放锁。
Swoole\Lock->lock(): bool
114 |
115 | 加锁操作。与lock方法不同的是,trylock()不会阻塞,它会立即返回。
Swoole\Lock->trylock(): booltrue,此时可以修改共享变量false,表示有其他进程持有锁
122 | !> SWOOlE_SEM 信号量没有trylock方法
123 |
124 | 释放锁。
126 |Swoole\Lock->unlock(): bool
127 |
128 | 只读加锁。
130 |Swoole\Lock->lock_read(): bool$lock->lock()或$lock->trylock(),这两个方法是获取独占锁,在独占锁加锁时,其他进程无法再进行任何加锁操作,包括读锁;$lock->lock()/$lock->trylock())时,$lock->lock_read()会发生阻塞,直到持有独占锁的进程释放锁。
136 | !> 只有SWOOLE_RWLOCK和SWOOLE_FILELOCK类型的锁支持只读加锁
137 |
138 | 加锁。此方法与lock_read()相同,但是非阻塞的。
Swoole\Lock->trylock_read(): bool
141 | !> 调用会立即返回,必须检测返回值以确定是否拿到了锁。 142 |
143 |加锁操作。作用与lock()方法一致,但lockwait()可以设置超时时间。
Swoole\Lock->lockwait(float $timeout = 1.0): bool
146 | float $timeout
149 | 1.5表示1s+500ms】1falsetrue
158 | !> 只有Mutex类型的锁支持lockwait!> 建议先查看Coroutine,了解协程基本概念之后再看本文。 18 | Swoole4 使用全新的协程内核引擎,现在 Swoole 拥有一个全职的开发团队,因此正在进入PHP历史上前所未有的时期,为性能的高速提升提供了独一无二的可能性。 19 | Swoole4 或更高版本拥有高可用性的内置协程,可以使用完全同步的代码来实现异步IO,PHP代码没有任何额外的关键字,底层会自动进行协程调度。 20 |
21 |睡眠1万次,读取,写入,检查和删除文件1万次,使用PDO和MySQLi与数据库通信1万次,创建TCP服务器和多个客户端相互通信1万次,创建UDP服务器和多个客户端相互通信1万次......一切都在一个进程中完美完成!
23 |use Swoole\Runtime;
24 | use Swoole\Coroutine;
25 | use function Swoole\Coroutine\run;
26 | // 此行代码后,文件操作,sleep,Mysqli,PDO,streams等都变成异步IO,见'一键协程化'章节。
27 | Runtime::enableCoroutine();
28 | $s = microtime(true);
29 | // Swoole\Coroutine\run()见'协程容器'章节。
30 | run(function() {
31 | // i just want to sleep...
32 | for ($c = 100; $c--;) {
33 | Coroutine::create(function () {
34 | for ($n = 100; $n--;) {
35 | usleep(1000);
36 | }
37 | });
38 | }
39 | // 10k file read and write
40 | for ($c = 100; $c--;) {
41 | Coroutine::create(function () use ($c) {
42 | $tmp_filename = "/tmp/test-{$c}.php";
43 | for ($n = 100; $n--;) {
44 | $self = file_get_contents(__FILE__);
45 | file_put_contents($tmp_filename, $self);
46 | assert(file_get_contents($tmp_filename) === $self);
47 | }
48 | unlink($tmp_filename);
49 | });
50 | }
51 | // 10k pdo and mysqli read
52 | for ($c = 50; $c--;) {
53 | Coroutine::create(function () {
54 | $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root');
55 | $statement = $pdo->prepare('SELECT * FROM `user`');
56 | for ($n = 100; $n--;) {
57 | $statement->execute();
58 | assert(count($statement->fetchAll()) > 0);
59 | }
60 | });
61 | }
62 | for ($c = 50; $c--;) {
63 | Coroutine::create(function () {
64 | $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');
65 | $statement = $mysqli->prepare('SELECT `id` FROM `user`');
66 | for ($n = 100; $n--;) {
67 | $statement->bind_result($id);
68 | $statement->execute();
69 | $statement->fetch();
70 | assert($id > 0);
71 | }
72 | });
73 | }
74 | // php_stream tcp server & client with 12.8k requests in single process
75 | function tcp_pack(string $data): string
76 | {
77 | return pack('n', strlen($data)) . $data;
78 | }
79 | function tcp_length(string $head): int
80 | {
81 | return unpack('n', $head)[1];
82 | }
83 | Coroutine::create(function () {
84 | $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);
85 | $socket = stream_socket_server(
86 | 'tcp://0.0.0.0:9502',
87 | $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx
88 | );
89 | if (!$socket) {
90 | echo "{$errstr} ({$errno})\n";
91 | } else {
92 | $i = 0;
93 | while ($conn = stream_socket_accept($socket, 1)) {
94 | stream_set_timeout($conn, 5);
95 | for ($n = 100; $n--;) {
96 | $data = fread($conn, tcp_length(fread($conn, 2)));
97 | assert($data === "Hello Swoole Server #{$n}!");
98 | fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!"));
99 | }
100 | if (++$i === 128) {
101 | fclose($socket);
102 | break;
103 | }
104 | }
105 | }
106 | });
107 | for ($c = 128; $c--;) {
108 | Coroutine::create(function () {
109 | $fp = stream_socket_client('tcp://127.0.0.1:9502', $errno, $errstr, 1);
110 | if (!$fp) {
111 | echo "{$errstr} ({$errno})\n";
112 | } else {
113 | stream_set_timeout($fp, 5);
114 | for ($n = 100; $n--;) {
115 | fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!"));
116 | $data = fread($fp, tcp_length(fread($fp, 2)));
117 | assert($data === "Hello Swoole Client #{$n}!");
118 | }
119 | fclose($fp);
120 | }
121 | });
122 | }
123 | // udp server & client with 12.8k requests in single process
124 | Coroutine::create(function () {
125 | $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0);
126 | $socket->bind('127.0.0.1', 9503);
127 | $client_map = [];
128 | for ($c = 128; $c--;) {
129 | for ($n = 0; $n < 100; $n++) {
130 | $recv = $socket->recvfrom($peer);
131 | $client_uid = "{$peer['address']}:{$peer['port']}";
132 | $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1;
133 | assert($recv === "Client: Hello #{$id}!");
134 | $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!");
135 | }
136 | }
137 | $socket->close();
138 | });
139 | for ($c = 128; $c--;) {
140 | Coroutine::create(function () {
141 | $fp = stream_socket_client('udp://127.0.0.1:9503', $errno, $errstr, 1);
142 | if (!$fp) {
143 | echo "$errstr ($errno)\n";
144 | } else {
145 | for ($n = 0; $n < 100; $n++) {
146 | fwrite($fp, "Client: Hello #{$n}!");
147 | $recv = fread($fp, 1024);
148 | list($address, $port) = explode(':', (stream_socket_get_name($fp, true)));
149 | assert($address === '127.0.0.1' && (int)$port === 9503);
150 | assert($recv === "Server: Hello #{$n}!");
151 | }
152 | fclose($fp);
153 | }
154 | });
155 | }
156 | });
157 | echo 'use ' . (microtime(true) - $s) . ' s';
158 |
159 |
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Resources/Documents/server12port.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Swoole\Server可以监听多个端口,每个端口都可以设置不同的协议处理方式,例如80端口处理HTTP协议,9507端口处理TCP协议。SSL/TLS传输加密也可以只对特定的端口启用。
18 | !> 例如主服务器是WebSocket或HTTP协议,新监听的TCP端口(listen的返回值,即Swoole\Server\Port对象,以下简称port)默认会继承主Server的协议设置,必须单独调用port对象的set方法和on方法设置新的协议才会启用新协议。
19 |
//返回port对象
22 | $port1 = $server->listen("127.0.0.1", 9501, SWOOLE_SOCK_TCP);
23 | $port2 = $server->listen("127.0.0.1", 9502, SWOOLE_SOCK_UDP);
24 | $port3 = $server->listen("127.0.0.1", 9503, SWOOLE_SOCK_TCP | SWOOLE_SSL);
25 |
26 | //port对象的调用set方法
28 | $port1->set([
29 | 'open_length_check' => true,
30 | 'package_length_type' => 'N',
31 | 'package_length_offset' => 0,
32 | 'package_max_length' => 800000,
33 | ]);
34 | $port3->set([
35 | 'open_eof_split' => true,
36 | 'package_eof' => "\r\n",
37 | 'ssl_cert_file' => 'ssl.cert',
38 | 'ssl_key_file' => 'ssl.key',
39 | ]);
40 |
41 | //设置每个port的回调函数
43 | $port1->on('connect', function ($serv, $fd){
44 | echo "Client:Connect.\n";
45 | });
46 | $port1->on('receive', function ($serv, $fd, $reactor_id, $data) {
47 | $serv->send($fd, 'Swoole: '.$data);
48 | $serv->close($fd);
49 | });
50 | $port1->on('close', function ($serv, $fd) {
51 | echo "Client: Close.\n";
52 | });
53 | $port2->on('packet', function ($serv, $data, $addr) {
54 | var_dump($data, $addr);
55 | });
56 |
57 | Swoole\Http\Server和Swoole\WebSocket\Server因为是使用继承子类实现的,无法通过调用Swoole\Server实例的listen来方法创建HTTP或者WebSocket服务器。
59 | 如服务器的主要功能为RPC,但希望提供一个简单的Web管理界面。在这样的场景中,可以先创建HTTP/WebSocket服务器,然后再进行listen监听原生TCP的端口。
60 |
$http_server = new Swoole\Http\Server('0.0.0.0',9998);
63 | $http_server->set(['daemonize'=> false]);
64 | $http_server->on('request', function ($request, $response) {
65 | $response->header("Content-Type", "text/html; charset=utf-8");
66 | $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
67 | });
68 | //多监听一个TCP端口,对外开启TCP服务,并设置TCP服务器的回调
69 | $tcp_server = $http_server->listen('0.0.0.0', 9999, SWOOLE_SOCK_TCP);
70 | //默认新监听的端口 9999 会继承主服务器的设置,也是 HTTP 协议
71 | //需要调用 set 方法覆盖主服务器的设置
72 | $tcp_server->set([]);
73 | $tcp_server->on('receive', function ($server, $fd, $threadId, $data) {
74 | echo $data;
75 | });
76 | $http_server->start();
77 | 通过这样的代码,就可以建立一个对外提供HTTP服务,又同时对外提供TCP服务的Server,更加具体的优雅代码组合则由你自己来实现。 78 |
79 |$port1 = $server->listen("127.0.0.1", 9501, SWOOLE_SOCK_TCP);
81 | $port1->set([
82 | 'open_websocket_protocol' => true, // 设置使得这个端口支持WebSocket协议
83 | ]);
84 | $port1 = $server->listen("127.0.0.1", 9501, SWOOLE_SOCK_TCP);
85 | $port1->set([
86 | 'open_http_protocol' => false, // 设置这个端口关闭HTTP协议功能
87 | ]);
88 | 同理还有:open_http_protocol、open_http2_protocol、open_mqtt_protocol 等参数
89 |
port未调用set方法,设置协议处理选项的监听端口,将会继承主服务器的相关配置HTTP/WebSocket服务器,如果未设置协议参数,监听的端口仍然会设置为HTTP或WebSocket协议,并且不会执行为端口设置的onReceive回调HTTP/WebSocket服务器,监听端口调用set设置配置参数,会清除主服务器的协议设定。监听端口将变为TCP协议。监听的端口如果希望仍然使用HTTP/WebSocket协议,需要在配置中增加open_http_protocol => true 和 open_websocket_protocol => true
95 | port可以通过set设置的参数有:backlog、open_tcp_keepalive、open_tcp_nodelay、tcp_defer_accept等open_length_check、open_eof_check、package_length_type等ssl_cert_file、ssl_key_file等
99 | 具体可参考配置章节
100 |
101 | port未调用on方法,设置回调函数的监听端口,默认使用主服务器的回调函数,port可以通过on方法设置的回调有:
Worker进程空间内执行
127 |
128 | $server = new Swoole\WebSocket\Server("0.0.0.0", 9514, SWOOLE_BASE);
130 | $tcp = $server->listen("0.0.0.0", 9515, SWOOLE_SOCK_TCP);
131 | $tcp->set([]);
132 | $server->on("open", function ($serv, $req) {
133 | echo "new WebSocket Client, fd={$req->fd}\n";
134 | });
135 | $server->on("message", function ($serv, $frame) {
136 | echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
137 | $serv->push($frame->fd, "this is server OnMessage");
138 | });
139 | $tcp->on('receive', function ($server, $fd, $reactor_id, $data) {
140 | //仅遍历 9514 端口的连接,因为是用的$server,不是$tcp
141 | $websocket = $server->ports[0];
142 | foreach ($websocket->connections as $_fd) {
143 | var_dump($_fd);
144 | if ($server->exist($_fd)) {
145 | $server->push($_fd, "this is server onReceive");
146 | }
147 | }
148 | $server->send($fd, 'receive: '.$data);
149 | });
150 | $server->start();?> 所有的协程必须在协程容器里面创建,Swoole程序启动的时候大部分情况会自动创建协程容器,用Swoole启动程序的方式一共有三种:
协程容器,参考enable_coroutine。Swoole提供的2个进程管理模块Process和Process\Pool的start方法,此种启动方式会在进程启动的时候创建协程容器,参考这两个模块构造函数的enable_coroutine参数。其他直接裸写协程的方式启动程序,需要先创建一个协程容器(Coroutine\run()函数,可以理解为java、c的main函数),例如:
启动一个全协程HTTP服务
use Swoole\Coroutine\Http\Server;
27 | use function Swoole\Coroutine\run;
28 | run(function () {
29 | $server = new Server('127.0.0.1', 9502, false);
30 | $server->handle('/', function ($request, $response) {
31 | $response->end("<h1>Index</h1>");
32 | });
33 | $server->handle('/test', function ($request, $response) {
34 | $response->end("<h1>Test</h1>");
35 | });
36 | $server->handle('/stop', function ($request, $response) use ($server) {
37 | $response->end("<h1>Stop</h1>");
38 | $server->shutdown();
39 | });
40 | $server->start();
41 | });
42 | echo 1;//得不到执行
43 | 添加2个协程并发的做一些事情
46 |use Swoole\Coroutine;
47 | use function Swoole\Coroutine\run;
48 | run(function () {
49 | Coroutine::create(function() {
50 | var_dump(file_get_contents("http://www.xinhuanet.com/"));
51 | });
52 | Coroutine::create(function() {
53 | Coroutine::sleep(1);
54 | echo "done\n";
55 | });
56 | });
57 | echo 1;//可以得到执行
58 | !> 在Swoole v4.4+版本可用。
59 | !> 不可以嵌套Coroutine\run()。
60 | Coroutine\run()里面的逻辑如果有未处理的事件在Coroutine\run()之后就进行EventLoop,后面的代码将得不到执行,反之,如果没有事件了将继续向下执行,可以再次Coroutine\run()。
61 | 上文的Coroutine\run()函数其实是对Swoole\Coroutine\Scheduler类(协程调度器类)的封装,想了解细节的同学可以看Swoole\Coroutine\Scheduler的方法:
62 |
?> 设置协程运行时参数。
65 | ?> 是Coroutine::set方法的别名。请参考 Coroutine::set 文档
Swoole\Coroutine\Scheduler->set(array $options): bool
67 | 示例
70 |$sch = new Swoole\Coroutine\Scheduler;
71 | $sch->set(['max_coroutine' => 100]);
72 |
73 | ?> 获取设置的协程运行时参数。 Swoole版本 >= v4.6.0 可用
75 | ?> 是Coroutine::getOptions方法的别名。请参考 Coroutine::getOptions 文档
Swoole\Coroutine\Scheduler->getOptions(): null|array
77 |
78 | ?> 添加任务。
80 |Swoole\Coroutine\Scheduler->add(callable $fn, ... $args): bool
81 | 参数
84 |callable $fn
... $args
示例
103 |use Swoole\Coroutine;
104 | $scheduler = new Coroutine\Scheduler;
105 | $scheduler->add(function ($a, $b) {
106 | Coroutine::sleep(1);
107 | echo assert($a == 'hello') . PHP_EOL;
108 | echo assert($b == 12345) . PHP_EOL;
109 | echo "Done.\n";
110 | }, "hello", 12345);
111 | $scheduler->start();
112 | 注意
115 | !> 与go函数不同,这里添加的协程不会立即执行,而是等待调用start方法时,一起启动并执行。如果程序中仅添加了协程,未调用start启动,协程函数$fn将不会被执行。
116 |
?> 添加并行任务。
119 | ?> 与add方法不同,parallel方法会创建并行协程。在start时会同时启动$num个$fn协程,并行地执行。
Swoole\Coroutine\Scheduler->parallel(int $num, callable $fn, ... $args): bool
121 | 参数
124 |int $num
callable $fn
... $args
示例
151 |use Swoole\Coroutine;
152 | $scheduler = new Coroutine\Scheduler;
153 | $scheduler->parallel(10, function ($t, $n) {
154 | Coroutine::sleep($t);
155 | echo "Co ".Coroutine::getCid()."\n";
156 | }, 0.05, 'A');
157 | $scheduler->start();
158 |
159 | ?> 启动程序。
161 | ?> 遍历add和parallel方法添加的协程任务,并执行。
Swoole\Coroutine\Scheduler->start(): bool
163 | 返回值
166 |启动成功,会执行所有添加的任务,所有协程退出时start会返回true
启动失败返回false,原因可能是已经启动了或者已经创建了其他调度器无法再次创建
!> 此处不包含所有常量,如需查看所有常量请访问或安装:ide-helper 18 |
19 || 常量 | 24 |作用 | 25 |
|---|---|
| SWOOLE_VERSION | 30 |当前Swoole的版本号,字符串类型,如1.6.0 | 31 |
| 常量 | 40 |作用 | 41 |
|---|---|
| SWOOLE_BASE | 46 |使用Base模式,业务代码在Reactor进程中直接执行 | 47 |
| SWOOLE_PROCESS | 50 |使用进程模式,业务代码在Worker进程中执行 | 51 |
| 常量 | 60 |作用 | 61 |
|---|---|
| SWOOLE_SOCK_TCP | 66 |创建tcp socket | 67 |
| SWOOLE_SOCK_TCP6 | 70 |创建tcp ipv6 socket | 71 |
| SWOOLE_SOCK_UDP | 74 |创建udp socket | 75 |
| SWOOLE_SOCK_UDP6 | 78 |创建udp ipv6 socket | 79 |
| SWOOLE_SOCK_UNIX_DGRAM | 82 |创建unix dgram socket | 83 |
| SWOOLE_SOCK_UNIX_STREAM | 86 |创建unix stream socket | 87 |
| SWOOLE_SOCK_SYNC | 90 |同步客户端 | 91 |
| 常量 | 100 |作用 | 101 |
|---|---|
| SWOOLE_SSLv3_METHOD | 106 |- | 107 |
| SWOOLE_SSLv3_SERVER_METHOD | 110 |- | 111 |
| SWOOLE_SSLv3_CLIENT_METHOD | 114 |- | 115 |
| SWOOLE_SSLv23_METHOD(默认加密方法) | 118 |- | 119 |
| SWOOLE_SSLv23_SERVER_METHOD | 122 |- | 123 |
| SWOOLE_SSLv23_CLIENT_METHOD | 126 |- | 127 |
| SWOOLE_TLSv1_METHOD | 130 |- | 131 |
| SWOOLE_TLSv1_SERVER_METHOD | 134 |- | 135 |
| SWOOLE_TLSv1_CLIENT_METHOD | 138 |- | 139 |
| SWOOLE_TLSv1_1_METHOD | 142 |- | 143 |
| SWOOLE_TLSv1_1_SERVER_METHOD | 146 |- | 147 |
| SWOOLE_TLSv1_1_CLIENT_METHOD | 150 |- | 151 |
| SWOOLE_TLSv1_2_METHOD | 154 |- | 155 |
| SWOOLE_TLSv1_2_SERVER_METHOD | 158 |- | 159 |
| SWOOLE_TLSv1_2_CLIENT_METHOD | 162 |- | 163 |
| SWOOLE_DTLSv1_METHOD | 166 |- | 167 |
| SWOOLE_DTLSv1_SERVER_METHOD | 170 |- | 171 |
| SWOOLE_DTLSv1_CLIENT_METHOD | 174 |- | 175 |
| SWOOLE_DTLS_SERVER_METHOD | 178 |- | 179 |
| SWOOLE_DTLS_CLIENT_METHOD | 182 |- | 183 |
!> SWOOLE_DTLSv1_METHOD、SWOOLE_DTLSv1_SERVER_METHOD、SWOOLE_DTLSv1_CLIENT_METHOD已在 Swoole 版本 >= v4.5.0 中移除。
187 |
| 常量 | 193 |作用 | 194 |
|---|---|
| SWOOLE_SSL_TLSv1 | 199 |- | 200 |
| SWOOLE_SSL_TLSv1_1 | 203 |- | 204 |
| SWOOLE_SSL_TLSv1_2 | 207 |- | 208 |
| SWOOLE_SSL_TLSv1_3 | 211 |- | 212 |
| SWOOLE_SSL_SSLv2 | 215 |- | 216 |
| SWOOLE_SSL_SSLv3 | 219 |- | 220 |
!> Swoole版本 >= v4.5.4 可用
224 |
| 常量 | 230 |作用 | 231 |
|---|---|
| SWOOLE_LOG_DEBUG | 236 |调试日志,仅作为内核开发调试使用 | 237 |
| SWOOLE_LOG_TRACE | 240 |跟踪日志,可用于跟踪系统问题,调试日志是经过精心设置的,会携带关键性信息 | 241 |
| SWOOLE_LOG_INFO | 244 |普通信息,仅作为信息展示 | 245 |
| SWOOLE_LOG_NOTICE | 248 |提示信息,系统可能存在某些行为,如重启、关闭 | 249 |
| SWOOLE_LOG_WARNING | 252 |警告信息,系统可能存在某些问题 | 253 |
| SWOOLE_LOG_ERROR | 256 |错误信息,系统发生了某些关键性的错误,需要即时解决 | 257 |
| SWOOLE_LOG_NONE | 260 |相当于关闭日志信息,日志信息不会抛出 | 261 |
!> SWOOLE_LOG_DEBUG和SWOOLE_LOG_TRACE两种日志,必须在编译Swoole扩展时使用--enable-debug-log或--enable-trace-log后才可以使用。正常版本中即使设置了log_level = SWOOLE_LOG_TRACE也是无法打印此类日志的。
265 |
线上运行的服务,随时都有大量请求在处理,底层抛出的日志数量非常巨大。可使用trace_flags设置跟踪日志的标签,仅打印部分跟踪日志。trace_flags支持使用|或操作符设置多个跟踪项。
$serv->set([
269 | 'log_level' => SWOOLE_LOG_TRACE,
270 | 'trace_flags' => SWOOLE_TRACE_SERVER | SWOOLE_TRACE_HTTP2,
271 | ]);
272 | 底层支持以下跟踪项,可使用SWOOLE_TRACE_ALL表示跟踪所有项目:
SWOOLE_TRACE_SERVERSWOOLE_TRACE_CLIENTSWOOLE_TRACE_BUFFERSWOOLE_TRACE_CONNSWOOLE_TRACE_EVENTSWOOLE_TRACE_WORKERSWOOLE_TRACE_REACTORSWOOLE_TRACE_PHPSWOOLE_TRACE_HTTP2SWOOLE_TRACE_EOF_PROTOCOLSWOOLE_TRACE_LENGTH_PROTOCOLSWOOLE_TRACE_CLOSESWOOLE_TRACE_HTTP_CLIENTSWOOLE_TRACE_COROUTINESWOOLE_TRACE_REDIS_CLIENTSWOOLE_TRACE_MYSQL_CLIENTSWOOLE_TRACE_AIOSWOOLE_TRACE_ALLServer->set()函数所设置的参数会保存到Server->$setting属性上。在回调函数中可以访问运行参数的值。该属性是一个array类型的数组。
Swoole\Server->setting
21 | $server = new Swoole\Server('127.0.0.1', 9501);
24 | $server->set(array('worker_num' => 4));
25 | echo $server->setting['worker_num'];
26 |
27 | TCP连接迭代器,可以使用foreach遍历服务器当前所有的连接,此属性的功能与Server->getClientList是一致的,但是更加友好。
29 | 遍历的元素为单个连接的fd。
Swoole\Server->connections
31 | !> $connections属性是一个迭代器对象,不是PHP数组,所以不能用var_dump或者数组下标来访问,只能通过foreach进行遍历操作
TCP连接,因此在BASE模式中,只能在当前进程内使用$connections迭代器foreach ($server->connections as $fd) {
38 | var_dump($fd);
39 | }
40 | echo "当前服务器共有 " . count($server->connections) . " 个连接\n";
41 |
42 | 返回当前服务器监听的主机地址的host,该属性是一个string类型的字符串。
Swoole\Server->host
45 |
46 | 返回当前服务器监听的端口的port,该属性是一个int类型的整数。
Swoole\Server->port
49 |
50 | 返回当前Server 的类型type,该属性是一个int类型的整数。
Swoole\Server->type
53 | !> 该属性返回会返回下列的值的其中一个
54 |SWOOLE_SOCK_TCP tcp ipv4 socketSWOOLE_SOCK_TCP6 tcp ipv6 socketSWOOLE_SOCK_UDP udp ipv4 socketSWOOLE_SOCK_UDP6 udp ipv6 socketSWOOLE_SOCK_UNIX_DGRAM unix socket dgramSWOOLE_SOCK_UNIX_STREAM unix socket stream
61 |
62 | 返回当前服务器是否启动ssl,该属性是一个bool类型。
Swoole\Server->ssl
65 |
66 | 返回当前服务器的进程模式mode,该属性是一个int类型的整数。
Swoole\Server->mode
69 | !> 该属性返回会返回下列的值的其中一个
SWOOLE_BASE 单进程模式SWOOLE_PROCESS 多进程模式
72 |
73 | 监听端口数组,如果服务器监听了多个端口可以遍历Server::$ports得到所有Swoole\Server\Port对象。
75 | 其中swoole_server::$ports[0]为构造方法所设置的主服务器端口。
$ports = $server->ports;
79 | $ports[0]->set($settings);
80 | $ports[1]->on('Receive', function () {
81 | //callback
82 | });
83 |
84 | 返回当前服务器主进程的PID。
Swoole\Server->master_pid
87 | !> 只能在onStart/onWorkerStart之后获取到
$server = new Swoole\Server("127.0.0.1", 9501);
90 | $server->on('start', function ($server){
91 | echo $server->master_pid;
92 | });
93 | $server->on('receive', function ($server, $fd, $reactor_id, $data) {
94 | $server->send($fd, 'Swoole: '.$data);
95 | $server->close($fd);
96 | });
97 | $server->start();
98 |
99 | 返回当前服务器管理进程的PID,该属性是一个int类型的整数。
Swoole\Server->manager_pid
102 | !> 只能在onStart/onWorkerStart之后获取到
$server = new Swoole\Server("127.0.0.1", 9501);
105 | $server->on('start', function ($server){
106 | echo $server->manager_pid;
107 | });
108 | $server->on('receive', function ($server, $fd, $reactor_id, $data) {
109 | $server->send($fd, 'Swoole: '.$data);
110 | $server->close($fd);
111 | });
112 | $server->start();得到当前Worker进程的编号,包括 Task进程,该属性是一个int类型的整数。
Swoole\Server->worker_id
118 | $server = new Swoole\Server('127.0.0.1', 9501);
121 | $server->set([
122 | 'worker_num' => 8,
123 | 'task_worker_num' => 4,
124 | ]);
125 | $server->on('WorkerStart', function ($server, int $workerId) {
126 | if ($server->taskworker) {
127 | echo "task workerId:{$workerId}\n";
128 | echo "task worker_id:{$server->worker_id}\n";
129 | } else {
130 | echo "workerId:{$workerId}\n";
131 | echo "worker_id:{$server->worker_id}\n";
132 | }
133 | });
134 | $server->on('Receive', function ($server, $fd, $reactor_id, $data) {
135 | });
136 | $server->on('Task', function ($serv, $task_id, $reactor_id, $data) {
137 | });
138 | $server->start();$workerId是相同的。Worker进程编号范围是[0, $server->setting['worker_num'] - 1][$server->setting['worker_num'], $server->setting['worker_num'] + $server->setting['task_worker_num'] - 1]
144 | !> 工作进程重启后worker_id的值是不变的
145 |
146 | 当前进程是否是 Task 进程,该属性是一个bool类型。
Swoole\Server->taskworkerSwoole 从v4.4.13版本开始提供了内置协程连接池,本章节会说明如何使用对应的连接池。
18 |
ConnectionPool,原始连接池,基于Channel自动调度,支持传入任意构造器(callable),构造器需返回一个连接对象
get方法获取连接(连接池未满时会创建新的连接)put方法回收连接fill方法填充连接池(提前创建连接)close关闭连接池
26 | !> Simps 框架 的 DB 组件 基于 Database 进行封装,实现了自动归还连接、事务等功能,可以进行参考或直接使用,具体可查看Simps 文档
27 |
28 | 各种数据库连接池和对象代理的高级封装,支持自动断线重连。目前包含PDO,Mysqli,Redis三种类型的数据库支持:
PDOConfig, PDOProxy, PDOPoolMysqliConfig, MysqliProxy, MysqliPoolRedisConfig, RedisProxy, RedisPool
34 | !> 1. MySQL断线重连可自动恢复大部分连接上下文(fetch模式,已设置的attribute,已编译的Statement等等),但诸如事务等上下文无法恢复,若处于事务中的连接断开,将会抛出异常,请自行评估重连的可靠性;
$pool->put(null);归还一个空连接以保证连接池的数量平衡。
38 |
39 | 用于创建连接池对象,存在两个参数,分别为对应的Config对象和连接池size
41 |$pool = new \Swoole\Database\PDOPool(Swoole\Database\PDOConfig $config, int $size);
42 | $pool = new \Swoole\Database\MysqliPool(Swoole\Database\MysqliConfig $config, int $size);
43 | $pool = new \Swoole\Database\RedisPool(Swoole\Database\RedisConfig $config, int $size);
44 | $configint $size
54 | <?php
63 | declare(strict_types=1);
64 | use Swoole\Coroutine;
65 | use Swoole\Database\PDOConfig;
66 | use Swoole\Database\PDOPool;
67 | use Swoole\Runtime;
68 | const N = 1024;
69 | Runtime::enableCoroutine();
70 | $s = microtime(true);
71 | Coroutine\run(function () {
72 | $pool = new PDOPool((new PDOConfig)
73 | ->withHost('127.0.0.1')
74 | ->withPort(3306)
75 | // ->withUnixSocket('/tmp/mysql.sock')
76 | ->withDbName('test')
77 | ->withCharset('utf8mb4')
78 | ->withUsername('root')
79 | ->withPassword('root')
80 | );
81 | for ($n = N; $n--;) {
82 | Coroutine::create(function () use ($pool) {
83 | $pdo = $pool->get();
84 | $statement = $pdo->prepare('SELECT ? + ?');
85 | if (!$statement) {
86 | throw new RuntimeException('Prepare failed');
87 | }
88 | $a = mt_rand(1, 100);
89 | $b = mt_rand(1, 100);
90 | $result = $statement->execute([$a, $b]);
91 | if (!$result) {
92 | throw new RuntimeException('Execute failed');
93 | }
94 | $result = $statement->fetchAll();
95 | if ($a + $b !== (int)$result[0][0]) {
96 | throw new RuntimeException('Bad result');
97 | }
98 | $pool->put($pdo);
99 | });
100 | }
101 | });
102 | $s = microtime(true) - $s;
103 | echo 'Use ' . $s . 's for ' . N . ' queries' . PHP_EOL;
104 |
105 | <?php
107 | declare(strict_types=1);
108 | use Swoole\Coroutine;
109 | use Swoole\Database\RedisConfig;
110 | use Swoole\Database\RedisPool;
111 | use Swoole\Runtime;
112 | const N = 1024;
113 | Runtime::enableCoroutine();
114 | $s = microtime(true);
115 | Coroutine\run(function () {
116 | $pool = new RedisPool((new RedisConfig)
117 | ->withHost('127.0.0.1')
118 | ->withPort(6379)
119 | ->withAuth('')
120 | ->withDbIndex(0)
121 | ->withTimeout(1)
122 | );
123 | for ($n = N; $n--;) {
124 | Coroutine::create(function () use ($pool) {
125 | $redis = $pool->get();
126 | $result = $redis->set('foo', 'bar');
127 | if (!$result) {
128 | throw new RuntimeException('Set failed');
129 | }
130 | $result = $redis->get('foo');
131 | if ($result !== 'bar') {
132 | throw new RuntimeException('Get failed');
133 | }
134 | $pool->put($redis);
135 | });
136 | }
137 | });
138 | $s = microtime(true) - $s;
139 | echo 'Use ' . $s . 's for ' . (N * 2) . ' queries' . PHP_EOL;
140 |
141 | <?php
143 | declare(strict_types=1);
144 | use Swoole\Coroutine;
145 | use Swoole\Database\MysqliConfig;
146 | use Swoole\Database\MysqliPool;
147 | use Swoole\Runtime;
148 | const N = 1024;
149 | Runtime::enableCoroutine();
150 | $s = microtime(true);
151 | Coroutine\run(function () {
152 | $pool = new MysqliPool((new MysqliConfig)
153 | ->withHost('127.0.0.1')
154 | ->withPort(3306)
155 | // ->withUnixSocket('/tmp/mysql.sock')
156 | ->withDbName('test')
157 | ->withCharset('utf8mb4')
158 | ->withUsername('root')
159 | ->withPassword('root')
160 | );
161 | for ($n = N; $n--;) {
162 | Coroutine::create(function () use ($pool) {
163 | $mysqli = $pool->get();
164 | $statement = $mysqli->prepare('SELECT ? + ?');
165 | if (!$statement) {
166 | throw new RuntimeException('Prepare failed');
167 | }
168 | $a = mt_rand(1, 100);
169 | $b = mt_rand(1, 100);
170 | if (!$statement->bind_param('dd', $a, $b)) {
171 | throw new RuntimeException('Bind param failed');
172 | }
173 | if (!$statement->execute()) {
174 | throw new RuntimeException('Execute failed');
175 | }
176 | if (!$statement->bind_result($result)) {
177 | throw new RuntimeException('Bind result failed');
178 | }
179 | if (!$statement->fetch()) {
180 | throw new RuntimeException('Fetch failed');
181 | }
182 | if ($a + $b !== (int)$result) {
183 | throw new RuntimeException('Bad result');
184 | }
185 | while ($statement->fetch()) {
186 | continue;
187 | }
188 | $pool->put($mysqli);
189 | });
190 | }
191 | });
192 | $s = microtime(true) - $s;
193 | echo 'Use ' . $s . 's for ' . N . ' queries' . PHP_EOL;?> 完全协程化的WebSocket服务器实现,继承自Coroutine\Http\Server,底层提供了对WebSocket协议的支持,在此不再赘述,只说差异。
18 | !> 此章节在v4.4.13后可用。
19 |
use Swoole\Http\Request;
22 | use Swoole\Http\Response;
23 | use Swoole\WebSocket\CloseFrame;
24 | use Swoole\Coroutine\Http\Server;
25 | use function Swoole\Coroutine\run;
26 | run(function () {
27 | $server = new Server('127.0.0.1', 9502, false);
28 | $server->handle('/websocket', function (Request $request, Response $ws) {
29 | $ws->upgrade();
30 | while (true) {
31 | $frame = $ws->recv();
32 | if ($frame === '') {
33 | $ws->close();
34 | break;
35 | } else if ($frame === false) {
36 | echo 'errorCode: ' . swoole_last_error() . "\n";
37 | $ws->close();
38 | break;
39 | } else {
40 | if ($frame->data == 'close' || get_class($frame) === CloseFrame::class) {
41 | $ws->close();
42 | break;
43 | }
44 | $ws->push("Hello {$frame->data}!");
45 | $ws->push("How are you, {$frame->data}?");
46 | }
47 | }
48 | });
49 | $server->handle('/', function (Request $request, Response $response) {
50 | $response->end(<<<HTML
51 | <h1>Swoole WebSocket Server</h1>
52 | <script>
53 | var wsServer = 'ws://127.0.0.1:9502/websocket';
54 | var websocket = new WebSocket(wsServer);
55 | websocket.onopen = function (evt) {
56 | console.log("Connected to WebSocket server.");
57 | websocket.send('hello');
58 | };
59 | websocket.onclose = function (evt) {
60 | console.log("Disconnected");
61 | };
62 | websocket.onmessage = function (evt) {
63 | console.log('Retrieved data from server: ' + evt.data);
64 | };
65 | websocket.onerror = function (evt, e) {
66 | console.log('Error occured: ' + evt.data);
67 | };
68 | </script>
69 | HTML
70 | );
71 | });
72 | $server->start();
73 | });
74 |
75 | use Swoole\Http\Request;
77 | use Swoole\Http\Response;
78 | use Swoole\WebSocket\CloseFrame;
79 | use Swoole\Coroutine\Http\Server;
80 | use function Swoole\Coroutine\run;
81 | run(function () {
82 | $server = new Server('127.0.0.1', 9502, false);
83 | $server->handle('/websocket', function (Request $request, Response $ws) {
84 | $ws->upgrade();
85 | global $wsObjects;
86 | $objectId = spl_object_id($ws);
87 | $wsObjects[$objectId] = $ws;
88 | while (true) {
89 | $frame = $ws->recv();
90 | if ($frame === '') {
91 | unset($wsObjects[$objectId]);
92 | $ws->close();
93 | break;
94 | } else if ($frame === false) {
95 | echo 'errorCode: ' . swoole_last_error() . "\n";
96 | $ws->close();
97 | break;
98 | } else {
99 | if ($frame->data == 'close' || get_class($frame) === CloseFrame::class) {
100 | unset($wsObjects[$objectId]);
101 | $ws->close();
102 | break;
103 | }
104 | foreach ($wsObjects as $obj) {
105 | $obj->push("Server:{$frame->data}");
106 | }
107 | }
108 | }
109 | });
110 | $server->start();
111 | });
112 |
113 | $ws->upgrade():向客户端发送WebSocket握手消息while(true)循环处理消息的接收和发送$ws->recv()接收WebSocket消息帧$ws->push()向对端发送数据帧$ws->close()关闭连接
120 | !> $ws是一个Swoole\Http\Response对象,具体每个方法使用方法参考下文。
121 |
122 | 发送WebSocket握手成功信息。
126 | !> 此方法不要用于异步风格的服务器中
Swoole\Http\Response->upgrade(): bool
128 |
129 | 接收WebSocket消息。
131 | !> 此方法不要用于异步风格的服务器中,调用recv方法时会挂起当前协程,等待数据到来时再恢复协程的执行
Swoole\Http\Response->recv(float $timeout = 0): Swoole\WebSocket\Frame | false | stringSwoole\WebSocket\Frame对象,请参考 Swoole\WebSocket\Framefalse,请使用 swoole_last_error() 获取错误码发送WebSocket数据帧。
142 | !> 此方法不要用于异步风格的服务器中,发送大数据包时,需要监听可写,因此会引起多次协程切换
Swoole\Http\Response->push(string|object $data, int $opcode = WEBSOCKET_OPCODE_TEXT, bool $finish = true): bool$data是 Swoole\WebSocket\Frame 对象则其后续参数会被忽略,支持发送各种帧类型
147 | string|object $dataint $opcode$opcode参数需要设置为WEBSOCKET_OPCODE_BINARY】WEBSOCKET_OPCODE_TEXTWEBSOCKET_OPCODE_BINARYbool $finishtruefalse
160 |
161 | 关闭WebSocket连接。
163 | !> 此方法不要用于异步风格的服务器中,在v4.4.15以前版本会误报Warning忽略即可。
Swoole\Http\Response->close(): bool
165 | 此方法会直接切断 TCP 连接,不会发送 Close 帧,这与 WebSocket\Server::disconnect() 方法不同。
166 | 可以在关闭连接前使用 push() 方法发送 Close 帧,主动通知客户端。
$frame = new Swoole\WebSocket\CloseFrame;
168 | $frame->reason = 'close';
169 | $ws->push($frame);
170 | $ws->close();18 |122 |建议先查看概览,了解一些协程基本概念后再看此节。 19 | 通道,用于协程间通讯,支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。 20 |
21 |实现原理
22 |23 |
121 |- 通道与
24 |PHP的Array类似,仅占用内存,没有其他额外的资源申请,所有操作均为内存操作,无IO消耗- 底层使用
25 |PHP引用计数实现,无内存拷贝。即使是传递巨大字符串或数组也不会产生额外性能消耗- 58 |
channel基于引用计数实现,是零拷贝的 26 | 27 |使用示例
28 |52 | 53 |use Swoole\Coroutine; 29 | use Swoole\Coroutine\Channel; 30 | use function Swoole\Coroutine\run; 31 | run(function(){ 32 | $channel = new Channel(1); 33 | Coroutine::create(function () use ($channel) { 34 | for($i = 0; $i < 10; $i++) { 35 | Coroutine::sleep(1.0); 36 | $channel->push(['rand' => rand(1000, 9999), 'index' => $i]); 37 | echo "{$i}\n"; 38 | } 39 | }); 40 | Coroutine::create(function () use ($channel) { 41 | while(1) { 42 | $data = $channel->pop(2.0); 43 | if ($data) { 44 | var_dump($data); 45 | } else { 46 | assert($channel->errCode === SWOOLE_CHANNEL_TIMEOUT); 47 | break; 48 | } 49 | } 50 | }); 51 | });方法
54 | 55 |__construct()
56 |通道构造方法。
57 |Swoole\Coroutine\Channel::__construct(int $capacity = 1)- 参数
59 |- 60 |
int $capacity- 功能:设置容量 【必须为大于或等于
61 |1的整数】- 默认值:
62 |1- 其它值:无 63 | !> 底层使用PHP引用计数来保存变量,缓存区只需要占用
69 |$capacity * sizeof(zval)字节的内存,PHP7版本下zval为16字节,如$capacity = 1024时,Channel最大将占用16K内存 64 | !> 在Server中使用时必须在onWorkerStart之后创建 65 | 66 |push()
67 |向通道中写入数据。
68 |Swoole\Coroutine\Channel->push(mixed $data, float $timeout = -1): bool- 参数
70 |- 71 |
mixed $data- 功能:push 数据 【可以是任意类型的PHP变量,包括匿名函数和资源】
72 |- 默认值:无
73 |- 其它值:无 74 | !> 为避免产生歧义,请勿向通道中写入空数据,如
75 |0、false、空字符串、null- 76 |
float $timeout- 功能:设置超时时间
77 |- 值单位:秒【支持浮点型,如
78 |1.5表示1s+500ms】- 默认值:
79 |-1- 其它值:无
80 |- 版本影响:Swoole版本 >= v4.2.12 81 | !> 在通道已满的情况下,
82 |push会挂起当前协程,在约定的时间内,如果没有任何消费者消费数据,将发生超时,底层会恢复当前协程,push调用立即返回false,写入失败- 返回值
83 |- 执行成功返回
84 |true- 通道被关闭时,执行失败返回
85 |false,可使用$channel->errCode得到错误码- 扩展
86 |- 通道已满
87 |- 自动
88 |yield当前协程,其他消费者协程pop消费数据后,通道可写,将重新resume当前协程- 多个生产者协程同时
89 |push时,底层自动进行排队,底层会按照顺序逐个resume这些生产者协程- 通道为空
90 |- 自动唤醒其中一个消费者协程
91 |- 多个消费者协程同时
97 |pop时,底层自动进行排队,按照顺序逐个resume这些消费者协程 92 | !>Coroutine\Channel使用本地内存,不同的进程之间内存是隔离的。只能在同一进程的不同协程内进行push和pop操作 93 | 94 |pop()
95 |从通道中读取数据。
96 |Swoole\Coroutine\Channel->pop(float $timeout = -1): mixed- 参数
98 |- 99 |
float $timeout- 功能:设置超时时间
100 |- 值单位:秒【支持浮点型,如
101 |1.5表示1s+500ms】- 默认值:
102 |-1【表示永不超时】- 其它值:无
103 |- 版本影响:Swoole版本 >= v4.0.3
104 |- 返回值
105 |- 返回值可以是任意类型的PHP变量,包括匿名函数和资源
106 |- 通道被关闭时,执行失败返回
107 |false- 扩展
108 |- 通道已满
109 |- 110 |
pop消费数据后,将自动唤醒其中一个生产者协程,让其写入新数据- 多个生产者协程同时
111 |push时,底层自动进行排队,按照顺序逐个resume这些生产者协程- 通道为空
112 |- 自动
113 |yield当前协程,其他生产者协程push生产数据后,通道可读,将重新resume当前协程- 多个消费者协程同时
118 |pop时,底层自动进行排队,底层会按照顺序逐个resume这些消费者协程 114 | 115 |stats()
116 |获取通道的状态。
117 |Swoole\Coroutine\Channel->stats(): array- 返回值 119 | 返回一个数组,缓冲通道将包括
120 |4项信息,无缓冲通道返回2项信息
- `consumer_num` 消费者数量,表示当前通道为空,有`N`个协程正在等待其他协程调用`push`方法生产数据
123 | - `producer_num` 生产者数量,表示当前通道已满,有`N`个协程正在等待其他协程调用`pop`方法消费数据
124 | - `queue_num` 通道中的元素数量
125 | array(
126 | "consumer_num" => 0,
127 | "producer_num" => 1,
128 | "queue_num" => 10
129 | );
130 |
131 | 关闭通道。并唤醒所有等待读写的协程。
133 |Swoole\Coroutine\Channel->close(): bool
134 | !> 唤醒所有生产者协程,push方法返回false;唤醒所有消费者协程,pop方法返回false
135 |
获取通道中的元素数量。
138 |Swoole\Coroutine\Channel->length(): int
139 |
140 | 判断当前通道是否为空。
142 |Swoole\Coroutine\Channel->isEmpty(): bool
143 |
144 | 判断当前通道是否已满。
146 |Swoole\Coroutine\Channel->isFull(): bool
147 |
148 | 通道缓冲区容量。 152 | 构造函数中设定的容量会保存在此,不过如果设定的容量小于1则此变量会等于1
153 |Swoole\Coroutine\Channel->capacity: int
154 |
155 | 获取错误码。
157 |Swoole\Coroutine\Channel->errCode: int
158 | | 返回值 164 | 值 | 165 |对应常量 | 166 |作用 | 167 |
|---|---|---|
| 0 | 172 |SWOOLE_CHANNEL_OK | 173 |默认 成功 | 174 |
| -1 | 177 |SWOOLE_CHANNEL_TIMEOUT | 178 |超时 pop失败时(超时) | 179 |
| -2 | 182 |SWOOLE_CHANNEL_CLOSED | 183 |channel已关闭,继续操作channel | 184 |