swoole是运行在PHP下的一个extesion扩展,实际上与普通的扩展不同。普通的扩展只是提供一个库函数。而swoole扩展在运行后会接管PHP的控制权,进入事件循环。当IO事件发生后,swoole会自动回调指定的PHP函数。
PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
Swoole可以广泛应用于互联网、移动通信、企业软件、网络游戏、物联网、车联网、智能家庭等领域。 使用PHP+Swoole作为网络通信框架,可以使企业IT研发团队的效率大大提升,更加专注于开发创新产品。
Swoole底层内置了异步非阻塞、多线程的网络IO服务器。PHP程序员仅需处理事件回调即可,无需关心底层。与Nginx/Tornado/Node.js等全异步的框架不同,Swoole既支持全异步,也支持同步。
Swoole是开源免费的自由软件,授权协议是Apache2.0。企业和个人开发者均可免费使用Swoole的代码,并且在Swoole之上所作的修改可用于商业产品,无需开源(注:必须保留原作者的版权声明)。
swoole使用纯C编写,不依赖其他第三方库。
swoole使用底层的socket系统调用。参见 sys/socket.h
有很多人使用strace -p去查看swoole主进程只能看到poll系统调用。正确的查看方法是strace -f -p
Swoole中使用了eventfd作为线程/进程间消息通知的机制。
Swoole使用timerfd来实现定时器
swoole中使用了signalfd来实现对信号的屏蔽和处理。可以有效地避免线程/进程被信号打断,系统调用restart的问题。在主进程中reactor线程不会接受任何信号。
1.8.7或更高版本已完全兼容PHP7
强大的TCP/UDP Server框架,多线程,EventLoop,事件驱动,异步,Worker进程组,Task异步任务,毫秒定时器,SSL/TLS隧道加密。
TCP/UDP客户端,支持同步并发调用,也支持异步事件驱动。
EventLoop API,让用户可以直接操作底层的事件循环,将socket,stream,管道等Linux文件加入到事件循环中。
eventloop接口仅可用于socket类型的文件描述符,不能用于磁盘文件读写
异步IO接口,提供了 异步文件系统IO,异步DNS查询,异步MySQL等API。包括2个重要的子模块:
进程管理模块,可以方便的创建子进程,进程间通信,进程管理。
强大的内存区管理工具,像C一样进行指针计算,又无需关心内存的申请和释放,而且不用担心内存越界,底层全部做好了。
基于共享内存和自旋锁实现的超高性能内存表。彻底解决线程,进程间数据共享,加锁同步等问题。
swoole_table的性能可以达到单线程每秒读写50W次
swoole-1.7.7增加了对cygwin环境的支持,在Windows环境下,可以直接使用cygwin + php 来跑swoole程序。
cygwin模式下需要对PHP进行简化,去掉不使用的扩展,避免进程占用内存过大,导致Fork操作失败
Windows 10系统增加了Linux子系统支持,BashOnWindows环境下也可以使用swoole
Swoole扩展是按照php标准扩展构建的。使用phpize来生成php编译配置,./configure来做编译配置检测,make进行编译,make install进行安装。
安装swoole前必须保证系统已经安装了下列软件
php-5.3.10 或更高版本gcc-4.4 或更高版本makeautoconf
下载源代码包后,在终端进入源码目录,执行下面的命令进行编译和安装
cd swoolephpize./configuremake sudo make install
(注:swoole的./configure有很多额外参数,可以通过./configure --help命令查看,这里仅开启其中async-mysql项,其他均选择默认项) 这里是./configure编译配置的额外参数,用于开启某些特性
swoole项目已收录到PHP官方扩展库,除了手工下载编译外,还可以通过PHP官方提供的pecl命令,一键下载安装swoole
pecl install swoole
编译安装成功后,修改php.ini加入
extension=swoole.so
通过php -m或phpinfo()来查看是否成功加载了swoole,如果没有可能是php.ini的路径不对,可以使用php -i |grep php.ini来定位到php.ini的绝对路径。
NOTICE: PHP message: PHP Warning: PHP Startup: swoole: Unable to initialize module
Module compiled with module API=20090626
PHP compiled with module API=20121212
These options need to match
in Unknown on line 0
php版本和编译时使用的phpize和php-config不对应,需要使用绝对路径来进行编译。使用绝对路径执行PHP。
/usr/local/php-5.4.17/bin/phpize./configure --with-php-config=/usr/local/php-5.4.17/bin/php-config/usr/local/php-5.4.17/bin/php server.php
php_mysqli_structs.h:64:23: fatal error: my_global.h: No such file or directory
没有找到mysqlclient的头文件,需要安装mysqlclient-dev
建议自行编译php,不要使用Linux包管理系统自带的php版本
fatal error: pcre.h: No such file or directory
原因是缺少pcre,需要安装libpcre
phpize命令需要autoconf工具,请先安装它。
make install需要root权限,如果不是以root用户登录的,请用sudo或su,再进行安装。
php -i|grep php.ini
查看加载的php.ini路径,确认加载了正确的php.ini。
修改php.ini,打开错误显示,查看是否存在启动时错误。
display_errors => On display_startup_errors => On
你的PHP版本低于PHP-5.3.10,请升级PHP版本。
不要气馁,加入我们的开发组QQ群:495864936,你的问题会在24小时内被解决。
下面贴一个基本的基于swoole的echo服务器
// Serverclass Server{ private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9501); $this->serv->set(array( 'worker_num' => 8, 'daemonize' => false, 'max_request' => 10000, 'dispatch_mode' => 2, 'debug_mode'=> 1 )); $this->serv->on('Start', array($this, 'onStart')); $this->serv->on('Connect', array($this, 'onConnect')); $this->serv->on('Receive', array($this, 'onReceive')); $this->serv->on('Close', array($this, 'onClose')); $this->serv->start(); } public function onStart( $serv ) { echo "Start
"; } public function onConnect( $serv, $fd, $from_id ) { $serv->send( $fd, "Hello {$fd}!" ); } public function onReceive( swoole_server $serv, $fd, $from_id, $data ) { echo "Get Message From Client {$fd}:{$data}
"; } public function onClose( $serv, $fd, $from_id ) { echo "Client {$fd} close connection
"; }}// 启动服务器$server = new Server();
从代码中可以看出,创建一个swoole_server基本分三步: 1. 通过构造函数创建swoole_server对象 2. 调用set函数设置swoole_server的相关配置选项 3. 调用on函数设置相关回调函数 关于set配置选项以及on回调函数的具体说明,请参考我整理的swoole文档( 配置选项)
这里只给出简单介绍。onStart回调在server运行前被调用,onConnect在有新客户端连接过来时被调用,onReceive函数在有数据发送到server时被调用,onClose在有客户端断开连接时被调用。 这里就可以大概看出如何使用swoole:在onConnect处监听新的连接;在onReceive处接收数据并处理,然后可以调用send函数将处理结果发送出去;在onClose处处理客户端下线的事件。
下面贴出客户端的代码:
<?phpclass Client{ private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP); } public function connect() { if( !$this->client->connect("127.0.0.1", 9501 , 1) ) { echo "Error: {$fp->errMsg}[{$fp->errCode}]
"; } $message = $this->client->recv(); echo "Get Message From Server:{$message}
"; fwrite(STDOUT, "请输入消息:"); $msg = trim(fgets(STDIN)); $this->client->send( $msg ); }}$client = new Client();$client->connect();
这里,通过swoole_client创建一个基于TCP的客户端实例,并调用connect函数向指定的IP及端口发起连接请求。随后即可通过recv()和send()两个函数来接收和发送请求。需要注意的是,这里我使用了默认的同步阻塞客户端,因此recv和send操作都会产生网络阻塞。
这个频道内会详细介绍异步编程与同步编程的不同之处以及需要注意的事项。
新手非常容易犯这个错误,由于swoole是常驻内存的,所以加载类/函数定义的文件后不会释放。因此引入类/函数的php文件时必须要使用include_once或require_once,否会发生cannot redeclare function/class 的致命错误。
PHP守护进程与普通Web程序的变量生命周期、内存管理方式完全不同。请参考 swoole_server内存管理 页面。编写swoole_server或其他常驻进程时需要特别注意。
进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因就是全局变量在不同的进程,内存空间是隔离的,所以无效。所以使用swoole开发Server程序需要了解进程隔离问题。
在异步IO的程序中,不得使用sleep/usleep/time_sleep_until/time_nanosleep。(下文中使用sleep泛指所有睡眠函数)
swoole提供的swoole_event_add、swoole_timer_tick、swoole_timer_after、swoole_process::signal、异步swoole_client 在进程sleep后会停止工作。swoole_server也无法再处理新的请求。
$serv = new swoole_server("127.0.0.1", 9501);$serv->on('receive', function ($serv, $fd, $from_id, $data) { sleep(100); $serv->send($fd, 'Swoole: '.$data);});$serv->start();
onReceive事件中执行了sleep函数,server在100秒内无法再收到任何客户端请求。
在swoole程序中禁止使用exit/die,如果PHP代码中有exit/die,当前工作的Worker进程、Task进程、User进程、以及swoole_process进程会立即退出。
建议使用try/catch的方式替换exit/die,实现中断执行跳出PHP函数调用栈。
function swoole_exit($msg){ //php-fpm的环境 if (ENV=='php') { exit($msg); } //swoole的环境 else { throw new SwooleExitException($msg); }}
异常处理的方式比exit/die更友好,因为异常是可控的,exit/die不可控。在最外层进行try/catch即可捕获异常,仅终止当前的任务。Worker进程可以继续处理新的请求,而exit/die会导致进程直接退出,当前进程保存的所有变量和资源都会被销毁。如果进程内还有其他任务要处理,遇到exit/die也将全部丢弃。
异步程序如果遇到死循环,事件将无法触发。异步IO程序使用Reactor模型,运行过程中必须在reactor->wait处轮询。如果遇到死循环,那么程序的控制权就在while中了,reactor无法得到控制权,无法检测事件,所以IO事件回调函数也将无法触发。
密集运算的代码不是阻塞
$serv = new swoole_server("127.0.0.1", 9501);$serv->on('receive', function ($serv, $fd, $from_id, $data) { while(1) { $i ++; } $serv->send($fd, 'Swoole: '.$data);});$serv->start();
onReceive事件中执行了死循环,server在无法再收到任何客户端请求,必须等待循环结束才能继续处理新的事件。
ulimit -n 要调整为100000甚至更大。 命令行下执行 ulimit -n 100000即可修改。如果不能修改,需要设置 /etc/security/limits.conf,加入
* soft nofile 262140* hard nofile 262140root soft nofile 262140root hard nofile 262140* soft core unlimited* hard core unlimitedroot soft core unlimitedroot hard core unlimited
swoole使用unix socket dgram来做进程间通信,如果请求量很大,需要调整此参数。系统默认为10,可以设置为100或者更大。
或者增加worker进程的数量,减少单个worker进程分配的请求量。
修改此参数增加socket缓存区的内存大小
net.ipv4.tcp_mem = 379008 505344 758016net.ipv4.tcp_wmem = 4096 16384 4194304net.ipv4.tcp_rmem = 4096 87380 4194304net.core.wmem_default = 8388608net.core.rmem_default = 8388608net.core.rmem_max = 16777216net.core.wmem_max = 16777216
是否socket reuse,此函数的作用是Server重启时可以快速重新使用监听的端口。如果没有设置此参数,会导致server重启时发生端口未及时释放而启动失败
使用socket快速回收,短连接Server需要开启此参数
当使用消息队列作为进程间通信方式时,需要调整此内核参数
设置内核参数
kernel.core_pattern = /data/core_files/core-%e-%p-%t
通过ulimit -c命令查看当前coredump文件的限制
ulimit -c
如果为0,需要修改/etc/security/limits.conf,进行limit设置。
开启core-dump后,一旦程序发生异常,会将进程导出到文件。对于调查程序问题有很大的帮助
如:修改net.unix.max_dgram_qlen = 100后,通过
cat /proc/sys/net/unix/max_dgram_qlen
如果修改成功,这里是新设置的值。
在swoole中,一个swoole_server的相关属性可以通过
$serv->set( $array configs );
函数来配置,这些配置选项使得swoole更加灵活。 示例:
$serv = new swoole_server("0.0.0.0", 9501);$serv->set(array( 'worker_num' => 8, 'max_request' => 10000, 'max_conn' => 100000, 'dispatch_mode' => 2, 'debug_mode'=> 1, 'daemonize' => false,));
配置选项以及相关介绍如下:
描述:指定启动的worker进程数。
说明:swoole是master-> n * worker的模式,开启的worker进程数越多,server负载能力越大,但是相应的server占有的内存也会更多。同时,当worker进程数过多时,进程间切换带来的系统开销也会更大。因此建议开启的worker进程数为cpu核数的1-4倍。
示例:
'worker_num' => 8
描述:每个worker进程允许处理的最大任务数。
说明:设置该值后,每个worker进程在处理完max_request个请求后就会自动重启。设置该值的主要目的是为了防止worker进程处理大量请求后可能引起的内存溢出。
示例:
'max_request' => 10000
描述:服务器允许维持的最大TCP连接数
说明:设置此参数后,当服务器已有的连接数达到该值时,新的连接会被拒绝。另外,该参数的值不能超过操作系统ulimit -n的值,同时此值也不宜设置过大,因为swoole_server会一次性申请一大块内存用于存放每一个connection的信息。
示例:
'max_conn' => 10000
描述:设置进程间的通信方式。
说明:共有三种通信方式,参数如下:
- 1 => 使用unix socket通信
- 2 => 使用消息队列通信
- 3 => 使用消息队列通信,并设置为争抢模式
示例:
'ipc_mode' => 1
描述:指定数据包分发策略。
说明:共有三种模式,参数如下:
- 1 => 轮循模式,收到会轮循分配给每一个worker进程
- 2 => 固定模式,根据连接的文件描述符分配worker。这样可以保证同一个连接发来的数据只会被同一个worker处理
- 3 => 抢占模式,主进程会根据Worker的忙闲状态选择投递,只会投递给处于闲置状态的Worker
示例:
'dispatch_mode' => 2
描述:服务器开启的task进程数。
说明:设置此参数后,服务器会开启异步task功能。此时可以使用task方法投递异步任务。
设置此参数后,必须要给swoole_server设置onTask/onFinish两个回调函数,否则启动服务器会报错。
示例:
'task_worker_num' => 8
描述:每个task进程允许处理的最大任务数。
说明:参考max_request task_worker_num
示例:
'task_max_request' => 10000
描述:设置task进程与worker进程之间通信的方式。
说明:参考ipc_mode
示例:
'task_ipc_mode' => 2
描述:设置程序进入后台作为守护进程运行。
说明:长时间运行的服务器端程序必须启用此项。如果不启用守护进程,当ssh终端退出后,程序将被终止运行。启用守护进程后,标准输入和输出会被重定向到 log_file,如果 log_file未设置,则所有输出会被丢弃。
示例:
'daemonize' => 0
描述:指定日志文件路径
说明:在swoole运行期发生的异常信息会记录到这个文件中。默认会打印到屏幕。注意log_file 不会自动切分文件,所以需要定期清理此文件。
示例:
'log_file' => '/data/log/swoole.log'
描述:设置心跳检测间隔
说明:此选项表示每隔多久轮循一次,单位为秒。每次检测时遍历所有连接,如果某个连接在间隔时间内没有数据发送,则强制关闭连接(会有onClose回调)。
示例:
'heartbeat_check_interval' => 60
描述:设置某个连接允许的最大闲置时间。
说明:该参数配合heartbeat_check_interval使用。每次遍历所有连接时,如果某个连接在heartbeat_idle_time时间内没有数据发送,则强制关闭连接。默认设置为heartbeat_check_interval * 2。
示例:
'heartbeat_idle_time' => 600
描述:打开eof检测功能
说明:与package_eof 配合使用。此选项将检测客户端连接发来的数据,当数据包结尾是指定的package_eof 字符串时才会将数据包投递至Worker进程,否则会一直拼接数据包直到缓存溢出或超时才会终止。一旦出错,该连接会被判定为恶意连接,数据包会被丢弃并强制关闭连接。
EOF检测不会从数据中间查找eof字符串,所以Worker进程可能会同时收到多个数据包,需要在应用层代码中自行explode(" ", $data) 来拆分数据包
示例:
'open_eof_check' => true
描述:设置EOF字符串
说明:package_eof最大只允许传入8个字节的字符串
示例:
'package_eof ' => '/r/n'
描述:打开包长检测
说明:包长检测提供了固定包头+包体这种格式协议的解析,。启用后,可以保证Worker进程onReceive每次都会收到一个完整的数据包。
示例:
'open_length_check' => true
描述:包头中第几个字节开始存放了长度字段
说明:配合open_length_check使用,用于指明长度字段的位置。
示例:
'package_length_offset' => 5
描述:从第几个字节开始计算长度。
说明:配合open_length_check使用,用于指明包头的长度。
示例:
'package_body_offset' => 10
描述:指定包长字段的类型
说明:配合open_length_check使用,指定长度字段的类型,参数如下:
- 's' => int16_t 机器字节序
- 'S' => uint16_t 机器字节序
- 'n' => uint16_t 大端字节序
- ’N‘ => uint32_t 大端字节序
- 'L' => uint32_t 机器字节序
- 'l' => int 机器字节序
示例:
'package_length_type' => 'N'
描述:设置最大数据包尺寸
说明:该值决定了数据包缓存区的大小。如果缓存的数据超过了该值,则会引发错误。具体错误处理由开启的协议解析的类型决定。
示例:
'package_max_length' => 8192
描述:启用CPU亲和性设置
说明:在多核的硬件平台中,启用此特性会将swoole的reactor线程/worker进程绑定到固定的一个核上。可以避免进程/线程的运行时在多个核之间互相切换,提高CPU Cache的命中率。
示例:
'open_cpu_affinity' => true
描述:启用open_tcp_nodelay
说明:开启后TCP连接发送数据时会无关闭Nagle合并算法,立即发往客户端连接。在某些场景下,如http服务器,可以提升响应速度。
示例:
'open_tcp_nodelay' => true
描述:启用tcp_defer_accept特性
说明:启动后,只有一个TCP连接有数据发送时才会触发accept。
示例:
'tcp_defer_accept' => true
描述:设置SSL隧道加密
说明:设置值为一个文件名字符串,指定cert证书和key的路径。
示例:
'ssl_cert_file' => '/config/ssl.crt','ssl_key_file' => '/config//ssl.key',
描述:打开TCP的KEEP_ALIVE选项
说明:使用TCP内置的keep_alive属性,用于保证连接不会因为长时闲置而被关闭。
示例:
'open_tcp_keepalive' => true
描述:指定探测间隔。
说明:配合open_tcp_keepalive使用,如果某个连接在tcp_keepidle内没有任何数据来往,则进行探测。
示例:
'tcp_keepidle' => 600
描述:指定探测时的发包间隔
说明:配合open_tcp_keepalive使用
示例:
'tcp_keepinterval' => 60
描述:指定探测的尝试次数
说明:配合open_tcp_keepalive使用,若tcp_keepcount次尝试后仍无响应,则判定连接已关闭。
示例:
'tcp_keepcount' => 5
描述:指定Listen队列长度
说明:此参数将决定最多同时有多少个等待accept的连接。
示例:
'backlog' => 128
描述:指定Reactor线程数
说明:设置主进程内事件处理线程的数量,默认会启用CPU核数相同的数量, 一般设置为CPU核数的1-4倍,最大不得超过CPU核数*4。
示例:
'reactor_num' => 8
描述:设置task的数据临时目录
说明:在swoole_server中,如果投递的数据超过8192字节,将启用临时文件来保存数据。这里的task_tmpdir就是用来设置临时文件保存的位置。
需要swoole-1.7.7+
示例:
'task_tmpdir' => '/tmp/task/'
除以上29个选项外,还有几个不常用或者已废弃的选项没有给出。以后如果有实际需求,会补充说明。
在swoole中,一个swoole_server的拥有若干回调函数,这些回调函数决定了swoole_server的相关功能。可以通过
$serv->on( string $event, mixed $callback );
函数来配置. 示例:
class Server{ private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9501); $this->serv->set(array( 'worker_num' => 8, 'daemonize' => false, 'max_request' => 10000, 'dispatch_mode' => 2, 'debug_mode'=> 1 )); $this->serv->on('Start', array($this, 'onStart')); $this->serv->on('Connect', array($this, 'onConnect')); $this->serv->on('Receive', array($this, 'onReceive')); $this->serv->on('Close', array($this, 'onClose')); $this->serv->start(); }}
全部回调函数以及相关介绍如下:
[TOC]
描述:接收数据的回调
函数原型:
function onReceive( swoole_server $serv, $fd, $from_id, $data );
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$fd | 连接的描述符 |
$from_id | reactor的id,无用 |
$data | 接收到的数据 |
说明:每当server接收到客户端发来的数据后,就会通过onReceive回调将数据投递给Worker。如果开启了协议检测,则会在收到完整数据包之后才会响应回调。注意,必须设置该回调函数,否则无法启动服务器。
描述:服务器启动的回调
函数原型:
function onStart( swoole_server $serv);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
说明:
onStart事件在Master进程的主线程中被调用。在此回调响应之前Swoole Server已进行了如下操作
接下来要执行
onStart回调中,仅允许echo、打印Log、修改进程名称。不得执行其他操作。onWorkerStart和onStart回调是在不同进程中并行执行的,不存在先后顺序。 可以在onStart回调中,将$serv->master_pid和$serv->manager_pid的值保存到一个文件中。这样可以编写脚本,向这两个PID发送信号来实现关闭和重启的操作。
从1.7.5+ Master进程内不再支持定时器,onMasterConnect/onMasterClose2个事件回调也彻底移除。Master进程内不再保留任何PHP的接口。
在onStart中创建的全局资源对象不能在worker进程中被使用,因为发生onStart调用时,worker进程已经创建好了。新创建的对象在主进程内,worker进程无法访问到此内存区域,因此全局对象创建的代码需要放置在swoole_server_start之前。
描述:Worker进程启动的回调
函数原型:
function onWorkerStart( swoole_server $serv,int $worker_id);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$worker_id | Worker进程的id |
说明:此事件在worker进程/task_worker启动时发生。
发生PHP致命错误或者代码中主动调用exit时,Worker/Task进程会退出,管理进程会重新创建新的进程 onWorkerStart/onStart是并发执行的,没有先后顺序
通过$worker_id参数的值来,判断worker是普通worker还是task_worker。$worker_id>= $serv->setting['worker_num'] 时表示这个进程是task_worker。
如果想使用swoole_server_reload实现代码重载入,必须在workerStart中require你的业务文件,而不是在文件头部。在onWorkerStart调用之前已包含的文件,不会重新载入代码。
可以将公用的,不易变的php文件放置到onWorkerStart之前。这样虽然不能重载入代码,但所有worker是共享的,不需要额外的内存来保存这些数据。
onWorkerStart之后的代码每个worker都需要在内存中保存一份 $worker_id是一个从0-$worker_num之间的数字,表示这个worker进程的ID $worker_id和进程PID没有任何关系
描述:新连接接入时的回调
函数原型:
function onConnect( swoole_server $serv,int $fd, int $from_id);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$fd | 连接的描述符 |
$from_id | reactor的id,无用 |
说明:有新的连接进入时,在worker进程中回调。onConnect/onClose这2个回调发生在worker进程内,而不是主进程。如果需要在主进程处理连接/关闭事件,请注册onMasterConnect/onMasterClose回调。onMasterConnect/onMasterClose回调总是先于onConnect/onClose被执行
描述:连接关闭时的回调
函数原型:
function onClose( swoole_server $serv,int $fd, int $from_id);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$fd | 连接的描述符 |
$from_id | reactor的id,无用 |
说明:TCP客户端连接关闭后,在worker进程中回调此函数。无论close由客户端发起还是服务器端主动调用swoole_server_close关闭连接,都会触发此事件。 因此只要连接关闭,就一定会回调此函数。
描述:task_worker进程处理任务的回调
函数原型:
function onTask(swoole_server $serv, int $task_id, int $from_id, string $data);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$task_id | 任务ID |
$from_id | 来自于哪个worker进程 |
$data | 任务内容 |
说明:在task_worker进程内被调用。worker进程可以使用swoole_server_task函数向task_worker进程投递新的任务。可以直接将任务结果字符串通过return方式返回给worker进程。worker进程将在onFinish回调中收到结果。注:如果serv->set(array('task_worker_num' => 8)) task_id 并不是从1-8 而是递增的。
描述:task_worker进程处理任务结束的回调
函数原型:
function onFinish(swoole_server $serv, int $task_id, string $data);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$task_id | 任务ID |
$data | 任务结果 |
说明:在此函数中会收到任务处理的结果,通过task_id和worker_id来区分不同的任务。
描述:定时器触发的回调
函数原型:
function onTimer(swoole_server $serv, int $interval);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$interval | 定时的间隔 |
说明:定时器被触发时,该函数被调用。通过interval来区分不同时间间隔的定时器。
功能描述:创建一个swoole_server资源对象
函数原型:
// 类成员函数public function swoole_server::__construct(string $host, int $port, int $mode = SWOOLE_PROCESS, int $sock_type = SWOOLE_SOCK_TCP);// 公共函数function swoole_server_create(string $host, int $port, int $mode = SWOOLE_PROCESS, int $sock_type = SWOOLE_SOCK_TCP);
返回:一个swoole_server对象
参数说明:
参数 | 说明 |
---|---|
string host | 监听的IP地址 |
int port | 监听的端口号 |
int mode | 运行模式 |
int sock_type | 指定的socket类型 |
说明: host、port、socket_type的详细说明见swoole_server::addlistener。
mode指定了swoole_server的运行模式,共有如下三种:
mode | 类型 | 说明 |
---|---|---|
SWOOLE_BASE | Base模式 | 传统的异步非阻塞Server。在Reactor内直接回调PHP的函数。如果回调函数中有阻塞操作会导致Server退化为同步模式。worker_num参数对与BASE模式仍然有效,swoole会启动多个Reactor进程 |
SWOOLE_THREAD | 线程模式(已废弃) | 多线程Worker模式,Reactor线程来处理网络事件轮询,读取数据。得到的请求交给Worker线程去处理。多线程模式比进程模式轻量一些,而且线程之间可以共享堆栈和资源。 访问共享内存时会有同步问题,需要使用Swoole提供的锁机制来保护数据。 |
SWOOLE_PROCESS | 进程模式(默认) | Swoole提供了完善的进程管理、内存保护机制。 在业务逻辑非常复杂的情况下,也可以长期稳定运行,适合业务逻辑非常复杂的场景。 |
样例:
$serv = new swoole_server("127.0.0.1" , 8888 , SWOOLE_PROCESS , SWOOLE_SOCK_TCP);
功能描述:设置swoole_server运行时的各项参数
函数原型:
// 类成员函数public function swoole_server::set(array $setting);// 公共函数function swoole_server_set(swoole_server $server, array $setting);
返回:无
参数说明:
参数 | 说明 |
---|---|
array setting | 配置选项数组,采用key-value形式 |
说明:
该函数必须在swoole_server::start函数调用前调用。
全部swoole_server的配置参数点此查看
样例:
$serv->set( array( 'worker_num' => 8, 'max_request' => 10000, 'max_conn' => 100000, 'dispatch_mode' => 2, 'debug_mode'=> 1, 'daemonize' => false, ));
功能描述:绑定swoole_server的相关回调函数
函数原型:
// 类成员函数public function bool swoole_server->on(string $event, mixed $callback);
返回:设置成功返回true,否则返回false
参数说明:
参数 | 说明 |
---|---|
string event | 回调的名称(大小写不敏感) |
mixed callback | 回调的PHP函数,可以是函数名的字符串,类静态方法,对象方法数组,匿名函数 |
说明:
该函数必须在swoole_server::start函数调用前调用。
此方法与swoole_server::handler功能相同,作用是与swoole_client风格保持一致。
swoole_server::on中事件名称字符串不要加on。
全部的回调函数列表点此查看
样例:
$serv->on('connect', function ($serv, $fd){ echo "Client:Connect.
";});$serv->on('receive', array( $myclass, 'onReceive' ) ); // onReceive是myclass的成员函数
功能描述:给swoole_server增加一个监听的地址和端口
函数原型:
// 类成员函数public function swoole_server::addlistener(string $host, int $port, $type = SWOOLE_SOCK_TCP);// 公共函数function swoole_server_addlisten(swoole_server $serv, string $host, int $port, $type = SWOOLE_SOCK_TCP);
返回:无
参数说明:
参数 | 说明 |
---|---|
string host | 监听的IP地址 |
int port | 监听的端口号 |
int sock_type | 指定的socket类型 |
说明: swoole支持如下socket类型:
sock_type | 说明 |
---|---|
SWOOLE_TCP/SWOOLE_SOCK_TCP | TCP IPv4 Socket |
SWOOLE_TCP6/SWOOLE_SOCK_TCP6 | TCP IPv6 Socket |
SWOOLE_UDP/SWOOLE_SOCK_UDP | UDP IPv4 Socket |
SWOOLE_UDP6/SWOOLE_SOCK_UDP6 | UDP IPv4 Socket |
SWOOLE_UNIX_DGRAM | Unix UDP Socket |
SWOOLE_UNIX_STREAM | Unix TCP Socket |
Unix Socket仅在1.7.1+后可用,此模式下host参数必须填写可访问的文件路径,port参数忽略
Unix Socket模式下,客户端fd将不再是数字,而是一个文件路径的字符串
SWOOLE_TCP等是1.7.0+后提供的简写方式,与1.7.0前的SWOOLE_SOCK_TCP是等同的
样例:
$serv->addlistener("127.0.0.1", 9502, SWOOLE_SOCK_TCP);$serv->addlistener("192.168.1.100", 9503, SWOOLE_SOCK_TCP);$serv->addlistener("0.0.0.0", 9504, SWOOLE_SOCK_UDP);$serv->addlistener("/var/run/myserv.sock", 0, SWOOLE_UNIX_STREAM);swoole_server_addlisten($serv, "127.0.0.1", 9502, SWOOLE_SOCK_TCP);
功能描述:设置Server的事件回调函数
函数原型:
// 类成员函数public function swoole_server::handler(string $event_name, mixed $event_callback_function);// 公共函数function swoole_server_handler(swoole_server $serv, string $event_name, mixed $event_callback_function);
返回:设置成功返回true,否则返回false
参数说明:
参数 | 说明 |
---|---|
string event_name | 回调的名称(大小写不敏感) |
mixed event_callback_function | 回调的PHP函数,可以是函数名的字符串,类静态方法,对象方法数组,匿名函数 |
说明: 该函数必须在swoole_server::start函数调用前调用。
事件名称字符串要加on。
全部的回调函数列表点此查看
onConnect/onClose/onReceive这3个回调函数必须设置。其他事件回调函数可选
如果设定了timer定时器,onTimer事件回调函数也必须设置
如果启用了task_worker,onTask/onFinish事件回调函数必须设置
样例:
$serv->handler('onStart', 'my_onStart');$serv->handler('onStart', array($this, 'my_onStart'));$serv->handler('onStart', 'myClass::onStart');
功能描述:启动server,开始监听所有TCP/UDP端口
函数原型:
// 类成员函数public function swoole_server::start()
返回:启动成功返回true,否则返回false
参数说明:无
说明:
启动成功后会创建worker_num+2个进程:Master进程+Manager进程+worker_num 个 Worker进程。
另外。启用task_worker会增加task_worker_num个Worker进程
三种进程的说明如下:
进程类型 | 说明 |
---|---|
Master进程 | 主进程内有多个Reactor线程,基于epoll/kqueue进行网络事件轮询。收到数据后转发到Worker进程去处理 |
Manager进程 | 对所有Worker进程进行管理,Worker进程生命周期结束或者发生异常时自动回收,并创建新的Worker进程 |
Worker进程 | 对收到的数据进行处理,包括协议解析和响应请求 |
样例:
$serv->start();
功能描述:重启所有worker进程。
函数原型:
// 类成员函数public function swoole_server::reload()
返回:调用成功返回true,否则返回false
参数说明:无
说明:
调用后会向Manager发送一个SIGUSR1信号,平滑重启全部的Worker进程(所谓平滑重启,是指重启动作会在Worker处理完正在执行的任务后发生,并不会中断正在运行的任务。)
小技巧:在onWorkerStart回调中require相应的php文件,当这些文件被修改后,只需要通过SIGUSR1信号即可实现服务器热更新。
1.7.7版本增加了仅重启task_worker的功能。只需向服务器发送SIGUSR2即可
样例:
$serv->reload();
功能描述:关闭服务器。
函数原型:
// 类成员函数public function swoole_server::shutdown()
返回:调用成功返回true,否则返回false
参数说明:无
说明:
此函数可以用在worker进程内,平滑关闭全部的Worker进程。
也可向Master进程发送SIGTERM信号关闭服务器。
样例:
$serv->shutdown();
功能描述:设置一个固定间隔的定时器
函数原型:
// 类成员函数public function swoole_server::addtimer(int $interval);// 公共函数function swoole_server_addtimer(swoole_server $serv, int $interval);
返回:设置成功返回true,否则返回false
参数说明:
参数 | 说明 |
---|---|
int interval | 定时器的时间间隔,单位为毫秒ms |
说明:
swoole定时器的最小颗粒是1毫秒,支持多个不同间隔的定时器。
注意不能存在2个相同间隔时间的定时器。
使用多个定时器时,其他定时器必须为最小定时器时间间隔的整数倍。
该函数只能在onWorkerStart/onConnect/onReceive/onClose回调函数中调用。
增加定时器后需要为Server设置onTimer回调函数,否则Server将无法启动。
样例:
$serv->addtimer(1000); //1sswoole_server_addtimer($serv,20); //20ms
功能描述:删除指定的定时器。
函数原型:
// 类成员函数public function swoole_server::deltimer(int $interval);
返回:无
参数说明:
参数 | 说明 |
---|---|
int interval | 定时器的时间间隔,单位为毫秒ms |
说明:
删除间隔为interval的定时器
样例:
$serv->deltimer(1000);
功能描述:在指定的时间后执行函数
函数原型:
// 类成员函数public function swoole_server::after(int $after_time_ms, mixed $callback_function, mixed params);// 公共函数function swoole_timer_after<
swoole是运行在PHP下的一个extesion扩展,实际上与普通的扩展不同。普通的扩展只是提供一个库函数。而swoole扩展在运行后会接管PHP的控制权,进入事件循环。当IO事件发生后,swoole会自动回调指定的PHP函数。
PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
Swoole可以广泛应用于互联网、移动通信、企业软件、网络游戏、物联网、车联网、智能家庭等领域。 使用PHP+Swoole作为网络通信框架,可以使企业IT研发团队的效率大大提升,更加专注于开发创新产品。
Swoole底层内置了异步非阻塞、多线程的网络IO服务器。PHP程序员仅需处理事件回调即可,无需关心底层。与Nginx/Tornado/Node.js等全异步的框架不同,Swoole既支持全异步,也支持同步。
Swoole是开源免费的自由软件,授权协议是Apache2.0。企业和个人开发者均可免费使用Swoole的代码,并且在Swoole之上所作的修改可用于商业产品,无需开源(注:必须保留原作者的版权声明)。
swoole使用纯C编写,不依赖其他第三方库。
swoole使用底层的socket系统调用。参见 sys/socket.h
有很多人使用strace -p去查看swoole主进程只能看到poll系统调用。正确的查看方法是strace -f -p
Swoole中使用了eventfd作为线程/进程间消息通知的机制。
Swoole使用timerfd来实现定时器
swoole中使用了signalfd来实现对信号的屏蔽和处理。可以有效地避免线程/进程被信号打断,系统调用restart的问题。在主进程中reactor线程不会接受任何信号。
1.8.7或更高版本已完全兼容PHP7
强大的TCP/UDP Server框架,多线程,EventLoop,事件驱动,异步,Worker进程组,Task异步任务,毫秒定时器,SSL/TLS隧道加密。
TCP/UDP客户端,支持同步并发调用,也支持异步事件驱动。
EventLoop API,让用户可以直接操作底层的事件循环,将socket,stream,管道等Linux文件加入到事件循环中。
eventloop接口仅可用于socket类型的文件描述符,不能用于磁盘文件读写
异步IO接口,提供了 异步文件系统IO,异步DNS查询,异步MySQL等API。包括2个重要的子模块:
进程管理模块,可以方便的创建子进程,进程间通信,进程管理。
强大的内存区管理工具,像C一样进行指针计算,又无需关心内存的申请和释放,而且不用担心内存越界,底层全部做好了。
基于共享内存和自旋锁实现的超高性能内存表。彻底解决线程,进程间数据共享,加锁同步等问题。
swoole_table的性能可以达到单线程每秒读写50W次
swoole-1.7.7增加了对cygwin环境的支持,在Windows环境下,可以直接使用cygwin + php 来跑swoole程序。
cygwin模式下需要对PHP进行简化,去掉不使用的扩展,避免进程占用内存过大,导致Fork操作失败
Windows 10系统增加了Linux子系统支持,BashOnWindows环境下也可以使用swoole
Swoole扩展是按照php标准扩展构建的。使用phpize来生成php编译配置,./configure来做编译配置检测,make进行编译,make install进行安装。
安装swoole前必须保证系统已经安装了下列软件
php-5.3.10 或更高版本gcc-4.4 或更高版本makeautoconf
下载源代码包后,在终端进入源码目录,执行下面的命令进行编译和安装
cd swoolephpize./configuremake sudo make install
(注:swoole的./configure有很多额外参数,可以通过./configure --help命令查看,这里仅开启其中async-mysql项,其他均选择默认项) 这里是./configure编译配置的额外参数,用于开启某些特性
swoole项目已收录到PHP官方扩展库,除了手工下载编译外,还可以通过PHP官方提供的pecl命令,一键下载安装swoole
pecl install swoole
编译安装成功后,修改php.ini加入
extension=swoole.so
通过php -m或phpinfo()来查看是否成功加载了swoole,如果没有可能是php.ini的路径不对,可以使用php -i |grep php.ini来定位到php.ini的绝对路径。
NOTICE: PHP message: PHP Warning: PHP Startup: swoole: Unable to initialize module
Module compiled with module API=20090626
PHP compiled with module API=20121212
These options need to match
in Unknown on line 0
php版本和编译时使用的phpize和php-config不对应,需要使用绝对路径来进行编译。使用绝对路径执行PHP。
/usr/local/php-5.4.17/bin/phpize./configure --with-php-config=/usr/local/php-5.4.17/bin/php-config/usr/local/php-5.4.17/bin/php server.php
php_mysqli_structs.h:64:23: fatal error: my_global.h: No such file or directory
没有找到mysqlclient的头文件,需要安装mysqlclient-dev
建议自行编译php,不要使用Linux包管理系统自带的php版本
fatal error: pcre.h: No such file or directory
原因是缺少pcre,需要安装libpcre
phpize命令需要autoconf工具,请先安装它。
make install需要root权限,如果不是以root用户登录的,请用sudo或su,再进行安装。
php -i|grep php.ini
查看加载的php.ini路径,确认加载了正确的php.ini。
修改php.ini,打开错误显示,查看是否存在启动时错误。
display_errors => On display_startup_errors => On
你的PHP版本低于PHP-5.3.10,请升级PHP版本。
不要气馁,加入我们的开发组QQ群:495864936,你的问题会在24小时内被解决。
下面贴一个基本的基于swoole的echo服务器
// Serverclass Server{ private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9501); $this->serv->set(array( 'worker_num' => 8, 'daemonize' => false, 'max_request' => 10000, 'dispatch_mode' => 2, 'debug_mode'=> 1 )); $this->serv->on('Start', array($this, 'onStart')); $this->serv->on('Connect', array($this, 'onConnect')); $this->serv->on('Receive', array($this, 'onReceive')); $this->serv->on('Close', array($this, 'onClose')); $this->serv->start(); } public function onStart( $serv ) { echo "Start
"; } public function onConnect( $serv, $fd, $from_id ) { $serv->send( $fd, "Hello {$fd}!" ); } public function onReceive( swoole_server $serv, $fd, $from_id, $data ) { echo "Get Message From Client {$fd}:{$data}
"; } public function onClose( $serv, $fd, $from_id ) { echo "Client {$fd} close connection
"; }}// 启动服务器$server = new Server();
从代码中可以看出,创建一个swoole_server基本分三步: 1. 通过构造函数创建swoole_server对象 2. 调用set函数设置swoole_server的相关配置选项 3. 调用on函数设置相关回调函数 关于set配置选项以及on回调函数的具体说明,请参考我整理的swoole文档( 配置选项)
这里只给出简单介绍。onStart回调在server运行前被调用,onConnect在有新客户端连接过来时被调用,onReceive函数在有数据发送到server时被调用,onClose在有客户端断开连接时被调用。 这里就可以大概看出如何使用swoole:在onConnect处监听新的连接;在onReceive处接收数据并处理,然后可以调用send函数将处理结果发送出去;在onClose处处理客户端下线的事件。
下面贴出客户端的代码:
<?phpclass Client{ private $client; public function __construct() { $this->client = new swoole_client(SWOOLE_SOCK_TCP); } public function connect() { if( !$this->client->connect("127.0.0.1", 9501 , 1) ) { echo "Error: {$fp->errMsg}[{$fp->errCode}]
"; } $message = $this->client->recv(); echo "Get Message From Server:{$message}
"; fwrite(STDOUT, "请输入消息:"); $msg = trim(fgets(STDIN)); $this->client->send( $msg ); }}$client = new Client();$client->connect();
这里,通过swoole_client创建一个基于TCP的客户端实例,并调用connect函数向指定的IP及端口发起连接请求。随后即可通过recv()和send()两个函数来接收和发送请求。需要注意的是,这里我使用了默认的同步阻塞客户端,因此recv和send操作都会产生网络阻塞。
这个频道内会详细介绍异步编程与同步编程的不同之处以及需要注意的事项。
新手非常容易犯这个错误,由于swoole是常驻内存的,所以加载类/函数定义的文件后不会释放。因此引入类/函数的php文件时必须要使用include_once或require_once,否会发生cannot redeclare function/class 的致命错误。
PHP守护进程与普通Web程序的变量生命周期、内存管理方式完全不同。请参考 swoole_server内存管理 页面。编写swoole_server或其他常驻进程时需要特别注意。
进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因就是全局变量在不同的进程,内存空间是隔离的,所以无效。所以使用swoole开发Server程序需要了解进程隔离问题。
在异步IO的程序中,不得使用sleep/usleep/time_sleep_until/time_nanosleep。(下文中使用sleep泛指所有睡眠函数)
swoole提供的swoole_event_add、swoole_timer_tick、swoole_timer_after、swoole_process::signal、异步swoole_client 在进程sleep后会停止工作。swoole_server也无法再处理新的请求。
$serv = new swoole_server("127.0.0.1", 9501);$serv->on('receive', function ($serv, $fd, $from_id, $data) { sleep(100); $serv->send($fd, 'Swoole: '.$data);});$serv->start();
onReceive事件中执行了sleep函数,server在100秒内无法再收到任何客户端请求。
在swoole程序中禁止使用exit/die,如果PHP代码中有exit/die,当前工作的Worker进程、Task进程、User进程、以及swoole_process进程会立即退出。
建议使用try/catch的方式替换exit/die,实现中断执行跳出PHP函数调用栈。
function swoole_exit($msg){ //php-fpm的环境 if (ENV=='php') { exit($msg); } //swoole的环境 else { throw new SwooleExitException($msg); }}
异常处理的方式比exit/die更友好,因为异常是可控的,exit/die不可控。在最外层进行try/catch即可捕获异常,仅终止当前的任务。Worker进程可以继续处理新的请求,而exit/die会导致进程直接退出,当前进程保存的所有变量和资源都会被销毁。如果进程内还有其他任务要处理,遇到exit/die也将全部丢弃。
异步程序如果遇到死循环,事件将无法触发。异步IO程序使用Reactor模型,运行过程中必须在reactor->wait处轮询。如果遇到死循环,那么程序的控制权就在while中了,reactor无法得到控制权,无法检测事件,所以IO事件回调函数也将无法触发。
密集运算的代码不是阻塞
$serv = new swoole_server("127.0.0.1", 9501);$serv->on('receive', function ($serv, $fd, $from_id, $data) { while(1) { $i ++; } $serv->send($fd, 'Swoole: '.$data);});$serv->start();
onReceive事件中执行了死循环,server在无法再收到任何客户端请求,必须等待循环结束才能继续处理新的事件。
ulimit -n 要调整为100000甚至更大。 命令行下执行 ulimit -n 100000即可修改。如果不能修改,需要设置 /etc/security/limits.conf,加入
* soft nofile 262140* hard nofile 262140root soft nofile 262140root hard nofile 262140* soft core unlimited* hard core unlimitedroot soft core unlimitedroot hard core unlimited
swoole使用unix socket dgram来做进程间通信,如果请求量很大,需要调整此参数。系统默认为10,可以设置为100或者更大。
或者增加worker进程的数量,减少单个worker进程分配的请求量。
修改此参数增加socket缓存区的内存大小
net.ipv4.tcp_mem = 379008 505344 758016net.ipv4.tcp_wmem = 4096 16384 4194304net.ipv4.tcp_rmem = 4096 87380 4194304net.core.wmem_default = 8388608net.core.rmem_default = 8388608net.core.rmem_max = 16777216net.core.wmem_max = 16777216
是否socket reuse,此函数的作用是Server重启时可以快速重新使用监听的端口。如果没有设置此参数,会导致server重启时发生端口未及时释放而启动失败
使用socket快速回收,短连接Server需要开启此参数
当使用消息队列作为进程间通信方式时,需要调整此内核参数
设置内核参数
kernel.core_pattern = /data/core_files/core-%e-%p-%t
通过ulimit -c命令查看当前coredump文件的限制
ulimit -c
如果为0,需要修改/etc/security/limits.conf,进行limit设置。
开启core-dump后,一旦程序发生异常,会将进程导出到文件。对于调查程序问题有很大的帮助
如:修改net.unix.max_dgram_qlen = 100后,通过
cat /proc/sys/net/unix/max_dgram_qlen
如果修改成功,这里是新设置的值。
在swoole中,一个swoole_server的相关属性可以通过
$serv->set( $array configs );
函数来配置,这些配置选项使得swoole更加灵活。 示例:
$serv = new swoole_server("0.0.0.0", 9501);$serv->set(array( 'worker_num' => 8, 'max_request' => 10000, 'max_conn' => 100000, 'dispatch_mode' => 2, 'debug_mode'=> 1, 'daemonize' => false,));
配置选项以及相关介绍如下:
描述:指定启动的worker进程数。
说明:swoole是master-> n * worker的模式,开启的worker进程数越多,server负载能力越大,但是相应的server占有的内存也会更多。同时,当worker进程数过多时,进程间切换带来的系统开销也会更大。因此建议开启的worker进程数为cpu核数的1-4倍。
示例:
'worker_num' => 8
描述:每个worker进程允许处理的最大任务数。
说明:设置该值后,每个worker进程在处理完max_request个请求后就会自动重启。设置该值的主要目的是为了防止worker进程处理大量请求后可能引起的内存溢出。
示例:
'max_request' => 10000
描述:服务器允许维持的最大TCP连接数
说明:设置此参数后,当服务器已有的连接数达到该值时,新的连接会被拒绝。另外,该参数的值不能超过操作系统ulimit -n的值,同时此值也不宜设置过大,因为swoole_server会一次性申请一大块内存用于存放每一个connection的信息。
示例:
'max_conn' => 10000
描述:设置进程间的通信方式。
说明:共有三种通信方式,参数如下:
- 1 => 使用unix socket通信
- 2 => 使用消息队列通信
- 3 => 使用消息队列通信,并设置为争抢模式
示例:
'ipc_mode' => 1
描述:指定数据包分发策略。
说明:共有三种模式,参数如下:
- 1 => 轮循模式,收到会轮循分配给每一个worker进程
- 2 => 固定模式,根据连接的文件描述符分配worker。这样可以保证同一个连接发来的数据只会被同一个worker处理
- 3 => 抢占模式,主进程会根据Worker的忙闲状态选择投递,只会投递给处于闲置状态的Worker
示例:
'dispatch_mode' => 2
描述:服务器开启的task进程数。
说明:设置此参数后,服务器会开启异步task功能。此时可以使用task方法投递异步任务。
设置此参数后,必须要给swoole_server设置onTask/onFinish两个回调函数,否则启动服务器会报错。
示例:
'task_worker_num' => 8
描述:每个task进程允许处理的最大任务数。
说明:参考max_request task_worker_num
示例:
'task_max_request' => 10000
描述:设置task进程与worker进程之间通信的方式。
说明:参考ipc_mode
示例:
'task_ipc_mode' => 2
描述:设置程序进入后台作为守护进程运行。
说明:长时间运行的服务器端程序必须启用此项。如果不启用守护进程,当ssh终端退出后,程序将被终止运行。启用守护进程后,标准输入和输出会被重定向到 log_file,如果 log_file未设置,则所有输出会被丢弃。
示例:
'daemonize' => 0
描述:指定日志文件路径
说明:在swoole运行期发生的异常信息会记录到这个文件中。默认会打印到屏幕。注意log_file 不会自动切分文件,所以需要定期清理此文件。
示例:
'log_file' => '/data/log/swoole.log'
描述:设置心跳检测间隔
说明:此选项表示每隔多久轮循一次,单位为秒。每次检测时遍历所有连接,如果某个连接在间隔时间内没有数据发送,则强制关闭连接(会有onClose回调)。
示例:
'heartbeat_check_interval' => 60
描述:设置某个连接允许的最大闲置时间。
说明:该参数配合heartbeat_check_interval使用。每次遍历所有连接时,如果某个连接在heartbeat_idle_time时间内没有数据发送,则强制关闭连接。默认设置为heartbeat_check_interval * 2。
示例:
'heartbeat_idle_time' => 600
描述:打开eof检测功能
说明:与package_eof 配合使用。此选项将检测客户端连接发来的数据,当数据包结尾是指定的package_eof 字符串时才会将数据包投递至Worker进程,否则会一直拼接数据包直到缓存溢出或超时才会终止。一旦出错,该连接会被判定为恶意连接,数据包会被丢弃并强制关闭连接。
EOF检测不会从数据中间查找eof字符串,所以Worker进程可能会同时收到多个数据包,需要在应用层代码中自行explode(" ", $data) 来拆分数据包
示例:
'open_eof_check' => true
描述:设置EOF字符串
说明:package_eof最大只允许传入8个字节的字符串
示例:
'package_eof ' => '/r/n'
描述:打开包长检测
说明:包长检测提供了固定包头+包体这种格式协议的解析,。启用后,可以保证Worker进程onReceive每次都会收到一个完整的数据包。
示例:
'open_length_check' => true
描述:包头中第几个字节开始存放了长度字段
说明:配合open_length_check使用,用于指明长度字段的位置。
示例:
'package_length_offset' => 5
描述:从第几个字节开始计算长度。
说明:配合open_length_check使用,用于指明包头的长度。
示例:
'package_body_offset' => 10
描述:指定包长字段的类型
说明:配合open_length_check使用,指定长度字段的类型,参数如下:
- 's' => int16_t 机器字节序
- 'S' => uint16_t 机器字节序
- 'n' => uint16_t 大端字节序
- ’N‘ => uint32_t 大端字节序
- 'L' => uint32_t 机器字节序
- 'l' => int 机器字节序
示例:
'package_length_type' => 'N'
描述:设置最大数据包尺寸
说明:该值决定了数据包缓存区的大小。如果缓存的数据超过了该值,则会引发错误。具体错误处理由开启的协议解析的类型决定。
示例:
'package_max_length' => 8192
描述:启用CPU亲和性设置
说明:在多核的硬件平台中,启用此特性会将swoole的reactor线程/worker进程绑定到固定的一个核上。可以避免进程/线程的运行时在多个核之间互相切换,提高CPU Cache的命中率。
示例:
'open_cpu_affinity' => true
描述:启用open_tcp_nodelay
说明:开启后TCP连接发送数据时会无关闭Nagle合并算法,立即发往客户端连接。在某些场景下,如http服务器,可以提升响应速度。
示例:
'open_tcp_nodelay' => true
描述:启用tcp_defer_accept特性
说明:启动后,只有一个TCP连接有数据发送时才会触发accept。
示例:
'tcp_defer_accept' => true
描述:设置SSL隧道加密
说明:设置值为一个文件名字符串,指定cert证书和key的路径。
示例:
'ssl_cert_file' => '/config/ssl.crt','ssl_key_file' => '/config//ssl.key',
描述:打开TCP的KEEP_ALIVE选项
说明:使用TCP内置的keep_alive属性,用于保证连接不会因为长时闲置而被关闭。
示例:
'open_tcp_keepalive' => true
描述:指定探测间隔。
说明:配合open_tcp_keepalive使用,如果某个连接在tcp_keepidle内没有任何数据来往,则进行探测。
示例:
'tcp_keepidle' => 600
描述:指定探测时的发包间隔
说明:配合open_tcp_keepalive使用
示例:
'tcp_keepinterval' => 60
描述:指定探测的尝试次数
说明:配合open_tcp_keepalive使用,若tcp_keepcount次尝试后仍无响应,则判定连接已关闭。
示例:
'tcp_keepcount' => 5
描述:指定Listen队列长度
说明:此参数将决定最多同时有多少个等待accept的连接。
示例:
'backlog' => 128
描述:指定Reactor线程数
说明:设置主进程内事件处理线程的数量,默认会启用CPU核数相同的数量, 一般设置为CPU核数的1-4倍,最大不得超过CPU核数*4。
示例:
'reactor_num' => 8
描述:设置task的数据临时目录
说明:在swoole_server中,如果投递的数据超过8192字节,将启用临时文件来保存数据。这里的task_tmpdir就是用来设置临时文件保存的位置。
需要swoole-1.7.7+
示例:
'task_tmpdir' => '/tmp/task/'
除以上29个选项外,还有几个不常用或者已废弃的选项没有给出。以后如果有实际需求,会补充说明。
在swoole中,一个swoole_server的拥有若干回调函数,这些回调函数决定了swoole_server的相关功能。可以通过
$serv->on( string $event, mixed $callback );
函数来配置. 示例:
class Server{ private $serv; public function __construct() { $this->serv = new swoole_server("0.0.0.0", 9501); $this->serv->set(array( 'worker_num' => 8, 'daemonize' => false, 'max_request' => 10000, 'dispatch_mode' => 2, 'debug_mode'=> 1 )); $this->serv->on('Start', array($this, 'onStart')); $this->serv->on('Connect', array($this, 'onConnect')); $this->serv->on('Receive', array($this, 'onReceive')); $this->serv->on('Close', array($this, 'onClose')); $this->serv->start(); }}
全部回调函数以及相关介绍如下:
[TOC]
描述:接收数据的回调
函数原型:
function onReceive( swoole_server $serv, $fd, $from_id, $data );
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$fd | 连接的描述符 |
$from_id | reactor的id,无用 |
$data | 接收到的数据 |
说明:每当server接收到客户端发来的数据后,就会通过onReceive回调将数据投递给Worker。如果开启了协议检测,则会在收到完整数据包之后才会响应回调。注意,必须设置该回调函数,否则无法启动服务器。
描述:服务器启动的回调
函数原型:
function onStart( swoole_server $serv);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
说明:
onStart事件在Master进程的主线程中被调用。在此回调响应之前Swoole Server已进行了如下操作
接下来要执行
onStart回调中,仅允许echo、打印Log、修改进程名称。不得执行其他操作。onWorkerStart和onStart回调是在不同进程中并行执行的,不存在先后顺序。 可以在onStart回调中,将$serv->master_pid和$serv->manager_pid的值保存到一个文件中。这样可以编写脚本,向这两个PID发送信号来实现关闭和重启的操作。
从1.7.5+ Master进程内不再支持定时器,onMasterConnect/onMasterClose2个事件回调也彻底移除。Master进程内不再保留任何PHP的接口。
在onStart中创建的全局资源对象不能在worker进程中被使用,因为发生onStart调用时,worker进程已经创建好了。新创建的对象在主进程内,worker进程无法访问到此内存区域,因此全局对象创建的代码需要放置在swoole_server_start之前。
描述:Worker进程启动的回调
函数原型:
function onWorkerStart( swoole_server $serv,int $worker_id);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$worker_id | Worker进程的id |
说明:此事件在worker进程/task_worker启动时发生。
发生PHP致命错误或者代码中主动调用exit时,Worker/Task进程会退出,管理进程会重新创建新的进程 onWorkerStart/onStart是并发执行的,没有先后顺序
通过$worker_id参数的值来,判断worker是普通worker还是task_worker。$worker_id>= $serv->setting['worker_num'] 时表示这个进程是task_worker。
如果想使用swoole_server_reload实现代码重载入,必须在workerStart中require你的业务文件,而不是在文件头部。在onWorkerStart调用之前已包含的文件,不会重新载入代码。
可以将公用的,不易变的php文件放置到onWorkerStart之前。这样虽然不能重载入代码,但所有worker是共享的,不需要额外的内存来保存这些数据。
onWorkerStart之后的代码每个worker都需要在内存中保存一份 $worker_id是一个从0-$worker_num之间的数字,表示这个worker进程的ID $worker_id和进程PID没有任何关系
描述:新连接接入时的回调
函数原型:
function onConnect( swoole_server $serv,int $fd, int $from_id);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$fd | 连接的描述符 |
$from_id | reactor的id,无用 |
说明:有新的连接进入时,在worker进程中回调。onConnect/onClose这2个回调发生在worker进程内,而不是主进程。如果需要在主进程处理连接/关闭事件,请注册onMasterConnect/onMasterClose回调。onMasterConnect/onMasterClose回调总是先于onConnect/onClose被执行
描述:连接关闭时的回调
函数原型:
function onClose( swoole_server $serv,int $fd, int $from_id);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$fd | 连接的描述符 |
$from_id | reactor的id,无用 |
说明:TCP客户端连接关闭后,在worker进程中回调此函数。无论close由客户端发起还是服务器端主动调用swoole_server_close关闭连接,都会触发此事件。 因此只要连接关闭,就一定会回调此函数。
描述:task_worker进程处理任务的回调
函数原型:
function onTask(swoole_server $serv, int $task_id, int $from_id, string $data);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$task_id | 任务ID |
$from_id | 来自于哪个worker进程 |
$data | 任务内容 |
说明:在task_worker进程内被调用。worker进程可以使用swoole_server_task函数向task_worker进程投递新的任务。可以直接将任务结果字符串通过return方式返回给worker进程。worker进程将在onFinish回调中收到结果。注:如果serv->set(array('task_worker_num' => 8)) task_id 并不是从1-8 而是递增的。
描述:task_worker进程处理任务结束的回调
函数原型:
function onFinish(swoole_server $serv, int $task_id, string $data);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$task_id | 任务ID |
$data | 任务结果 |
说明:在此函数中会收到任务处理的结果,通过task_id和worker_id来区分不同的任务。
描述:定时器触发的回调
函数原型:
function onTimer(swoole_server $serv, int $interval);
参数 | 描述 |
---|---|
$serv | swoole_server对象 |
$interval | 定时的间隔 |
说明:定时器被触发时,该函数被调用。通过interval来区分不同时间间隔的定时器。
功能描述:创建一个swoole_server资源对象
函数原型:
// 类成员函数public function swoole_server::__construct(string $host, int $port, int $mode = SWOOLE_PROCESS, int $sock_type = SWOOLE_SOCK_TCP);// 公共函数function swoole_server_create(string $host, int $port, int $mode = SWOOLE_PROCESS, int $sock_type = SWOOLE_SOCK_TCP);
返回:一个swoole_server对象
参数说明:
参数 | 说明 |
---|---|
string host | 监听的IP地址 |
int port | 监听的端口号 |
int mode | 运行模式 |
int sock_type | 指定的socket类型 |
说明: host、port、socket_type的详细说明见swoole_server::addlistener。
mode指定了swoole_server的运行模式,共有如下三种:
mode | 类型 | 说明 |
---|---|---|
SWOOLE_BASE | Base模式 | 传统的异步非阻塞Server。在Reactor内直接回调PHP的函数。如果回调函数中有阻塞操作会导致Server退化为同步模式。worker_num参数对与BASE模式仍然有效,swoole会启动多个Reactor进程 |
SWOOLE_THREAD | 线程模式(已废弃) | 多线程Worker模式,Reactor线程来处理网络事件轮询,读取数据。得到的请求交给Worker线程去处理。多线程模式比进程模式轻量一些,而且线程之间可以共享堆栈和资源。 访问共享内存时会有同步问题,需要使用Swoole提供的锁机制来保护数据。 |
SWOOLE_PROCESS | 进程模式(默认) | Swoole提供了完善的进程管理、内存保护机制。 在业务逻辑非常复杂的情况下,也可以长期稳定运行,适合业务逻辑非常复杂的场景。 |
样例:
$serv = new swoole_server("127.0.0.1" , 8888 , SWOOLE_PROCESS , SWOOLE_SOCK_TCP);
功能描述:设置swoole_server运行时的各项参数
函数原型:
// 类成员函数public function swoole_server::set(array $setting);// 公共函数function swoole_server_set(swoole_server $server, array $setting);
返回:无
参数说明:
参数 | 说明 |
---|---|
array setting | 配置选项数组,采用key-value形式 |
说明:
该函数必须在swoole_server::start函数调用前调用。
全部swoole_server的配置参数点此查看
样例:
$serv->set( array( 'worker_num' => 8, 'max_request' => 10000, 'max_conn' => 100000, 'dispatch_mode' => 2, 'debug_mode'=> 1, 'daemonize' => false, ));
功能描述:绑定swoole_server的相关回调函数
函数原型:
// 类成员函数public function bool swoole_server->on(string $event, mixed $callback);
返回:设置成功返回true,否则返回false
参数说明:
参数 | 说明 |
---|---|
string event | 回调的名称(大小写不敏感) |
mixed callback | 回调的PHP函数,可以是函数名的字符串,类静态方法,对象方法数组,匿名函数 |
说明:
该函数必须在swoole_server::start函数调用前调用。
此方法与swoole_server::handler功能相同,作用是与swoole_client风格保持一致。
swoole_server::on中事件名称字符串不要加on。
全部的回调函数列表点此查看
样例:
$serv->on('connect', function ($serv, $fd){ echo "Client:Connect.
";});$serv->on('receive', array( $myclass, 'onReceive' ) ); // onReceive是myclass的成员函数
功能描述:给swoole_server增加一个监听的地址和端口
函数原型:
// 类成员函数public function swoole_server::addlistener(string $host, int $port, $type = SWOOLE_SOCK_TCP);// 公共函数function swoole_server_addlisten(swoole_server $serv, string $host, int $port, $type = SWOOLE_SOCK_TCP);
返回:无
参数说明:
参数 | 说明 |
---|---|
string host | 监听的IP地址 |
int port | 监听的端口号 |
int sock_type | 指定的socket类型 |
说明: swoole支持如下socket类型:
sock_type | 说明 |
---|---|
SWOOLE_TCP/SWOOLE_SOCK_TCP | TCP IPv4 Socket |
SWOOLE_TCP6/SWOOLE_SOCK_TCP6 | TCP IPv6 Socket |
SWOOLE_UDP/SWOOLE_SOCK_UDP | UDP IPv4 Socket |
SWOOLE_UDP6/SWOOLE_SOCK_UDP6 | UDP IPv4 Socket |
SWOOLE_UNIX_DGRAM | Unix UDP Socket |
SWOOLE_UNIX_STREAM | Unix TCP Socket |
Unix Socket仅在1.7.1+后可用,此模式下host参数必须填写可访问的文件路径,port参数忽略
Unix Socket模式下,客户端fd将不再是数字,而是一个文件路径的字符串
SWOOLE_TCP等是1.7.0+后提供的简写方式,与1.7.0前的SWOOLE_SOCK_TCP是等同的
样例:
$serv->addlistener("127.0.0.1", 9502, SWOOLE_SOCK_TCP);$serv->addlistener("192.168.1.100", 9503, SWOOLE_SOCK_TCP);$serv->addlistener("0.0.0.0", 9504, SWOOLE_SOCK_UDP);$serv->addlistener("/var/run/myserv.sock", 0, SWOOLE_UNIX_STREAM);swoole_server_addlisten($serv, "127.0.0.1", 9502, SWOOLE_SOCK_TCP);
功能描述:设置Server的事件回调函数
函数原型:
// 类成员函数public function swoole_server::handler(string $event_name, mixed $event_callback_function);// 公共函数function swoole_server_handler(swoole_server $serv, string $event_name, mixed $event_callback_function);
返回:设置成功返回true,否则返回false
参数说明:
参数 | 说明 |
---|---|
string event_name | 回调的名称(大小写不敏感) |
mixed event_callback_function | 回调的PHP函数,可以是函数名的字符串,类静态方法,对象方法数组,匿名函数 |
说明: 该函数必须在swoole_server::start函数调用前调用。
事件名称字符串要加on。
全部的回调函数列表点此查看
onConnect/onClose/onReceive这3个回调函数必须设置。其他事件回调函数可选
如果设定了timer定时器,onTimer事件回调函数也必须设置
如果启用了task_worker,onTask/onFinish事件回调函数必须设置
样例:
$serv->handler('onStart', 'my_onStart');$serv->handler('onStart', array($this, 'my_onStart'));$serv->handler('onStart', 'myClass::onStart');
功能描述:启动server,开始监听所有TCP/UDP端口
函数原型:
// 类成员函数public function swoole_server::start()
返回:启动成功返回true,否则返回false
参数说明:无
说明:
启动成功后会创建worker_num+2个进程:Master进程+Manager进程+worker_num 个 Worker进程。
另外。启用task_worker会增加task_worker_num个Worker进程
三种进程的说明如下:
进程类型 | 说明 |
---|---|
Master进程 | 主进程内有多个Reactor线程,基于epoll/kqueue进行网络事件轮询。收到数据后转发到Worker进程去处理 |
Manager进程 | 对所有Worker进程进行管理,Worker进程生命周期结束或者发生异常时自动回收,并创建新的Worker进程 |
Worker进程 | 对收到的数据进行处理,包括协议解析和响应请求 |
样例:
$serv->start();
功能描述:重启所有worker进程。
函数原型:
// 类成员函数public function swoole_server::reload()
返回:调用成功返回true,否则返回false
参数说明:无
说明:
调用后会向Manager发送一个SIGUSR1信号,平滑重启全部的Worker进程(所谓平滑重启,是指重启动作会在Worker处理完正在执行的任务后发生,并不会中断正在运行的任务。)
小技巧:在onWorkerStart回调中require相应的php文件,当这些文件被修改后,只需要通过SIGUSR1信号即可实现服务器热更新。
1.7.7版本增加了仅重启task_worker的功能。只需向服务器发送SIGUSR2即可
样例:
$serv->reload();
功能描述:关闭服务器。
函数原型:
// 类成员函数public function swoole_server::shutdown()
返回:调用成功返回true,否则返回false
参数说明:无
说明:
此函数可以用在worker进程内,平滑关闭全部的Worker进程。
也可向Master进程发送SIGTERM信号关闭服务器。
样例:
$serv->shutdown();
功能描述:设置一个固定间隔的定时器
函数原型:
// 类成员函数public function swoole_server::addtimer(int $interval);// 公共函数function swoole_server_addtimer(swoole_server $serv, int $interval);
返回:设置成功返回true,否则返回false
参数说明:
参数 | 说明 |
---|---|
int interval | 定时器的时间间隔,单位为毫秒ms |
说明:
swoole定时器的最小颗粒是1毫秒,支持多个不同间隔的定时器。
注意不能存在2个相同间隔时间的定时器。
使用多个定时器时,其他定时器必须为最小定时器时间间隔的整数倍。
该函数只能在onWorkerStart/onConnect/onReceive/onClose回调函数中调用。
增加定时器后需要为Server设置onTimer回调函数,否则Server将无法启动。
样例:
$serv->addtimer(1000); //1sswoole_server_addtimer($serv,20); //20ms
功能描述:删除指定的定时器。
函数原型:
// 类成员函数public function swoole_server::deltimer(int $interval);
返回:无
参数说明:
参数 | 说明 |
---|---|
int interval | 定时器的时间间隔,单位为毫秒ms |
说明:
删除间隔为interval的定时器
样例:
$serv->deltimer(1000);
功能描述:在指定的时间后执行函数
函数原型:
// 类成员函数public function swoole_server::after(int $after_time_ms, mixed $callback_function, mixed params);// 公共函数function swoole_timer_after<
创建一个异步服务器程序,支持TCP、UDP、UnixSocket 3种协议,支持IPv4和IPv6,支持SSL/TLS单向双向证书的隧道加密。使用者无需关注底层实现细节,仅需要设置网络事件的回调函数即可。
swoole_server只能用于php-cli环境,否则会抛出致命错误
$serv = new swoole_server("127.0.0.1", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);
$serv->set(array( 'worker_num' => 4, 'daemonize' => true, 'backlog' => 128,));
$serv->on('Connect', 'my_onConnect');$serv->on('Receive', 'my_onReceive');$serv->on('Close', 'my_onClose');
PHP中可以使用4种回调函数的风格
$serv->start();
$serv->manager_pid; //管理进程的PID,通过向管理进程发送SIGUSR1信号可实现柔性重启$serv->master_pid; //主进程的PID,通过向主进程发送SIGTERM信号可安全关闭服务器$serv->connections; //当前服务器的客户端连接,可使用foreach遍历所有连接
$serv = new SwooleServer("127.0.0.1", 9501);//设置服务器参数$serv->set(array( 'worker_num' => 8, //工作进程数量 'daemonize' => true, //是否作为守护进程));//设置事件回调函数$serv->on('connect', function ($serv, $fd) { echo "Client:Connect.
";});$serv->on('receive', function ($serv, $fd, $reactor_id, $data) { $serv->send($fd, 'Swoole: ' . $data); $serv->close($fd);});$serv->on('close', function ($serv, $fd) { echo "Client: Close.
";});//启动服务器$serv->start();
swoole_client提供了tcp/udp socket的客户端的封装代码,使用时仅需 new swoole_client即可。 swoole的socket client对比PHP提供的stream族函数有哪些好处:
除了普通的同步阻塞+select的使用方法外,swoole_client还支持异步非阻塞回调。
$client = new swoole_client(SWOOLE_SOCK_TCP);if (!$client->connect('127.0.0.1', 9501, -1)){ exit("connect failed. Error: {$client->errCode}
");}$client->send("hello world
");echo $client->recv();$client->close();
php-fpm/apache环境下只能使用同步客户端
apache环境下仅支持prefork
多进程模式,不支持prework
多线程
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);$client->on("connect", function(swoole_client $cli) { $cli->send("GET / HTTP/1.1
");});$client->on("receive", function(swoole_client $cli, $data){ echo "Receive: $data"; $cli->send(str_repeat('A', 100)."
"); sleep(1);});$client->on("error", function(swoole_client $cli){ echo "error
";});$client->on("close", function(swoole_client $cli){ echo "Connection close
";});$client->connect('127.0.0.1', 9501);
异步客户端只能使用在cli命令行环境
swoole-1.7.7增加了内置Http服务器的支持,通过几行代码即可写出一个异步非阻塞多进程的Http服务器。
常见使用场景:因为swoole是在cli命令下执行的,在传统通过nginx+fastcgi模式下很多root的shell无法执行,而使用这个swoole服务器就很好的控制rsync,git,svn等。
$http = new swoole_http_server("127.0.0.1", 9501);$http->on('request', function ($request, $response) { $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");});$http->start();
swoole_http_server对Http协议的支持并不完整,建议仅作为应用服务器。并且在前端增加Nginx作为代理
$serv = new SwooleHttpServer("127.0.0.1", 9502);$serv->on('Request', function($request, $response) { var_dump($request->get); var_dump($request->post); var_dump($request->cookie); var_dump($request->files); var_dump($request->header); var_dump($request->server); $response->cookie("User", "Swoole"); $response->header("X-Server", "Swoole"); $response->end("<h1>Hello Swoole!</h1>");});$serv->start();
通过使用apache bench工具进行压力测试,在Inter Core-I5 4核 + 8G内存的普通PC机器上,swoole_http_server可以达到近11万QPS。远远超过php-fpm,golang自带http服务器,node.js自带http服务器。性能几乎接近与Nginx的静态文件处理。
ab -c 200 -n 200000 -k http://127.0.0.1:9501
nghttp2
库,下载nghttp2后编译安装Http2
协议必须开启openssl
openssl
必须支持TLS1.2
、ALPN
、NPN
./configure --enable-openssl --enable-http2
设置http服务器的open_http2_protocol
为true
$serv->set([ 'ssl_cert_file' => $ssl_dir . '/ssl.crt', 'ssl_key_file' => $ssl_dir . '/ssl.key', 'open_http2_protocol' => true,]);
server { root /data/wwwroot/; server_name local.swoole.com; location / { if (!-e $request_filename) { proxy_pass http://127.0.0.1:9501; proxy_http_version 1.1; proxy_set_header Connection "keep-alive"; } }}
Swoole-1.8.0版本增加了对异步Http/WebSocket客户端的支持。底层是用纯C编写,拥有超高的性能。
异步HTTP客户端目前仍在实验阶段,请谨慎使用
function swoole_http_client->__construct(string $ip, int port, bool $ssl = false);
swoole-1.7.9 增加了内置的websocket服务器支持,通过几行PHP代码就可以写出一个异步非阻塞多进程的WebSocket服务器。
常见使用场景:我们在使用php开发的时候,原生最不好用的是socket类库了,而在开发IM和及时通信项目是,我们现在有了新的选择后端使用php Swoole WebSocket + 前端 html5 WebSocket;
$server = new swoole_websocket_server("0.0.0.0", 9501);$server->on('open', function (swoole_websocket_server $server, $request) { echo "server: handshake success with fd{$request->fd}
";});$server->on('message', function (swoole_websocket_server $server, $frame) { echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
"; $server->push($frame->fd, "this is server");});$server->on('close', function ($ser, $fd) { echo "client {$fd} closed
";});$server->start();
swoole_websocket_server 继承自 swoole_http_server
http 400
错误页面SwooleHttpClient
作为WebSocket客户端甚至你还可以结合使用socket.io来配合开发
Swoole-1.8.14版本增加一个兼容Redis服务器端协议的Server框架,可基于此框架实现Redis协议的服务器程序。SwooleRedisServer
继承自SwooleServer
,可调用父类提供的所有方法。
RedisServer
不需要设置onReceive
回调。实例程序:https://github.com/swoole/swoole-src/blob/master/examples/redis/server.php
redis-cli
、redis-benchmark
使用Redis客户端,需要安装hiredis库。下载hiredis
源码后,执行
make -jsudo make installsudo ldconfig
编译swoole是,在configure
指令中加入--enable-async-redis
./configure --enable-async-redismake cleanmake -jsudo make install
$redis = new SwooleRedis;$redis->connect('127.0.0.1', 6379, function ($redis, $result) { $redis->set('test_key', 'value', function ($redis, $result) { $redis->get('test_key', function ($redis, $result) { var_dump($result); }); });});$cli = new SwooleHttpClient('127.0.0.1', 80);$cli->setHeaders(array('User-Agent' => 'swoole-http-client'));$cli->setCookies(array('test' => 'value'));$cli->post('/dump.php', array("test" => 'abc'), function ($cli) { var_dump($cli->body); $cli->get('/index.php', function ($cli) { var_dump($cli->cookies); var_dump($cli->headers); });});
mysqlnd
和 mysqli
2个扩展,请使用php -m
或phpinfo
确认PHP是否有这2个扩展。--enable-async-mysql
1.8.6版本已移除对
mysqli
和mysqlnd
扩展的依赖,并改为内置,无需额外的编译参数开启
$db = new SwooleMySQL;$server = array( 'host' => '127.0.0.1', 'user' => 'test', 'password' => 'test', 'database' => 'test',);$db->connect($server, function ($db, $result) { $db->query("show tables", function (SwooleMySQL $db, $result) { if ($result === false) { var_dump($db->error, $db->errno); } elseif ($result === true) { var_dump($db->affected_rows, $db->insert_id); } else { var_dump($result); $db->close(); } });});
MySQL短连接每次请求操作数据库都需要建立与MySQL服务器建立TCP连接,这是需要时间开销的。TCP连接需要3次网络通信。这样就增加了一定的延时和额外的IO消耗。请求结束后会关闭MySQL连接,还会发生3/4次网络通信。
close操作不会增加响应延时,原因是close后是由操作系统自动进行通信的,应用程序感知不到
长连接就可以避免每次请求都创建连接的开销,节省了时间和IO消耗。提升了PHP程序的性能。
在cli环境下,PHP程序需要长时间运行,客户端与MySQL服务器之间的TCP连接是不稳定的。
这时PHP程序中的MySQL连接就失效了。如果仍然执行mysql_query,就会报一个“MySQL server has gone away”的错误。程序处理不到就直接遇到致命错误并退出了。所以PHP程序中需要断线重连。
有很多人提出了mysql_ping的方案,每次mysql_query进行连接检测或者定时连接检测。这个方案不是最好的。原因是
最佳的方案是,进行断线重连 。它的原理是:
MySQL异步是指将MySQL连接事件驱动化,这样就编程了非阻塞IO。数据库操作并不会阻塞进程,在MySQL-Server返回结果时再执行对应的逻辑。
有几个点需要注意一下:
异步回调程序中,异步MySQL并没有提升性能。异步最大的好处是可以高并发,如果并发1万个请求,那么就需要建立1万个MySQL连接,这会给MySQL-Server带来巨大的压力。
MySQL是根据连接数分配资源的,一个连接需要开启一个线程。1000连接那么需要维持1000线程才可以。线程数量增加后,线程间切换会占用大量CPU资源
MySQL短连接反而不会出现此问题,因为短连接在使用完后就释放了。不会占用MySQL-Server的连接资源
虽然应用层代码使用异步回调避免了自身的阻塞,实际上真正的瓶颈是数据库服务器。异步MySQL还带来了额外的编程复杂度,所以除非是特殊场景的需求,否则不建议使用异步MySQL。
如果程序中坚持要使用异步,那么必须是异步MySQL+连接池的形式。超过规定的MySQL最大连接后,应当对SQL请求进行排队,而不是创建新连接,避免大量并发请求导致MySQL服务器崩溃。
连接池是可以有效降低MySQL-Server负载的。原理是 连接池使用一个共享资源的模式,如并发100个请求,实际上并不是每个请求的所有时间都在执行SQL查询。这样100个请求,共享20个MySQL连接就可以满足需求了。当一个请求操作完数据库后,开始进入模板渲染等流程,这时就会释放数据库连接给其他的请求使用。
连接池仅在超大型应用中才有价值。普通的应用采用MySQL长连接方案,每个php-fpm创建一个MySQL连接,每台机器开启100个php-fpm进程。如果有10台机器,每台机器并发的请求为100。实际上只需要创建1000个MySQL连接就能满足需求,数据库的压力并不大。即使有100台机器,硬件配置好的存储服务器依然可以承受。
达到数百或者数千台应用服务器时,MySQL服务器就需要维持十万级的连接。这时数据库的压力就会非常大了。连接池技术就可以派上用场了,可以大大降低数据库连接数。
基于swoole的AsyncTask模块实现的连接池是完美方案,编程简单,没有数据同步和锁的问题。甚至可以多个服务共享连接池。缺点是1, 灵活性不如多线程连接池,无法动态增减连接。2, 有一次进程间通信的开销。
node.js/ngx_lua等在多进程的模式下,无法开发出真正的连接池,除非也像swoole_task这样来实现
swoole1.6.12后增加了异步文件读写,异步DNS等特性。自此建立了完整的异步并行API。
Task进程
是同步阻塞的,没有EventLoop
,因此无法使除定时器之外的用任何异步IOswoole_async_set
关闭signalfd
特性Swoole支持3种类型的异步文件读写IO,可以使用swoole_async_set
来设置AIO模式。
此函数可以设置异步IO相关的选项。
swoole_async_set(array $setting);
SWOOLE_AIO_BASE
(使用类似于Node.js的线程池同步阻塞模拟异步)、SWOOLE_AIO_LINUX
(Linux Native AIO) 2种模式Linux Native AIO的优点是由内核支持是真正的异步文件IO,缺点是只支持DirectIO,无法利用到系统的PageCache
swoole_async模块目前为实验性质,不建议在生产环境使用,请使用PHP的文件读写函数。
基于Linux Native AIO系统调用,是真正的异步IO,并非阻塞模拟。
优点:
缺点:
基于线程池模拟实现,文件读写请求投递到任务队列,然后由AIO线程读写文件,完成后通知主线程。AIO线程本身是同步阻塞的。所以并非真正的异步IO。
优点:
可修改
thread_num
项设置启用的AIO线程数量
缺点:
$fp = stream_socket_client("tcp://127.0.0.1:80", $code, $msg, 3);$http_request = "GET /index.html HTTP/1.1
";fwrite($fp, $http_request);SwooleEvent::add($fp, function($fp){ echo fread($fp, 8192); swoole_event_del($fp); fclose($fp);});SwooleTimer::after(2000, function() { echo "2000ms timeout
";});SwooleTimer::tick(1000, function() { echo "1000ms interval
";});
swoole 的异步任务task系统可以很方便的为我们在开发的过程中调用异步任务的执行,而无需等待。
task模块用来做一些异步的慢速任务,比如webim中发广播,发送邮件,异步订单处理、异步支付处理等。
node.js 假如有10万个连接,要发广播时,那会循环10万次,这时候程序不能做任何事情,不能接受新的连接,也不能收包发包。
而swoole不同,丢给task进程之后,worker进程可以继续处理新的数据请求。任务完成后会异步地通知worker进程告诉它此任务已经完成。
当然task模块的作用还不仅如此,实现PHP的数据库连接池,异步队列等,还需要进一步挖掘。
$serv = new SwooleServer("127.0.0.1", 9502);$serv->set(array('task_worker_num' => 4));$serv->on('Receive', function($serv, $fd, $from_id, $data) { $task_id = $serv->task("Async"); echo "Dispath AsyncTask: id=$task_id
";});$serv->on('Task', function ($serv, $task_id, $from_id, $data) { echo "New AsyncTask[id=$task_id]".PHP_EOL; $serv->finish("$data -> OK");});$serv->on('Finish', function ($serv, $task_id, $data) { echo "AsyncTask[$task_id] Finish: $data".PHP_EOL;});$serv->start();
投递一个异步任务到task_worker池中。此函数是非阻塞的,执行完毕会立即返回。worker进程可以继续处理新的请求。
int swoole_server::task(mixed $data, int $dst_worker_id = -1) $task_id = $serv->task("some data");//swoole-1.8.6或更高版本$serv->task("taskcallback", -1, function (swoole_server $serv, $task_id, $data) { echo "Task Callback: "; var_dump($task_id, $data);});
0 - (serv->task_worker_num -1)
$task_id
,表示此任务的ID。如果有finish回应,onFinish
回调中会携带$task_id
参数onFinish
函数,如果任务设置了回调函数,Task返回结果时会直接执行制定的回调函数,不再执行Server的onFinish
回调$dst_worker_id在1.6.11+后可用,默认为随机投递
$task_id是从0-42亿的整数,在当前进程内是唯一的
task方法不能在task进程/用户自定义进程中调用
此功能用于将慢速的任务异步地去执行,比如一个聊天室服务器,可以用它来进行发送广播。当任务完成时,在task进程中调用$serv->finish("finish")
告诉worker进程此任务已完成。当然swoole_server->finish
是可选的。
task底层使用Unix Socket管道通信,是全内存的,没有IO消耗。单进程读写性能可达100万/s,不同的进程使用不同的管道通信,可以最大化利用多核。
AsyncTask功能在1.6.4版本增加,默认不启动task功能,需要在手工设置task_worker_num来启动此功能
task_worker的数量在swoole_server::set参数中调整,如task_worker_num => 64,表示启动64个进程来接收异步任务
swoole_server->task/taskwait/finish 3个方法当传入的$data
数据超过8K时会启用临时文件来保存。当临时文件内容超过server->package_max_length
时底层会抛出一个警告。
WARN: task package is too big.
server->package_max_length 默认为2M
函数原型:
string $result = swoole_server->taskwait(mixed $task_data, float $timeout = 0.5, int $dst_worker_id = -1);
taskwait与task方法作用相同,用于投递一个异步的任务到task进程池去执行。与task不同的是taskwait是阻塞等待的,直到任务完成或者超时返回。
$result为任务执行的结果,由$serv->finish函数发出。如果此任务超时,这里会返回false。
taskwait是阻塞接口,如果你的Server是全异步的请使用swoole_server::task和swoole_server::finish,不要使用taskwait
第3个参数可以制定要给投递给哪个task进程,传入ID即可,范围是0 - serv->task_worker_num
$dst_worker_id在1.6.11+后可用,默认为随机投递
taskwait方法不能在task进程中调用
在task_worker进程内被调用。worker进程可以使用swoole_server_task函数向task_worker进程投递新的任务。当前的Task进程在调用onTask
回调函数时会将进程状态切换为忙碌,这时将不再接收新的Task,当onTask
函数返回时会将进程状态切换为空闲然后继续接收新的Task。
function onTask((swoole_server $serv, int $task_id, int $src_worker_id, string $data));
1.7.2以上的版本,在onTask函数中 return字符串,表示将此内容返回给worker进程。worker进程中会触发onFinish函数,表示投递的task已完成。
1.7.2以前的版本,需要调用swoole_server->finish()
函数将结果返回给worker进程
Swoole 2.0正式版发布了。2.0版本最大的更新是增加了对协程(Coroutine)的支持。正式版已同时支持PHP5和PHP7。基于Swoole2.0协程PHP开发者可以已同步的方式编写代码,底层自动进行协程调度,转变为异步IO。解决了传统异步编程嵌套回调的问题。
与Node.js(ES6+)、Python等语言使用yield/generator、async/await的实现方式相比,Swoole协程无需修改代码添加额外的关键词。
与Go语言的goroutine相比,Swoole协程是内置式的,应用层代码无需添加go关键词启动协程,只需要使用封装好的协程客户端即可,使用更简单。另外Swoole协程的IO组件在底层内置了超时机制,不需要使用复杂的select/chan/timer实现客户端超时。
目前Swoole底层内置的协程客户端组件包括:udpclient、tcpclient、httpclient、redisclient、mysqlclient,基本涵盖了开发者常用的几种通信协议。协程组件只能在服务器的onConnect、onRequest、onReceive、onMessage 回调函数中使用。
使用示例:
$server = new SwooleHttpServer('127.0.0.1', 9501);/* 触发on request事件时,SWOOLE会开辟一个协程栈,对协程栈进行初始化 */$server->on('Request', function ($request, $response) { $tcp_cli = new SwooleCoroutineClient(SWOOLE_SOCK_TCP); /** client在调用connect函数后,SWOOLE会将PHP上下文信息保存到当前栈内 然后将协程挂起,待确认连接成功后,触发epoll事件,然后协程切换 恢复PHP上下文信息,返回结果,继续执行PHP代码 */ if ($tcp_cli->connect('127.0.0.1', 9906) === false) { $response->end("connect server failed."); return; } $tcp_cli->send('test for the coro'); /* client在调用recv函数后,SWOOLE会将PHP上下文信息保存到当前栈内 然后将协程挂起待后端svr回包,触发epoll事件,然后协程切换 恢复PHP上下文信息,返回结果,继续执行PHP代码 如果后端在设定的超时时间内,未能回包,返回false client的errCode定为110 */ $ret = $tcp_cli->recv(100); $tcp_cli->close(); if ($ret) { $response->end(" swoole response is ok"); } else { $response->end(" recv failed error : {$tcp_cli->errCode}"); }});$server->start();
UDP客户端
$udp_cli = new SwooleCoroutineClient(SWOOLE_SOCK_UDP);$ret = $udp_cli->connect('127.0.0.1', 9906);$udp_cli->send('test for the coro');$ret = $udp_cli->recv(100);$udp_cli->close();if ($ret){ $response->end("swoole response is ok");}else{ $response->end("recv failed error : {$client->errCode}");}
MySQL客户端
$swoole_mysql = new SwooleCoroutineMySQL();$swoole_mysql->connect([ 'host' => '127.0.0.1', 'user' => 'user', 'password' => 'pass', 'database' => 'test']);$res = $swoole_mysql->query('select sleep(1)');
Redis客户端
$redis = new SwooleCoroutineRedis();$redis->connect('127.0.0.1', 6379);$val = $redis->get('key');
Http客户端
$cli = new SwooleCoroutineHttpClient('127.0.0.1', 80);$cli->setHeaders([ 'Host' => "localhost", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip',]);$cli->set([ 'timeout' => 1]);$cli->get('/index.php');echo $cli->body; $cli->close();
Swoole在2.0开始内置协程(Coroutine)的能力,提供了具备协程能力IO接口(统一在名空间SwooleCoroutine*
)。
2.0.2或更高版本已支持PHP7
协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。Swoole可以为每一个请求创建对应的协程,根据IO的状态来合理的调度协程,这会带来了以下优势:
开发者可以无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护。
同时由于swoole是在底层封装了协程,所以对比传统的php层协程框架,开发者不需要使用yield关键词来标识一个协程IO操作,所以不再需要对yield的语义进行深入理解以及对每一级的调用都修改为yield,这极大的提高了开发效率。
协程API目前针对了TCP,UDP等主流协议client的封装,包括:
可以满足大部分开发者的需求。对于私有协议,开发者可以使用协程的TCP或者UDP接口去方便的封装。
swoole_server
或者swoole_http_server
进行开发,目前只支持在onRequet
, onReceive
, onConnect
事件回调函数中使用协程。swoole2.0需要通过添加--enable-coroutine
编译参数启用协程能力,示例如下:
phpize./configure --with-php-config={path-to-php-config} --enable-coroutinemakemake install
添加编译参数,swoole server将切换到协程模式。
开启协程模式后,swoole_server
和swoole_http_server
将以为每一个请求创建对应的协程,开发者可以在onRequet
、onReceive
、onConnect
3个事件回调中使用协程客户端。
在SwooleServer
的set方法中增加了一个配置参数max_coro_num
,用于配置一个worker进程最多同时处理的协程数目。因为随着worker进程处理的协程数目的增加,其占用的内存也会增加,为了避免超出php的memory_limit
限制,请根据实际业务的压测结果设置该值,默认为3000。
当代码执行到connect()和recv()
函数时,swoole会触发进行协程切换,此时swoole可以去处理其他的事件或者接受新的请求。当此client连接
成功或者后端服务回包
后,swoole server会恢复协程上下文,代码逻辑继续从切换点开始恢复执行。开发者整个过程不需要关心整个切换过程。具体使用可以参考client的文档。
__call()
dereferencing pointer ‘v.327’ does break strict-aliasing rules
、dereferencing type-punned pointer will break strict-aliasing rules
请手动编辑Makefile,将CFLAGS = -Wall -pthread -g -O2
替换为CFLAGS = -Wall -pthread -g -O2 -fno-strict-aliasing
,然后重新编译make clean;make;make install
bool getDefer();
bool setDefer([bool $is_defer = true]);
mixed recv();
在协程版本的Client中,实现了多个客户端并发的发包功能。
通常,如果一个业务请求中需要做一次redis请求和一次mysql请求,那么网络IO会是这样子:
redis发包->redis收包->mysql发包->mysql收包
以上流程网络IO的时间就等于 redis网络IO时间 + mysql网络IO时间。
而对于协程版本的Client,网络IO可以是这样子:
redis发包->mysql发包->redis收包->mysql收包
以上流程网络IO的时间就接近于 MAX(redis网络IO时间, mysql网络IO时间)。
现在支持并发请求的Client有:
除了SwooleCoroutineClient,其他Client都实现了defer特性,用于声明延迟收包。
因为SwooleCoroutineClient的发包和收包方法是分开的,所以就不需要实现defer特性了,而其他Client的发包和收包都是在一个方法中,所以需要一个setDefer()方法声明延迟收包,然后通过recv()方法收包。
协程版本Client并发请求示例代码:
<?php$server = new SwooleHttpServer("127.0.0.1", 9502, SWOOLE_BASE);$server->set([ 'worker_num' => 1,]);$server->on('Request', function ($request, $response) { $tcpclient = new SwooleCoroutineClient(SWOOLE_SOCK_TCP); $tcpclient->connect('127.0.0.1', 9501,0.5) $tcpclient->send("hello world
"); $redis = new SwooleCoroutineRedis(); $redis->connect('127.0.0.1', 6379); $redis->setDefer(); $redis->get('key'); $mysql = new SwooleCoroutineMySQL(); $mysql->connect([ 'host' => '127.0.0.1', 'user' => 'user', 'password' => 'pass', 'database' => 'test', ]); $mysql->setDefer(); $mysql->query('select sleep(1)'); $httpclient = new SwooleCoroutineHttpClient('0.0.0.0', 9599); $httpclient->setHeaders(['Host' => "api.mp.qq.com"]); $httpclient->set([ 'timeout' => 1]); $httpclient->setDefer(); $httpclient->get('/'); $tcp_res = $tcpclient->recv(); $redis_res = $redis->recv(); $mysql_res = $mysql->recv(); $http_res = $httpclient->recv(); $response->end('Test End');});$server->start();
Swoole2.0基于setjmp
、longjmp
实现,在进行协程切换时会自动保存Zend VM的内存状态(主要是EG全局内存和vm stack)。
$server = new SwooleHttpServer('127.0.0.1', 9501, SWOOLE_BASE);#1$server->on('Request', function($request, $response) { $mysql = new SwooleCoroutineMySQL(); #2 $res = $mysql->connect([ 'host' => '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test', ]); #3 if ($res == false) { $response->end("MySQL connect fail!"); return; } $ret = $mysql->query('show tables', 2); $response->end("swoole response is ok, result=".var_export($ret, true));});$server->start();
onRequest
事件回调函数时,底层会调用C函数coro_create
创建一个协程(#1位置),同时保存这个时间点的CPU寄存器状态和ZendVM stack信息。mysql->connect
时发生IO操作,底层会调用C函数coro_save
保存当前协程的状态,包括Zend VM上下文以及协程描述信息,并调用coro_yield
让出程序控制权,当前的请求会挂起(#2位置)core_resume
恢复对应的协程,恢复ZendVM上下文,继续向下执行PHP代码(#3位置)mysql->query
的执行过程与mysql->connect
一致,也会进行一次协程切换调度end
方法返回结果,并销毁此协程相比普通的异步回调程序,协程多增加额外的内存占用。
Ubuntu16.04 + Core I5 4核 + 8G内存 PHP7.0.10
ab -c 100 -n 10000 http://127.0.0.1:9501/
测试结果:
Server Software: swoole-http-serverServer Hostname: 127.0.0.1Server Port: 9501Document Path: /Document Length: 348 bytesConcurrency Level: 100Time taken for tests: 0.883 secondsComplete requests: 10000Failed requests: 168 (Connect: 0, Receive: 0, Length: 168, Exceptions: 0)Total transferred: 4914560 bytesHTML transferred: 3424728 bytesRequests per second: 11323.69 [#/sec] (mean)Time per request: 8.831 [ms] (mean)Time per request: 0.088 [ms] (mean, across all concurrent requests)Transfer rate: 5434.67 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 0 0 0.2 0 2Processing: 0 9 9.6 6 96Waiting: 0 9 9.6 6 96Total: 0 9 9.6 6 96Percentage of the requests served within a certain time (ms) 50% 6 66% 9 75% 11 80% 12 90% 19 95% 27 98% 43 99% 51 100% 96 (longest request)
创建一个异步服务器程序,支持TCP、UDP、UnixSocket 3种协议,支持IPv4和IPv6,支持SSL/TLS单向双向证书的隧道加密。使用者无需关注底层实现细节,仅需要设置网络事件的回调函数即可。
swoole_server只能用于php-cli环境,否则会抛出致命错误
$serv = new swoole_server("127.0.0.1", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP);
$serv->set(array( 'worker_num' => 4, 'daemonize' => true, 'backlog' => 128,));
$serv->on('Connect', 'my_onConnect');$serv->on('Receive', 'my_onReceive');$serv->on('Close', 'my_onClose');
PHP中可以使用4种回调函数的风格
$serv->start();
$serv->manager_pid; //管理进程的PID,通过向管理进程发送SIGUSR1信号可实现柔性重启$serv->master_pid; //主进程的PID,通过向主进程发送SIGTERM信号可安全关闭服务器$serv->connections; //当前服务器的客户端连接,可使用foreach遍历所有连接
$serv = new SwooleServer("127.0.0.1", 9501);//设置服务器参数$serv->set(array( 'worker_num' => 8, //工作进程数量 'daemonize' => true, //是否作为守护进程));//设置事件回调函数$serv->on('connect', function ($serv, $fd) { echo "Client:Connect.
";});$serv->on('receive', function ($serv, $fd, $reactor_id, $data) { $serv->send($fd, 'Swoole: ' . $data); $serv->close($fd);});$serv->on('close', function ($serv, $fd) { echo "Client: Close.
";});//启动服务器$serv->start();
swoole_client提供了tcp/udp socket的客户端的封装代码,使用时仅需 new swoole_client即可。 swoole的socket client对比PHP提供的stream族函数有哪些好处:
除了普通的同步阻塞+select的使用方法外,swoole_client还支持异步非阻塞回调。
$client = new swoole_client(SWOOLE_SOCK_TCP);if (!$client->connect('127.0.0.1', 9501, -1)){ exit("connect failed. Error: {$client->errCode}
");}$client->send("hello world
");echo $client->recv();$client->close();
php-fpm/apache环境下只能使用同步客户端
apache环境下仅支持prefork
多进程模式,不支持prework
多线程
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);$client->on("connect", function(swoole_client $cli) { $cli->send("GET / HTTP/1.1
");});$client->on("receive", function(swoole_client $cli, $data){ echo "Receive: $data"; $cli->send(str_repeat('A', 100)."
"); sleep(1);});$client->on("error", function(swoole_client $cli){ echo "error
";});$client->on("close", function(swoole_client $cli){ echo "Connection close
";});$client->connect('127.0.0.1', 9501);
异步客户端只能使用在cli命令行环境
swoole-1.7.7增加了内置Http服务器的支持,通过几行代码即可写出一个异步非阻塞多进程的Http服务器。
常见使用场景:因为swoole是在cli命令下执行的,在传统通过nginx+fastcgi模式下很多root的shell无法执行,而使用这个swoole服务器就很好的控制rsync,git,svn等。
$http = new swoole_http_server("127.0.0.1", 9501);$http->on('request', function ($request, $response) { $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");});$http->start();
swoole_http_server对Http协议的支持并不完整,建议仅作为应用服务器。并且在前端增加Nginx作为代理
$serv = new SwooleHttpServer("127.0.0.1", 9502);$serv->on('Request', function($request, $response) { var_dump($request->get); var_dump($request->post); var_dump($request->cookie); var_dump($request->files); var_dump($request->header); var_dump($request->server); $response->cookie("User", "Swoole"); $response->header("X-Server", "Swoole"); $response->end("<h1>Hello Swoole!</h1>");});$serv->start();
通过使用apache bench工具进行压力测试,在Inter Core-I5 4核 + 8G内存的普通PC机器上,swoole_http_server可以达到近11万QPS。远远超过php-fpm,golang自带http服务器,node.js自带http服务器。性能几乎接近与Nginx的静态文件处理。
ab -c 200 -n 200000 -k http://127.0.0.1:9501
nghttp2
库,下载nghttp2后编译安装Http2
协议必须开启openssl
openssl
必须支持TLS1.2
、ALPN
、NPN
./configure --enable-openssl --enable-http2
设置http服务器的open_http2_protocol
为true
$serv->set([ 'ssl_cert_file' => $ssl_dir . '/ssl.crt', 'ssl_key_file' => $ssl_dir . '/ssl.key', 'open_http2_protocol' => true,]);
server { root /data/wwwroot/; server_name local.swoole.com; location / { if (!-e $request_filename) { proxy_pass http://127.0.0.1:9501; proxy_http_version 1.1; proxy_set_header Connection "keep-alive"; } }}
Swoole-1.8.0版本增加了对异步Http/WebSocket客户端的支持。底层是用纯C编写,拥有超高的性能。
异步HTTP客户端目前仍在实验阶段,请谨慎使用
function swoole_http_client->__construct(string $ip, int port, bool $ssl = false);
swoole-1.7.9 增加了内置的websocket服务器支持,通过几行PHP代码就可以写出一个异步非阻塞多进程的WebSocket服务器。
常见使用场景:我们在使用php开发的时候,原生最不好用的是socket类库了,而在开发IM和及时通信项目是,我们现在有了新的选择后端使用php Swoole WebSocket + 前端 html5 WebSocket;
$server = new swoole_websocket_server("0.0.0.0", 9501);$server->on('open', function (swoole_websocket_server $server, $request) { echo "server: handshake success with fd{$request->fd}
";});$server->on('message', function (swoole_websocket_server $server, $frame) { echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
"; $server->push($frame->fd, "this is server");});$server->on('close', function ($ser, $fd) { echo "client {$fd} closed
";});$server->start();
swoole_websocket_server 继承自 swoole_http_server
http 400
错误页面SwooleHttpClient
作为WebSocket客户端甚至你还可以结合使用socket.io来配合开发
Swoole-1.8.14版本增加一个兼容Redis服务器端协议的Server框架,可基于此框架实现Redis协议的服务器程序。SwooleRedisServer
继承自SwooleServer
,可调用父类提供的所有方法。
RedisServer
不需要设置onReceive
回调。实例程序:https://github.com/swoole/swoole-src/blob/master/examples/redis/server.php
redis-cli
、redis-benchmark
使用Redis客户端,需要安装hiredis库。下载hiredis
源码后,执行
make -jsudo make installsudo ldconfig
编译swoole是,在configure
指令中加入--enable-async-redis
./configure --enable-async-redismake cleanmake -jsudo make install
$redis = new SwooleRedis;$redis->connect('127.0.0.1', 6379, function ($redis, $result) { $redis->set('test_key', 'value', function ($redis, $result) { $redis->get('test_key', function ($redis, $result) { var_dump($result); }); });});$cli = new SwooleHttpClient('127.0.0.1', 80);$cli->setHeaders(array('User-Agent' => 'swoole-http-client'));$cli->setCookies(array('test' => 'value'));$cli->post('/dump.php', array("test" => 'abc'), function ($cli) { var_dump($cli->body); $cli->get('/index.php', function ($cli) { var_dump($cli->cookies); var_dump($cli->headers); });});
mysqlnd
和 mysqli
2个扩展,请使用php -m
或phpinfo
确认PHP是否有这2个扩展。--enable-async-mysql
1.8.6版本已移除对
mysqli
和mysqlnd
扩展的依赖,并改为内置,无需额外的编译参数开启
$db = new SwooleMySQL;$server = array( 'host' => '127.0.0.1', 'user' => 'test', 'password' => 'test', 'database' => 'test',);$db->connect($server, function ($db, $result) { $db->query("show tables", function (SwooleMySQL $db, $result) { if ($result === false) { var_dump($db->error, $db->errno); } elseif ($result === true) { var_dump($db->affected_rows, $db->insert_id); } else { var_dump($result); $db->close(); } });});
MySQL短连接每次请求操作数据库都需要建立与MySQL服务器建立TCP连接,这是需要时间开销的。TCP连接需要3次网络通信。这样就增加了一定的延时和额外的IO消耗。请求结束后会关闭MySQL连接,还会发生3/4次网络通信。
close操作不会增加响应延时,原因是close后是由操作系统自动进行通信的,应用程序感知不到
长连接就可以避免每次请求都创建连接的开销,节省了时间和IO消耗。提升了PHP程序的性能。
在cli环境下,PHP程序需要长时间运行,客户端与MySQL服务器之间的TCP连接是不稳定的。
这时PHP程序中的MySQL连接就失效了。如果仍然执行mysql_query,就会报一个“MySQL server has gone away”的错误。程序处理不到就直接遇到致命错误并退出了。所以PHP程序中需要断线重连。
有很多人提出了mysql_ping的方案,每次mysql_query进行连接检测或者定时连接检测。这个方案不是最好的。原因是
最佳的方案是,进行断线重连 。它的原理是:
MySQL异步是指将MySQL连接事件驱动化,这样就编程了非阻塞IO。数据库操作并不会阻塞进程,在MySQL-Server返回结果时再执行对应的逻辑。
有几个点需要注意一下:
异步回调程序中,异步MySQL并没有提升性能。异步最大的好处是可以高并发,如果并发1万个请求,那么就需要建立1万个MySQL连接,这会给MySQL-Server带来巨大的压力。
MySQL是根据连接数分配资源的,一个连接需要开启一个线程。1000连接那么需要维持1000线程才可以。线程数量增加后,线程间切换会占用大量CPU资源
MySQL短连接反而不会出现此问题,因为短连接在使用完后就释放了。不会占用MySQL-Server的连接资源
虽然应用层代码使用异步回调避免了自身的阻塞,实际上真正的瓶颈是数据库服务器。异步MySQL还带来了额外的编程复杂度,所以除非是特殊场景的需求,否则不建议使用异步MySQL。
如果程序中坚持要使用异步,那么必须是异步MySQL+连接池的形式。超过规定的MySQL最大连接后,应当对SQL请求进行排队,而不是创建新连接,避免大量并发请求导致MySQL服务器崩溃。
连接池是可以有效降低MySQL-Server负载的。原理是 连接池使用一个共享资源的模式,如并发100个请求,实际上并不是每个请求的所有时间都在执行SQL查询。这样100个请求,共享20个MySQL连接就可以满足需求了。当一个请求操作完数据库后,开始进入模板渲染等流程,这时就会释放数据库连接给其他的请求使用。
连接池仅在超大型应用中才有价值。普通的应用采用MySQL长连接方案,每个php-fpm创建一个MySQL连接,每台机器开启100个php-fpm进程。如果有10台机器,每台机器并发的请求为100。实际上只需要创建1000个MySQL连接就能满足需求,数据库的压力并不大。即使有100台机器,硬件配置好的存储服务器依然可以承受。
达到数百或者数千台应用服务器时,MySQL服务器就需要维持十万级的连接。这时数据库的压力就会非常大了。连接池技术就可以派上用场了,可以大大降低数据库连接数。
基于swoole的AsyncTask模块实现的连接池是完美方案,编程简单,没有数据同步和锁的问题。甚至可以多个服务共享连接池。缺点是1, 灵活性不如多线程连接池,无法动态增减连接。2, 有一次进程间通信的开销。
node.js/ngx_lua等在多进程的模式下,无法开发出真正的连接池,除非也像swoole_task这样来实现
swoole1.6.12后增加了异步文件读写,异步DNS等特性。自此建立了完整的异步并行API。
Task进程
是同步阻塞的,没有EventLoop
,因此无法使除定时器之外的用任何异步IOswoole_async_set
关闭signalfd
特性Swoole支持3种类型的异步文件读写IO,可以使用swoole_async_set
来设置AIO模式。
此函数可以设置异步IO相关的选项。
swoole_async_set(array $setting);
SWOOLE_AIO_BASE
(使用类似于Node.js的线程池同步阻塞模拟异步)、SWOOLE_AIO_LINUX
(Linux Native AIO) 2种模式Linux Native AIO的优点是由内核支持是真正的异步文件IO,缺点是只支持DirectIO,无法利用到系统的PageCache
swoole_async模块目前为实验性质,不建议在生产环境使用,请使用PHP的文件读写函数。
基于Linux Native AIO系统调用,是真正的异步IO,并非阻塞模拟。
优点:
缺点:
基于线程池模拟实现,文件读写请求投递到任务队列,然后由AIO线程读写文件,完成后通知主线程。AIO线程本身是同步阻塞的。所以并非真正的异步IO。
优点:
可修改
thread_num
项设置启用的AIO线程数量
缺点:
$fp = stream_socket_client("tcp://127.0.0.1:80", $code, $msg, 3);$http_request = "GET /index.html HTTP/1.1
";fwrite($fp, $http_request);SwooleEvent::add($fp, function($fp){ echo fread($fp, 8192); swoole_event_del($fp); fclose($fp);});SwooleTimer::after(2000, function() { echo "2000ms timeout
";});SwooleTimer::tick(1000, function() { echo "1000ms interval
";});
swoole 的异步任务task系统可以很方便的为我们在开发的过程中调用异步任务的执行,而无需等待。
task模块用来做一些异步的慢速任务,比如webim中发广播,发送邮件,异步订单处理、异步支付处理等。
node.js 假如有10万个连接,要发广播时,那会循环10万次,这时候程序不能做任何事情,不能接受新的连接,也不能收包发包。
而swoole不同,丢给task进程之后,worker进程可以继续处理新的数据请求。任务完成后会异步地通知worker进程告诉它此任务已经完成。
当然task模块的作用还不仅如此,实现PHP的数据库连接池,异步队列等,还需要进一步挖掘。
$serv = new SwooleServer("127.0.0.1", 9502);$serv->set(array('task_worker_num' => 4));$serv->on('Receive', function($serv, $fd, $from_id, $data) { $task_id = $serv->task("Async"); echo "Dispath AsyncTask: id=$task_id
";});$serv->on('Task', function ($serv, $task_id, $from_id, $data) { echo "New AsyncTask[id=$task_id]".PHP_EOL; $serv->finish("$data -> OK");});$serv->on('Finish', function ($serv, $task_id, $data) { echo "AsyncTask[$task_id] Finish: $data".PHP_EOL;});$serv->start();
投递一个异步任务到task_worker池中。此函数是非阻塞的,执行完毕会立即返回。worker进程可以继续处理新的请求。
int swoole_server::task(mixed $data, int $dst_worker_id = -1) $task_id = $serv->task("some data");//swoole-1.8.6或更高版本$serv->task("taskcallback", -1, function (swoole_server $serv, $task_id, $data) { echo "Task Callback: "; var_dump($task_id, $data);});
0 - (serv->task_worker_num -1)
$task_id
,表示此任务的ID。如果有finish回应,onFinish
回调中会携带$task_id
参数onFinish
函数,如果任务设置了回调函数,Task返回结果时会直接执行制定的回调函数,不再执行Server的onFinish
回调$dst_worker_id在1.6.11+后可用,默认为随机投递
$task_id是从0-42亿的整数,在当前进程内是唯一的
task方法不能在task进程/用户自定义进程中调用
此功能用于将慢速的任务异步地去执行,比如一个聊天室服务器,可以用它来进行发送广播。当任务完成时,在task进程中调用$serv->finish("finish")
告诉worker进程此任务已完成。当然swoole_server->finish
是可选的。
task底层使用Unix Socket管道通信,是全内存的,没有IO消耗。单进程读写性能可达100万/s,不同的进程使用不同的管道通信,可以最大化利用多核。
AsyncTask功能在1.6.4版本增加,默认不启动task功能,需要在手工设置task_worker_num来启动此功能
task_worker的数量在swoole_server::set参数中调整,如task_worker_num => 64,表示启动64个进程来接收异步任务
swoole_server->task/taskwait/finish 3个方法当传入的$data
数据超过8K时会启用临时文件来保存。当临时文件内容超过server->package_max_length
时底层会抛出一个警告。
WARN: task package is too big.
server->package_max_length 默认为2M
函数原型:
string $result = swoole_server->taskwait(mixed $task_data, float $timeout = 0.5, int $dst_worker_id = -1);
taskwait与task方法作用相同,用于投递一个异步的任务到task进程池去执行。与task不同的是taskwait是阻塞等待的,直到任务完成或者超时返回。
$result为任务执行的结果,由$serv->finish函数发出。如果此任务超时,这里会返回false。
taskwait是阻塞接口,如果你的Server是全异步的请使用swoole_server::task和swoole_server::finish,不要使用taskwait
第3个参数可以制定要给投递给哪个task进程,传入ID即可,范围是0 - serv->task_worker_num
$dst_worker_id在1.6.11+后可用,默认为随机投递
taskwait方法不能在task进程中调用
在task_worker进程内被调用。worker进程可以使用swoole_server_task函数向task_worker进程投递新的任务。当前的Task进程在调用onTask
回调函数时会将进程状态切换为忙碌,这时将不再接收新的Task,当onTask
函数返回时会将进程状态切换为空闲然后继续接收新的Task。
function onTask((swoole_server $serv, int $task_id, int $src_worker_id, string $data));
1.7.2以上的版本,在onTask函数中 return字符串,表示将此内容返回给worker进程。worker进程中会触发onFinish函数,表示投递的task已完成。
1.7.2以前的版本,需要调用swoole_server->finish()
函数将结果返回给worker进程
Swoole 2.0正式版发布了。2.0版本最大的更新是增加了对协程(Coroutine)的支持。正式版已同时支持PHP5和PHP7。基于Swoole2.0协程PHP开发者可以已同步的方式编写代码,底层自动进行协程调度,转变为异步IO。解决了传统异步编程嵌套回调的问题。
与Node.js(ES6+)、Python等语言使用yield/generator、async/await的实现方式相比,Swoole协程无需修改代码添加额外的关键词。
与Go语言的goroutine相比,Swoole协程是内置式的,应用层代码无需添加go关键词启动协程,只需要使用封装好的协程客户端即可,使用更简单。另外Swoole协程的IO组件在底层内置了超时机制,不需要使用复杂的select/chan/timer实现客户端超时。
目前Swoole底层内置的协程客户端组件包括:udpclient、tcpclient、httpclient、redisclient、mysqlclient,基本涵盖了开发者常用的几种通信协议。协程组件只能在服务器的onConnect、onRequest、onReceive、onMessage 回调函数中使用。
使用示例:
$server = new SwooleHttpServer('127.0.0.1', 9501);/* 触发on request事件时,SWOOLE会开辟一个协程栈,对协程栈进行初始化 */$server->on('Request', function ($request, $response) { $tcp_cli = new SwooleCoroutineClient(SWOOLE_SOCK_TCP); /** client在调用connect函数后,SWOOLE会将PHP上下文信息保存到当前栈内 然后将协程挂起,待确认连接成功后,触发epoll事件,然后协程切换 恢复PHP上下文信息,返回结果,继续执行PHP代码 */ if ($tcp_cli->connect('127.0.0.1', 9906) === false) { $response->end("connect server failed."); return; } $tcp_cli->send('test for the coro'); /* client在调用recv函数后,SWOOLE会将PHP上下文信息保存到当前栈内 然后将协程挂起待后端svr回包,触发epoll事件,然后协程切换 恢复PHP上下文信息,返回结果,继续执行PHP代码 如果后端在设定的超时时间内,未能回包,返回false client的errCode定为110 */ $ret = $tcp_cli->recv(100); $tcp_cli->close(); if ($ret) { $response->end(" swoole response is ok"); } else { $response->end(" recv failed error : {$tcp_cli->errCode}"); }});$server->start();
UDP客户端
$udp_cli = new SwooleCoroutineClient(SWOOLE_SOCK_UDP);$ret = $udp_cli->connect('127.0.0.1', 9906);$udp_cli->send('test for the coro');$ret = $udp_cli->recv(100);$udp_cli->close();if ($ret){ $response->end("swoole response is ok");}else{ $response->end("recv failed error : {$client->errCode}");}
MySQL客户端
$swoole_mysql = new SwooleCoroutineMySQL();$swoole_mysql->connect([ 'host' => '127.0.0.1', 'user' => 'user', 'password' => 'pass', 'database' => 'test']);$res = $swoole_mysql->query('select sleep(1)');
Redis客户端
$redis = new SwooleCoroutineRedis();$redis->connect('127.0.0.1', 6379);$val = $redis->get('key');
Http客户端
$cli = new SwooleCoroutineHttpClient('127.0.0.1', 80);$cli->setHeaders([ 'Host' => "localhost", "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip',]);$cli->set([ 'timeout' => 1]);$cli->get('/index.php');echo $cli->body; $cli->close();
Swoole在2.0开始内置协程(Coroutine)的能力,提供了具备协程能力IO接口(统一在名空间SwooleCoroutine*
)。
2.0.2或更高版本已支持PHP7
协程可以理解为纯用户态的线程,其通过协作而不是抢占来进行切换。相对于进程或者线程,协程所有的操作都可以在用户态完成,创建和切换的消耗更低。Swoole可以为每一个请求创建对应的协程,根据IO的状态来合理的调度协程,这会带来了以下优势:
开发者可以无感知的用同步的代码编写方式达到异步IO的效果和性能,避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护。
同时由于swoole是在底层封装了协程,所以对比传统的php层协程框架,开发者不需要使用yield关键词来标识一个协程IO操作,所以不再需要对yield的语义进行深入理解以及对每一级的调用都修改为yield,这极大的提高了开发效率。
协程API目前针对了TCP,UDP等主流协议client的封装,包括:
可以满足大部分开发者的需求。对于私有协议,开发者可以使用协程的TCP或者UDP接口去方便的封装。
swoole_server
或者swoole_http_server
进行开发,目前只支持在onRequet
, onReceive
, onConnect
事件回调函数中使用协程。swoole2.0需要通过添加--enable-coroutine
编译参数启用协程能力,示例如下:
phpize./configure --with-php-config={path-to-php-config} --enable-coroutinemakemake install
添加编译参数,swoole server将切换到协程模式。
开启协程模式后,swoole_server
和swoole_http_server
将以为每一个请求创建对应的协程,开发者可以在onRequet
、onReceive
、onConnect
3个事件回调中使用协程客户端。
在SwooleServer
的set方法中增加了一个配置参数max_coro_num
,用于配置一个worker进程最多同时处理的协程数目。因为随着worker进程处理的协程数目的增加,其占用的内存也会增加,为了避免超出php的memory_limit
限制,请根据实际业务的压测结果设置该值,默认为3000。
当代码执行到connect()和recv()
函数时,swoole会触发进行协程切换,此时swoole可以去处理其他的事件或者接受新的请求。当此client连接
成功或者后端服务回包
后,swoole server会恢复协程上下文,代码逻辑继续从切换点开始恢复执行。开发者整个过程不需要关心整个切换过程。具体使用可以参考client的文档。
__call()
dereferencing pointer ‘v.327’ does break strict-aliasing rules
、dereferencing type-punned pointer will break strict-aliasing rules
请手动编辑Makefile,将CFLAGS = -Wall -pthread -g -O2
替换为CFLAGS = -Wall -pthread -g -O2 -fno-strict-aliasing
,然后重新编译make clean;make;make install
bool getDefer();
bool setDefer([bool $is_defer = true]);
mixed recv();
在协程版本的Client中,实现了多个客户端并发的发包功能。
通常,如果一个业务请求中需要做一次redis请求和一次mysql请求,那么网络IO会是这样子:
redis发包->redis收包->mysql发包->mysql收包
以上流程网络IO的时间就等于 redis网络IO时间 + mysql网络IO时间。
而对于协程版本的Client,网络IO可以是这样子:
redis发包->mysql发包->redis收包->mysql收包
以上流程网络IO的时间就接近于 MAX(redis网络IO时间, mysql网络IO时间)。
现在支持并发请求的Client有:
除了SwooleCoroutineClient,其他Client都实现了defer特性,用于声明延迟收包。
因为SwooleCoroutineClient的发包和收包方法是分开的,所以就不需要实现defer特性了,而其他Client的发包和收包都是在一个方法中,所以需要一个setDefer()方法声明延迟收包,然后通过recv()方法收包。
协程版本Client并发请求示例代码:
<?php$server = new SwooleHttpServer("127.0.0.1", 9502, SWOOLE_BASE);$server->set([ 'worker_num' => 1,]);$server->on('Request', function ($request, $response) { $tcpclient = new SwooleCoroutineClient(SWOOLE_SOCK_TCP); $tcpclient->connect('127.0.0.1', 9501,0.5) $tcpclient->send("hello world
"); $redis = new SwooleCoroutineRedis(); $redis->connect('127.0.0.1', 6379); $redis->setDefer(); $redis->get('key'); $mysql = new SwooleCoroutineMySQL(); $mysql->connect([ 'host' => '127.0.0.1', 'user' => 'user', 'password' => 'pass', 'database' => 'test', ]); $mysql->setDefer(); $mysql->query('select sleep(1)'); $httpclient = new SwooleCoroutineHttpClient('0.0.0.0', 9599); $httpclient->setHeaders(['Host' => "api.mp.qq.com"]); $httpclient->set([ 'timeout' => 1]); $httpclient->setDefer(); $httpclient->get('/'); $tcp_res = $tcpclient->recv(); $redis_res = $redis->recv(); $mysql_res = $mysql->recv(); $http_res = $httpclient->recv(); $response->end('Test End');});$server->start();
Swoole2.0基于setjmp
、longjmp
实现,在进行协程切换时会自动保存Zend VM的内存状态(主要是EG全局内存和vm stack)。
$server = new SwooleHttpServer('127.0.0.1', 9501, SWOOLE_BASE);#1$server->on('Request', function($request, $response) { $mysql = new SwooleCoroutineMySQL(); #2 $res = $mysql->connect([ 'host' => '127.0.0.1', 'user' => 'root', 'password' => 'root', 'database' => 'test', ]); #3 if ($res == false) { $response->end("MySQL connect fail!"); return; } $ret = $mysql->query('show tables', 2); $response->end("swoole response is ok, result=".var_export($ret, true));});$server->start();
onRequest
事件回调函数时,底层会调用C函数coro_create
创建一个协程(#1位置),同时保存这个时间点的CPU寄存器状态和ZendVM stack信息。mysql->connect
时发生IO操作,底层会调用C函数coro_save
保存当前协程的状态,包括Zend VM上下文以及协程描述信息,并调用coro_yield
让出程序控制权,当前的请求会挂起(#2位置)core_resume
恢复对应的协程,恢复ZendVM上下文,继续向下执行PHP代码(#3位置)mysql->query
的执行过程与mysql->connect
一致,也会进行一次协程切换调度end
方法返回结果,并销毁此协程相比普通的异步回调程序,协程多增加额外的内存占用。
Ubuntu16.04 + Core I5 4核 + 8G内存 PHP7.0.10
ab -c 100 -n 10000 http://127.0.0.1:9501/
测试结果:
Server Software: swoole-http-serverServer Hostname: 127.0.0.1Server Port: 9501Document Path: /Document Length: 348 bytesConcurrency Level: 100Time taken for tests: 0.883 secondsComplete requests: 10000Failed requests: 168 (Connect: 0, Receive: 0, Length: 168, Exceptions: 0)Total transferred: 4914560 bytesHTML transferred: 3424728 bytesRequests per second: 11323.69 [#/sec] (mean)Time per request: 8.831 [ms] (mean)Time per request: 0.088 [ms] (mean, across all concurrent requests)Transfer rate: 5434.67 [Kbytes/sec] receivedConnection Times (ms) min mean[+/-sd] median maxConnect: 0 0 0.2 0 2Processing: 0 9 9.6 6 96Waiting: 0 9 9.6 6 96Total: 0 9 9.6 6 96Percentage of the requests served within a certain time (ms) 50% 6 66% 9 75% 11 80% 12 90% 19 95% 27 98% 43 99% 51 100% 96 (longest request)