Runtime
相对于Swoole1.x
,Swoole4+
提供了协程这个大杀器,所有业务代码都是同步的,但底层的IO却是异步的,保证并发的同时避免了传统异步回调所带来的离散的代码逻辑和陷入多层回调中导致代码无法维护, 要达到这个效果必须所有的IO
请求都是异步IO,而Swoole1.x
时代的提供的Mysql
、Redis
等客户端虽然是异步IO,但是是异步回调的编程方式,不是协程方式,所以我们在Swoole4
时代移除了这些客户端。
为了解决这些客户端的协程支持问题我们做了大量的工作:
刚开始,我们针对每种类型的客户端都做了一个协程客户端,都在这里,但这样做有3个问题:
- 实现复杂,每个客户端细枝末节的协议都很复杂,想都完美的支持工作量巨大。
- 用户需要更改的代码比较多,比如原来查询
Mysql
是用的PHP原生的PDO
,那么现在需要用Swoole\Coroutine\MySQL的方法。 - 我们很难覆盖到所有的操作,比如
proc_open()
、sleep()
函数等等也可能阻塞住导致程序变成同步阻塞的。
针对上述问题,我们换了实现思路,采用
Hook
原生PHP函数的方式实现协程客户端,通过一行代码就可以让原来的同步IO的代码变成可以协程调度的异步IO,即一键协程化
。
!> 此特性在v4.3
版本后开始稳定,能Hook
的函数也越来越多,所以有些之前写的协程客户端已经不再推荐使用了,详情查看协程客户端, 例如:在v4.3+
支持了文件操作(file_get_contents
、fread
等)的Hook
,如果您使用的是v4.3+
就不要再用Swoole提供的协程文件操作了。
函数原型
Co::set(['hook_flags'=> SWOOLE_HOOK_ALL]);//v4.4+版本使用此方法。
或
Swoole\Runtime::enableCoroutine(int $flags = SWOOLE_HOOK_ALL);
通过flags
设置要Hook
的函数的范围,同时开启多个flags需要|
操作,例如Co::set(['hook_flags'=> SWOOLE_HOOK_TCP|SWOOLE_HOOK_SLEEP]);
,被Hook
的函数就无法在非协程容器中使用。
选项
flags
支持的选项有:
SWOOLE_HOOK_ALL
打开下述所有类型的flags (不包括CURL)
Co::set(['hook_flags' => SWOOLE_HOOK_ALL]);//不包括CURL
Co::set(['hook_flags' => SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL]);//真正的hook所有类型,包括CURL
SWOOLE_HOOK_TCP
v4.1
开始支持,TCP Socket类型的stream,包括最常见的Redis
,PDO
,Mysqli
,以及用PHP的streams系列函数操作TCP连接的操作,都可以Hook
,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_TCP]);
Co\run(function() {
for ($c = 100; $c--;) {
go(function () {//创建100个协程
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);//此处产生协程调度,cpu切到下一个协程,不会阻塞进程
$redis->get('key');//此处产生协程调度,cpu切到下一个协程,不会阻塞进程
});
}
});
上述代码使用的就是原生的Redis
类,但是其实已经变成了异步IO
,Co\run()
是创建了协程容器,go()
是创建协程,这两个操作在Swoole
提供的Swoole\Server类簇都是自动做好的,不需要手动做,参考enable_coroutine。
也就是说传统的PHP
程序员用最熟悉的逻辑代码就能写出高并发、高性能的程序,如下:
Co::set(['hook_flags' => SWOOLE_HOOK_TCP]);
$http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->set(['enable_coroutine' => true]);
$http->on('request', function ($request, $response) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);//此处产生协程调度,cpu切到下一个协程(下一个请求),不会阻塞进程
$redis->get('key');//此处产生协程调度,cpu切到下一个协程(下一个请求),不会阻塞进程
});
$http->start();
SWOOLE_HOOK_UNIX
v4.2
开始支持。Unix Stream Socket
类型的stream,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_UNIX]);
Co\run(function () {
go(function () {
$socket = stream_socket_server(
'unix://swoole.sock', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN
);
if (!$socket) {
echo "$errstr ($errno)" . PHP_EOL;
exit(1);
}
while (stream_socket_accept($socket)) {
}
});
echo "here" . PHP_EOL;//优先执行子协程(参考"父子协程优先级"),但会马上产生协程调度执行此行。
});
SWOOLE_HOOK_UDP
v4.2
开始支持。UDP Socket类型的stream,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_UDP]);
Co\run(function () {
go(function () {
$socket = stream_socket_server(
'udp://0.0.0.0:6666',
$errno, $errstr, STREAM_SERVER_BIND
);
if (!$socket) {
echo "$errstr ($errno)" . PHP_EOL;
exit(1);
}
while (stream_socket_recvfrom($socket, 1, 0)) {
}
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_UDG
v4.2
开始支持。Unix Dgram Socket类型的stream,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_UDG]);
Co\run(function () {
go(function () {
$socket = stream_socket_server(
'udg://swoole.sock', $errno, $errstr, STREAM_SERVER_BIND
);
if (!$socket) {
echo "$errstr ($errno)" . PHP_EOL;
exit(1);
}
while (stream_socket_recvfrom($socket, 1, 0)) {
}
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_SSL
v4.2
开始支持。SSL Socket类型的stream,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_SSL]);
Co\run(function () {
go(function () {
$host = 'host.domain.tld';
$port = 1234;
$timeout = 10;
$cert = '/path/to/your/certchain/certchain.pem';
$context = stream_context_create(array('ssl'=>array('local_cert'=> $cert,
)));
if ($fp = stream_socket_client('ssl://'.$host.':'.$port, $errno, $errstr, 30,
STREAM_CLIENT_CONNECT, $context)) {
echo "connected\n";
} else {
echo "ERROR: $errno - $errstr \n";
}
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_TLS
v4.2
开始支持。TLS Socket类型的stream,参考。
示例:
Co::set(['hook_flags' => SWOOLE_HOOK_TLS]);
SWOOLE_HOOK_SLEEP
v4.2
开始支持。sleep
函数的Hook
,包括了sleep
、usleep
、time_nanosleep
、time_sleep_until
,由于底层的定时器最小粒度是1ms
,因此使用usleep
等高精度睡眠函数时,如果设置为低于1ms
时,将直接使用sleep
系统调用。可能会引起非常短暂的睡眠阻塞。示例:
Co::set(['hook_flags' => SWOOLE_HOOK_SLEEP]);
Co\run(function () {
go(function () {
sleep(1);
echo '1' . PHP_EOL;
});
go(function () {
echo '2' . PHP_EOL;
});
});
//输出
2
1
SWOOLE_HOOK_FILE
v4.3
开始支持。
文件操作的
Hook
,支持的函数有:fopen
fread
/fgets
fwrite
/fputs
file_get_contents
、file_put_contents
unlink
mkdir
rmdir
示例:
Co::set(['hook_flags' => SWOOLE_HOOK_FILE]);
Co\run(function () {
go(function () {
$fp = fopen("test.log", "a+");
fwrite($fp, str_repeat('A', 2048));
fwrite($fp, str_repeat('B', 2048));
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_STREAM_FUNCTION
v4.4
开始支持。stream_select()
的Hook
,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_STREAM_FUNCTION]);
Co\run(function () {
go(function () {
$fp1 = stream_socket_client("tcp://www.baidu.com:80", $errno, $errstr, 30);
$fp2 = stream_socket_client("tcp://www.qq.com:80", $errno, $errstr, 30);
if (!$fp1) {
echo "$errstr ($errno) \n";
} else {
fwrite($fp1, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n");
$r_array = [$fp1, $fp2];
$w_array = $e_array = null;
$n = stream_select($r_array, $w_array, $e_array, 10);
$html = '';
while (!feof($fp1)) {
$html .= fgets($fp1, 1024);
}
fclose($fp1);
}
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_BLOCKING_FUNCTION
v4.4
开始支持。这里的blocking function
包括了:gethostbyname
、exec
、shell_exec
,示例:
Co::set(['hook_flags' => SWOOLE_HOOK_BLOCKING_FUNCTION]);
Co\run(function () {
go(function () {
while (true) {
exec("cat");
}
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_PROC
v4.4
开始支持。Hook proc*
函数,包括了:proc_open
、proc_close
、proc_get_status
、proc_terminate
。
示例:
Co::set(['hook_flags' => SWOOLE_HOOK_PROC]);
Co\run(function () {
go(function () {
$descriptorspec = array(
0 => array("pipe", "r"), // stdin, child process read from it
1 => array("pipe", "w"), // stdout, child process write to it
);
$process = proc_open('php', $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], '<?php echo "I am process\n" ?>');
fclose($pipes[0]);
while (true) {
echo fread($pipes[1], 1024);
}
fclose($pipes[1]);
$return_value = proc_close($process);
echo "command returned $return_value" . PHP_EOL;
}
});
echo "here" . PHP_EOL;
});
SWOOLE_HOOK_CURL
v4.4LTS后或v4.5
开始正式支持。
CURL的HOOK,支持的函数有:
- curl_init
- curl_setopt
- curl_exec
- curl_multi_getcontent
- curl_setopt_array
- curl_error
- curl_getinfo
- curl_errno
- curl_close
- curl_reset
示例:
Co::set(['hook_flags' => SWOOLE_HOOK_CURL]);
Co\run(function () {
go(function () {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://www.xinhuanet.com/");
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
var_dump($result);
});
echo "here" . PHP_EOL;
});
!> Swoole\Runtime::enableCoroutine()
VS Co::set(['hook_flags])
用哪个
Swoole\Runtime::enableCoroutine()
,可以在服务启动后(运行时)动态设置flags,调用方法后当前进程内全局生效,应该放在整个项目开始以获得100%覆盖的效果。Co::set()
可以理解为PHP的ini_set()
,需要在Server->start()前或Co\run()前调用,否则设置的hook_flags
不会生效,在v4.4+
版本应该用此种方式设置flags
。- 无论是
Co::set(['hook_flags])
还是Swoole\Runtime::enableCoroutine()
都应该只调用一次,重复调用会被覆盖。
方法
getHookFlags()
!> Swoole版本 >= v4.4.12
获取当前已Hook
内容的flags
,可能会与开启Hook
时传入的flags
不一致(由于未Hook
成功的flags
将会被清除)
Swoole\Runtime::getHookFlags(): int;
常见的Hook列表
可用列表
redis
扩展- 使用
mysqlnd
模式的pdo_mysql
、mysqli
扩展,如果未启用mysqlnd
将不支持协程化 soap
扩展file_get_contents
、fopen
stream_socket_client
(predis
、php-amqplib
)stream_socket_server
stream_select
(需要4.3.2
以上版本)fsockopen
proc_open
(需要4.4.0
以上版本)curl
不可用列表
mysql
:底层使用libmysqlclient
mongo
:底层使用mongo-c-client
pdo_pgsql
pdo_ori
pdo_odbc
pdo_firebird
API变更
v4.3
及以前版本,enableCoroutine
的API需要2个参数。
Swoole\Runtime::enableCoroutine(bool $enable = true, int $flags = SWOOLE_HOOK_ALL);
$enable
:打开或关闭Hook。$flags
:选择要Hook
的类型,可以多选,默认为全选。仅在$enable = true
时有效。
!> Runtime::enableCoroutine(false)
关闭上一次设置的所有选项协程Hook
设置。