Swoole
Swoole
是一个使用 C++
语言编写的基于异步事件驱动和协程的并行网络通信引擎,为 PHP
提供协程、高性能网络编程支持。提供了多种通信协议的网络服务器和客户端模块,可以方便快速的实现 TCP/UDP服务
、高性能Web
、WebSocket服务
、物联网
、实时通讯
、游戏
、微服务
等,使 PHP
不再局限于传统的 Web 领域。
Swoole 类图
可以直接点击链接到对应的文档页
├── .gitignore
├── LICENSE
├── README.md
├── Swoole.docset
├── ._icon.png
├── ._icon@2x.png
├── Contents
│ ├── Info.plist
│ └── Resources
│ │ ├── Documents
│ │ ├── ._css
│ │ ├── ._index.html
│ │ ├── CONTRIBUTING.html
│ │ ├── blog_list.html
│ │ ├── case.html
│ │ ├── client.html
│ │ ├── client_init.html
│ │ ├── consts.html
│ │ ├── coroutine.html
│ │ ├── coroutine12barrier.html
│ │ ├── coroutine12channel.html
│ │ ├── coroutine12conn_pool.html
│ │ ├── coroutine12coroutine.html
│ │ ├── coroutine12gdb.html
│ │ ├── coroutine12http_server.html
│ │ ├── coroutine12multi_call.html
│ │ ├── coroutine12notice.html
│ │ ├── coroutine12proc_open.html
│ │ ├── coroutine12scheduler.html
│ │ ├── coroutine12server.html
│ │ ├── coroutine12system.html
│ │ ├── coroutine12wait_group.html
│ │ ├── coroutine12ws_server.html
│ │ ├── coroutine_client12client.html
│ │ ├── coroutine_client12fastcgi.html
│ │ ├── coroutine_client12http2_client.html
│ │ ├── coroutine_client12http_client.html
│ │ ├── coroutine_client12init.html
│ │ ├── coroutine_client12mysql.html
│ │ ├── coroutine_client12postgresql.html
│ │ ├── coroutine_client12redis.html
│ │ ├── coroutine_client12socket.html
│ │ ├── css
│ │ │ ├── ._Word2Chm.css
│ │ │ ├── ._bootstrap.css
│ │ │ ├── ._default.css
│ │ │ ├── ._noframe.css
│ │ │ ├── Word2Chm.css
│ │ │ ├── bootstrap.css
│ │ │ └── noframe.css
│ │ ├── environment.html
│ │ ├── event.html
│ │ ├── functions.html
│ │ ├── getting_started12extension.html
│ │ ├── getting_started12notice.html
│ │ ├── http_server.html
│ │ ├── index.html
│ │ ├── learn.html
│ │ ├── learn_other.html
│ │ ├── library.html
│ │ ├── memory12atomic.html
│ │ ├── memory12lock.html
│ │ ├── memory12table.html
│ │ ├── other12alias.html
│ │ ├── other12config.html
│ │ ├── other12discussion.html
│ │ ├── other12donate.html
│ │ ├── other12errno.html
│ │ ├── other12issue.html
│ │ ├── other12signal.html
│ │ ├── other12sysctl.html
│ │ ├── other12tools.html
│ │ ├── process12process.html
│ │ ├── process12process_manager.html
│ │ ├── process12process_pool.html
│ │ ├── question12install.html
│ │ ├── question12swoole.html
│ │ ├── question12use.html
│ │ ├── redis_server.html
│ │ ├── runtime.html
│ │ ├── server12co_init.html
│ │ ├── server12event_class.html
│ │ ├── server12events.html
│ │ ├── server12init.html
│ │ ├── server12methods.html
│ │ ├── server12packet_class.html
│ │ ├── server12pipemessage_class.html
│ │ ├── server12port.html
│ │ ├── server12properties.html
│ │ ├── server12server_port.html
│ │ ├── server12setting.html
│ │ ├── server12statusinfo_class.html
│ │ ├── server12task_class.html
│ │ ├── server12taskresult_class.html
│ │ ├── server12tcp_init.html
│ │ ├── start12coroutine.html
│ │ ├── start12start_http_server.html
│ │ ├── start12start_mqtt.html
│ │ ├── start12start_server.html
│ │ ├── start12start_task.html
│ │ ├── start12start_tcp_server.html
│ │ ├── start12start_udp_server.html
│ │ ├── start12start_ws_server.html
│ │ ├── timer.html
│ │ ├── version12bc.html
│ │ ├── version12log.html
│ │ ├── version12supported.html
│ │ └── websocket_server.html
│ │ ├── docSet.dsidx
│ │ └── docset.sql
├── icon.png
└── icon@2x.png
├── Swoole4.tgz
├── dash-demo.png
├── demo.gif
└── src
├── README.md
├── composer.json
├── composer.lock
└── run.php
/.gitignore:
--------------------------------------------------------------------------------
1 | src/vendor/
2 | src/docset.sql
3 | src/html/
4 | src/md/
5 | src/result.log
6 |
7 |
8 | /vendor/
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Swoole.docset/._icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/fcdf5205efdffc1bf46ba415993d980995efb0df/Swoole.docset/._icon.png
--------------------------------------------------------------------------------
/Swoole.docset/._icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/halfstring/swoole-chinese-docset/fcdf5205efdffc1bf46ba415993d980995efb0df/Swoole.docset/._icon@2x.png
--------------------------------------------------------------------------------
/Swoole.docset/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
感谢以下为了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 |
方便学习Swoole,此章节整理并汇总Swoole系列文章。 18 |
19 |如果您在使用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提供的客户端,包括同步阻塞客户端和协程客户端,Swoole4不再支持异步客户端,相应的需求完全可以用协程客户端代替,参考。
18 | 19 | -------------------------------------------------------------------------------- /Swoole.docset/Contents/Resources/Documents/consts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |!> 此处不包含所有常量,如需查看所有常量请访问或安装: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_SERVER
SWOOLE_TRACE_CLIENT
SWOOLE_TRACE_BUFFER
SWOOLE_TRACE_CONN
SWOOLE_TRACE_EVENT
SWOOLE_TRACE_WORKER
SWOOLE_TRACE_REACTOR
SWOOLE_TRACE_PHP
SWOOLE_TRACE_HTTP2
SWOOLE_TRACE_EOF_PROTOCOL
SWOOLE_TRACE_LENGTH_PROTOCOL
SWOOLE_TRACE_CLOSE
SWOOLE_TRACE_HTTP_CLIENT
SWOOLE_TRACE_COROUTINE
SWOOLE_TRACE_REDIS_CLIENT
SWOOLE_TRACE_MYSQL_CLIENT
SWOOLE_TRACE_AIO
SWOOLE_TRACE_ALL
本节介绍一些协程基本概念和常见问题,也可以通过 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 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
并发编程的用户体验。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 |
Swoole 从v4.4.13
版本开始提供了内置协程连接池,本章节会说明如何使用对应的连接池。
18 |
ConnectionPool,原始连接池,基于Channel自动调度,支持传入任意构造器(callable
),构造器需返回一个连接对象
get
方法获取连接(连接池未满时会创建新的连接)put
方法回收连接fill
方法填充连接池(提前创建连接)close
关闭连接池
26 | !> Simps 框架 的 DB 组件 基于 Database 进行封装,实现了自动归还连接、事务等功能,可以进行参考或直接使用,具体可查看Simps 文档
27 |
28 | 各种数据库连接池和对象代理的高级封装,支持自动断线重连。目前包含PDO,Mysqli,Redis三种类型的数据库支持:
PDOConfig
, PDOProxy
, PDOPool
MysqliConfig
, MysqliProxy
, MysqliPool
RedisConfig
, 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 | $config
int $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;
使用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微课程中的视频教程
?> 完全协程化的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 | [//]: # (
18 | 此处删除了setDefer特性,因为支持setDefer的客户端都推荐用一键协程化了。
19 | )
20 | 使用子协程(go)
+通道(channel)
实现并发请求。
21 | !>建议先看概览,了解协程基本概念再看此节。
22 |
onRequest
中需要并发两个HTTP
请求,可使用go
函数创建2
个子协程,并发地请求多个URL
channel
,使用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功能,将更简单一些。
由于在协程空间内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 | }
?> 所有的协程必须在协程容器
里面创建,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
,原因可能是已经启动了或者已经创建了其他调度器无法再次创建
在Swoole4
中可以使用Channel实现协程间的通信、依赖管理、协程同步。基于Channel可以很容易地实现Golang
的sync.WaitGroup
功能。
18 |
21 |78 | 79 | -------------------------------------------------------------------------------- /Swoole.docset/Contents/Resources/Documents/coroutine12ws_server.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 | });
?> 完全协程化的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 | string
Swoole\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 $data
int $opcode
$opcode
参数需要设置为WEBSOCKET_OPCODE_BINARY
】WEBSOCKET_OPCODE_TEXT
WEBSOCKET_OPCODE_BINARY
bool $finish
true
false
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();
下列协程客户端是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。
由于某些跟踪调试的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
是一个使用 C++
语言编写的基于异步事件驱动和协程的并行网络通信引擎,为 PHP
提供协程、高性能网络编程支持。提供了多种通信协议的网络服务器和客户端模块,可以方便快速的实现 TCP/UDP服务
、高性能Web
、WebSocket服务
、物联网
、实时通讯
、游戏
、微服务
等,使 PHP
不再局限于传统的 Web 领域。
可以直接点击链接到对应的文档页
网络编程中经常使用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 在 v4 版本后内置了 Library 模块,使用 PHP 代码编写内核功能,使得底层设施更加稳定可靠
18 | !> 该模块也可通过 composer 单独安装,单独安装使用时需要通过php.ini
配置swoole.enable_library=Off
关闭扩展内置的 library
19 | 目前提供了以下工具组件:
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(): bool
true
,此时可以修改共享变量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
】1
false
true
158 | !> 只有Mutex
类型的锁支持lockwait
简化协程相关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::sleep
Swoole\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 |
配置 | 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 |
最快响应:GitHub Issue区 (在此提问交流请尊重问题模板和GitHub社区规则) 20 | 查看如何提交错误报告 21 |
22 |有问题可以先去问答系统:wenda.swoole.com 中搜索相似问题,如得不到解决请统一在问答系统当中发帖询问。
24 | 发帖后会经过人工审核,有新的回复时,右上角我的问答
中会有所提示。
25 | 为了确保问有所答,由Swoole
开发组进行轮值策略,每人负责一天,专职来解决问答中的问题。
26 |
由于微信群无法直接加入,故可先加下方二维码好友,并声明:加入Swoole微信交流群
,同意添加好友之后会邀请加入群聊。
40 |
41 |
微信搜索或扫描下方二维码,关注Swoole官方
公众号,可以及时得到项目最新进展、版本更新、活动与聚会、周边开源项目、产品案例等信息。
44 |
当你觉得发现了一个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 |
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 = 16777216
net.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 | 如果修改成功,这里是新设置的值。
进程管理器,基于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 $ipcType
Process\Pool
的$ipc_type
一致【默认为0
表示不使用任何进程间通信特性】0
int $msgQueueKey
key
,和Process\Pool
的$msgqueue_key
一致设置工作进程之间的通信方式。
72 |Swoole\Process\Manager->setIPCType(int $ipcType): self;
callable $func
bool $enableCoroutine
批量增加工作进程。
117 |Swoole\Process\Manager->addBatch(int $workerNum, callable $func, bool $enableCoroutine = false): self
!> 本页面由 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\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
功能需要自己监听信号来做逻辑。这里是对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/server12init.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 方便的创建一个异步服务器程序,支持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/server12pipemessage_class.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 这里是对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/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();
Server->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->taskworker
这里是对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\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/server12task_class.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 这里是对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): bool
mixed $data
56 | Swoole\Server\Task->finish
函数必须为Server
设置onFinish回调函数。此函数只可用于 Task进程的onTask回调中
71 |
72 | 将给定的数据序列化。
74 |Swoole\Server\Task->pack(mixed $data): string|false
mixed $data
78 | string $data
95 | 这里是对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/server12tcp_init.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 此节包含Swoole\Server
类的全部方法、属性、配置项以及所有的事件。Swoole\Server
类是所有异步风格服务器的基类,后面章节的Swoole\Http\Server
、Swoole\WebSocket\Server
、Swoole\Redis\Server
都是它的子类。
!> 建议先查看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/start12start_http_server.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 请将以下代码写入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.php
http://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 | });
通过设置open_mqtt_protocol选项,启用后会解析MQTT
包头,Worker 进程的onReceive事件每次会返回一个完整的MQTT
数据包。
18 | 可以使用 Swoole 作为 MQTT 服务端或客户端,实现一套完整物联网(IOT)解决方案。
20 |89 | 90 | -------------------------------------------------------------------------------- /Swoole.docset/Contents/Resources/Documents/start12start_server.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
的绝大部分功能只能用于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
环境下。
在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()
操作。
请将以下代码写入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数据包边界问题。
请将以下代码写入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
请将以下代码写入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。
Server
默认运行模式为SWOOLE_BASE
PHP
版本要求提高到8.0
PSR-0
的类别名,仅保留命名空间风格类名,如swoole_server
必须修改为Swoole\Server
Swoole\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_sigtimedwait
Event::rshutdown()
标记为已弃用,请改用 Coroutine\run
39 |
40 | SWOOLE_HOOK_ALL
包括 SWOOLE_HOOK_CURL
ssl_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
保持一致, 所有属性名修改为复数形式, 涉及以下属性
headers
cookies
87 |
88 | 90 |由于底层实现过于复杂, 难以维护, 且用户经常对其使用产生误区, 故暂时删除以下API:
91 |
Coroutine\Channel::select
93 | 但同时增加了Coroutine\Channel->pop
方法的第二参数为timeout
来满足开发需求
94 |
95 | 97 |由于协程内核升级, 可以在任意函数任意地方调用协程, 无需做特殊处理, 故删除了以下API
98 |
Coroutine::call_user_func
Coroutine::call_user_func_array
分支 | 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
:支持多线程模式。