Swoole 是一个面向 PHP 的高性能的事件驱动、异步、基于协程的并发库。
通过Docker运行Swoole程序
docker run --rm phpswoole/swoole "php --ri swoole"
有关如何使用它的详细信息,请参阅:如何使用此图像。
https://wiki.swoole.com/
$http = new SwooleHttpServer('127.0.0.1', 9501);$http->set(['hook_flags' => SWOOLE_HOOK_ALL]);$http->on('request', function ($request, $response) { $结果=[]; Co::join([go(function () use (&$result) {$result['google'] = file_get_contents("https://www.google.com/"); }),go(function () use (&$result) {$result['taobao'] = file_get_contents("https://www.taobao.com/"); }) ]);$response->end(json_encode($result)); });$http->start();
Corun(function() {Cogo(function() {while(1) {sleep(1);$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);echo fread ($fp, 8192), PHP_EOL; } });Cogo(function() {$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);while(1) {$conn = stream_socket_accept($fp) ;fwrite($conn, '当地时间是 ' .日期('n/j/Y g:i a')); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);while(true) {$redis->subscribe(['test'], function ($instance, $channelName, $message) {echo '新的redis消息:'.$channelName, "==>", $message, PHP_EOL; }); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);$count = 0;while(true) {sleep(2);$redis- >发布('测试','你好,世界,计数='.$count++); } }); });
Swoole 在底层挂钩了 PHP 的阻塞 io 函数,并自动将其转换为非阻塞函数,使得这些函数可以在协程中并发调用。
ext-curl(支持 symfony 和 guzzle)
ext-redis
ext-mysqli
ext-pdo_mysql
ext-pdo_pgsql
ext-pdo_sqlite
ext-pdo_oracle
ext-pdo_odbc
流函数(例如stream_socket_client/stream_socket_server),支持TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
外部插座
外皂
睡眠/usleep/time_sleep_until
进程打开
gethostbyname/shell_exec/exec
fread/fopen/fsockopen/fwrite/flock
IDE 帮助器和 API:https://github.com/swoole/ide-helper
推特:https://twitter.com/phpswoole
不和谐:https://discord.swoole.dev
中文社区:https://wiki.swoole.com/#/other/discussion
Project Awesome Swoole 维护着一份与 Swoole 相关的精彩内容的精选列表,包括
基于 Swoole 的框架和库。
将 Swoole 与流行的 PHP 框架集成的软件包,包括 Laravel、Symfony、Slim 和 Yii。
有关 Swoole 的书籍、视频和其他学习材料。
用于开发基于 Swoole 的应用程序的调试、分析和测试工具。
协程友好的包和库。
其他 Swoole 相关项目和资源。
Swoole 中的网络层是基于事件的,并充分利用了底层的 epoll/kqueue 实现,使得服务数百万个请求变得非常容易。
Swoole 4.x采用了全新的引擎内核,现在已经拥有了全职的开发团队,因此我们正在进入PHP历史上前所未有的时期,这为性能的快速进化提供了独特的可能性。
Swoole 4.x 或更高版本支持高可用的内置协程,您可以使用完全同步的代码来实现异步性能。 PHP代码无需任何额外关键字,底层自动协程调度。
开发者可以将协程理解为超轻量级线程,可以轻松地在单个进程中创建数千个协程。
并发10K请求从MySQL读取数据仅需0.2s!
$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () {$mysql = new SwooleCoroutineMySQL;$mysql->connect(['主机' => '127.0.0.1','用户' => 'root','密码' => 'root','数据库' => '测试']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0 ); } }); } });回显“使用”。 (microtime(true) - $s) 。 's';
您可以在单个事件循环上创建多个服务:TCP、HTTP、Websocket 和 HTTP2,并轻松处理数千个请求。
函数 tcp_pack(string $data): string{return pack('N', strlen($data)) 。 $数据; }function tcp_unpack(string $data): string{return substr($data, 4, unpack('N', substr($data, 0, 4))[1]); }$tcp_options = ['open_length_check' => true,'package_length_type' => 'N','package_length_offset' => 0,'package_body_offset' => 4];
$server = new SwooleWebSocketServer('127.0.0.1', 9501, SWOOLE_BASE);$server->set(['open_http2_protocol' => true]);// http && http2$server->on('request', function ( SwooleHttpRequest $request, SwooleHttpResponse $response) {$response->end('Hello ' . $请求->rawcontent()); });// websocket$server->on('message', function (SwooleWebSocketServer $server, SwooleWebSocketFrame $frame) {$server->push($frame->fd, 'Hello ' . $frame->data); });// tcp$tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP);$tcp_server->set($tcp_options);$tcp_server->on('接收', function (SwooleServer $服务器,int $fd,int $reactor_id,字符串$data) {$server->send($fd, tcp_pack('你好'。tcp_unpack($data))); });$服务器->开始();
无论您进行 DNS 查询还是发送请求或接收响应,所有这些都是由协程自动调度的。
go(function () {// http$http_client = new SwooleCoroutineHttpClient('127.0.0.1', 9501);assert($http_client->post('/', 'Swoole Http'));var_dump($http_client->body );// websocket$http_client->upgrade('/');$http_client->push('Swoole Websocket');var_dump($http_client->recv()->data); });go(function () {// http2$http2_client = new SwooleCoroutineHttp2Client('localhost', 9501);$http2_client->connect();$http2_request = new SwooleHttp2Request;$http2_request->method = 'POST';$ http2_request->data = 'Swoole Http2';$http2_client->send($http2_request);$http2_response = $http2_client->recv();var_dump($http2_response->data); });go(function () use ($tcp_options) {// tcp$tcp_client = new SwooleCoroutineClient(SWOOLE_TCP);$tcp_client->set($tcp_options);$tcp_client->connect('127.0.0.1', 9502) ;$tcp_client->send(tcp_pack('Swoole Tcp'));var_dump(tcp_unpack($tcp_client->recv())); });
Channel是协程之间交换数据的唯一途径,Coroutine+Channel的开发组合就是著名的CSP编程模型。
在 Swoole 开发中,Channel 通常用于实现连接池或调度协程并发。
在下面的示例中,我们有一千个并发请求到 redis。通常情况下,这已经超出了Redis最大连接数设置,会抛出连接异常,但是基于Channel的连接池可以完美调度请求。我们不必担心连接过载。
RedisPool 类 {/**@var SwooleCoroutineChannel */protected $pool;/** * RedisPool 构造函数。 * @param int $size 最大连接数 */public function __construct(int $size = 100) {$this->pool = new SwooleCoroutineChannel($size);for ($i = 0; $i < $size; $i++) {$redis = new SwooleCoroutineRedis();$res = $redis->connect('127.0 .0.1', 6379);if ($res == false) { throw new RuntimeException("连接redis服务器失败。"); } else {$this->put($redis); } } }公共函数 get(): SwooleCoroutineRedis{return $this->pool->pop(); }公共函数 put(SwooleCoroutineRedis $redis) {$this->池->push($redis); }公共函数 close(): void{$this->pool->close();$this->pool = null; } }go(function () {$pool = new RedisPool();//最大并发数大于最大连接数//不过没关系,channel会帮你调度for ($c = 0; $c < 1000; $ c++) {go(function () use ($pool, $c) {for ($n = 0; $n < 100; $n++) {$redis = $pool->get();assert($redis->set("真棒-{$c}-{$n}", 'swoole'));assert($redis->get("真棒-{$c}-{$n}", 'swoole'));assert($redis->get("真棒-{$c}-{$n}") }-{$n}") === 'swoole');assert($redis->delete("真棒-{$c}-{$n}"));$pool->put($redis); } }); } });
一些 Swoole 的客户端实现了 defer 模式来实现并发,但你仍然可以通过协程和通道的组合来灵活地实现它。
go(function () {// 用户:我需要你给我带一些信息 // Channel:好的!我来负责调度。$channel = new SwooleCoroutineChannel;go(function () use ($channel) { // 协程 A:好的!我将向您展示 github 地址信息$addr_info = Co::getaddrinfo('github.com');$channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); });go(function () use ($channel) {// 协程 B:好的!我会告诉你你的代码是什么样的$mirror = Co::readFile(__FILE__);$channel->push(['B ', $mirror]); });go(function () use ($channel) {// 协程 C:好的!我将向您展示日期$channel->push(['C', date(DATE_W3C)]); });for ($i = 3; $i--;) {list($id, $data) = $channel->pop();echo "来自 {$id}:n {$data}n"; }// 用户:太棒了,我第一时间拿到了所有信息!});
$id = SwooleTimer::tick(100, function () {echo " 做某事...n"; });SwooleTimer::after(500, function () use ($id) {SwooleTimer::clear($id);echo "⏰ Donen"; });SwooleTimer::after(1000, function () use ($id) {if (!SwooleTimer::exists($id)) {echo " 好吧!n"; } });
去(函数(){$ i = 0; while(true){ Co::sleep(0.1);echo "做某事...n";if (++$i === 5) {echo "Donen";break; } }echo“好吧!n”; });
从 Swoole v4.1.0 开始,我们添加了使用一行代码将同步 PHP 网络库转换为协程库的功能。
只需调用脚本顶部的 SwooleRuntime::enableCoroutine() 方法即可。在下面的示例中,我们连接到 php-redis 并在 0.1 秒内并发读取 10k 请求:
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () { ($redis = new Redis)->connect('127.0.0.1', 6379);for ($n = 100; $n--;) {assert($redis->get('awesome') === ' swoole'); } }); } });回显“使用”。 (microtime(true) - $s) 。 's';
通过调用该方法,Swoole内核替换了ZendVM流函数指针。如果您使用基于 php_stream 的扩展,所有套接字操作都可以在运行时动态转换为由协程调度的异步 IO!
睡眠10K次,读、写、检查和删除文件10K次,使用PDO和MySQLi与数据库通信10K次,创建TCP服务器和多个客户端相互通信10K次,创建UDP服务器和多个客户端相互通信10K次相互通信 10K 次...一切都在一个过程中运行良好!
看看 Swoole 带来了什么,想象一下......
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// 我只想睡觉...for ($c = 100; $c--;) {go(function () {for ($n = 100; $n--;) {usleep(1000); } }); }// 10K文件读写for ($c = 100; $c--;) {go(function () use ($c) {$tmp_filename = "/tmp/test-{$c}.php";for ($n = 100; $n--;) {$self = file_get_contents(__FILE__);file_put_contents($tmp_filename, $self);assert(file_get_contents($tmp_filename) === $self); }取消链接($tmp_文件名); }); }// 10K pdo 和 mysqli readfor ($c = 50; $c--;) {go(function () {$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8' , 'root', 'root');$statement = $pdo->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$statement->execute();assert(count($statement->fetchAll()) > 0); } }); }for ($c = 50; $c--;) {go(function () {$mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');$statement = $ mysqli->prepare('从 `user`' 中选择 `id`);for ($n = 100; $n--;) {$statement->bind_result($id);$statement->execute();$statement->fetch();assert($id > 0); } }); }// php_stream tcp 服务器和客户端在单个进程中处理 12.8K 请求 function tcp_pack(string $data): string{return pack('n', strlen($data)) . $数据; }function tcp_length(string $head): int{return unpack('n', $head)[1]; }go(function () {$ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);$socket = stream_socket_server('tcp://0.0.0.0:分段阅读_第 9502 章$ctx);if (!$socket) {echo "$errstr ($errno)n"; } else {$i = 0;while ($conn = stream_socket_accept($socket, 1)) {stream_set_timeout($conn, 5);for ($n = 100; $n--;) {$data = fread($ conn, tcp_length(fread($conn, 2)));assert($data === "你好 Swoole 服务器#{$n}!");fwrite($conn, <span class=