Swoole — это управляемая событиями асинхронная библиотека параллелизма на основе сопрограмм с высокой производительностью для PHP.
Запустите программу Swoole от Docker
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, 'Местное время ' . date('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- >publish('test','hello, world, count='.$count++); } }); });
Swoole перехватывает блокирующую функцию io PHP на нижнем уровне и автоматически преобразует ее в неблокирующую функцию, чтобы эти функции можно было вызывать одновременно в сопрограммах.
ext-curl (поддержка Symfony и Guzzle)
экст-Редис
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
внешние розетки
экст-мыло
спать/успать/time_sleep_until
proc_open
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,2 секунды!
$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () {$mysql = new SwooleCoroutineMySQL;$mysql->connect(['host' => '127.0.0.1','пользователь' => 'root','пароль' => 'root','database' => 'test']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0 ); } }); } });echo 'использовать' . (microtime(true) - $s) . 'с';
Вы можете создать несколько сервисов в одном цикле событий: TCP, HTTP, Websocket и HTTP2 и легко обрабатывать тысячи запросов.
функция tcp_pack(строка $data): строка {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 $запрос, SwooleHttpResponse $ответ) {$response->end('Привет' . $request->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('receive', function (SwooleServer $ сервер, int $fd, int $reactor_id, строка $data) {$server->send($fd, tcp_pack('Привет' . tcp_unpack($data))); });$server->start();
Независимо от того, запрашиваете ли вы 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 = 'Свул 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())); });
Канал — единственный способ обмена данными между сопрограммами. Комбинация разработки «Сопрограмма + Канал» — это известная модель программирования CSP.
В разработке Swoole Channel обычно используется для реализации пула соединений или планирования одновременного выполнения сопрограмм.
В следующем примере у нас есть тысяча одновременных запросов на Redis. Обычно это превышает максимальное количество подключений Redis и вызывает исключение подключения, но пул соединений на основе канала может идеально планировать запросы. Нам не нужно беспокоиться о перегрузке соединения.
класс 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."); } Еще {$this->put($redis); } } } Публичная функция get(): SwooleCoroutineRedis{return $this->pool->pop(); } Публичная функция put (SwooleCoroutineRedis $redis) {$this->pool->push($redis); } Публичная функция close(): void{$this->pool->close();$this->pool = null; } }go(function () {$pool = new RedisPool();// максимальное количество параллелизма больше, чем максимальное количество подключений // но это не проблема, канал поможет вам с планированием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->delete("awesome-{$c}-{$n}"));$pool->put($redis); } }); } });
Некоторые клиенты Swoole реализуют режим отсрочки для параллелизма, но вы все равно можете реализовать его гибко с помощью комбинации сопрограмм и каналов.
go(function () {// Пользователь: Мне нужно, чтобы вы вернули мне некоторую информацию. // Канал: ОК! Я буду отвечать за планирование. $channel = new SwooleCoroutineChannel;go(function () use ($channel) { // Сопрограмма A: Хорошо, я покажу вам адрес github info$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 ', $зеркало]); });go(function () use ($channel) {// Сопрограмма C: Хорошо, я покажу вам дату$channel->push(['C', date(DATE_W3C)]); }); for ($i = 3; $i--;) {list($id, $data) = $channel->pop();echo "From {$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"; } });
go(function () {$i = 0; while (true) { Co::sleep(0.1);echo " Сделайте что-нибудь...n";if (++$i === 5) {echo " Donen";break; } }echo "Хорошо!n"; });
Начиная с версии Swoole 4.1.0, мы добавили возможность преобразовывать синхронные сетевые библиотеки PHP в библиотеки сопрограмм с помощью одной строки кода.
Просто вызовите метод SwooleRuntime::enableCoroutine() в верхней части вашего скрипта. В примере ниже мы подключаемся к php-redis и одновременно читаем 10 тыс. запросов за 0,1 с:
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () { ($redis = новый Redis)->connect('127.0.0.1', 6379);for ($n = 100; $n--;) {assert($redis->get('awesome') === ' вздутие'); } }); } });echo 'использовать' . (microtime(true) - $s) . 'с';
Вызывая этот метод, ядро Swoole заменяет указатели на функции потока ZendVM. Если вы используете расширения на основе php_stream, все операции с сокетами могут быть динамически преобразованы в асинхронные операции ввода-вывода, запланированные сопрограммой во время выполнения!
Спать 10 тысяч раз, читать, писать, проверять и удалять файлы 10 тысяч раз, использовать PDO и MySQLi для связи с базой данных 10 тысяч раз, создавать TCP-сервер и несколько клиентов для связи друг с другом 10 тысяч раз, создавать UDP-сервер и несколько клиентов для общаемся друг с другом 10К раз... Всё работает хорошо в одном процессе!
Просто посмотрите, что приносит Swoole, только представьте...
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// я просто хочу спать...for ($c = 100; $c--;) {go(function () {for ($n = 100; $n--;) {usleep(1000); } }); }// Чтение и запись файла размером 10 КБ ($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); }unlink($tmp_filename); }); }// 10K pdo и mysqli для чтения ($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('SELECT `id` FROM `user`');for ($n = 100; $n--;) {$statement->bind_result($id);$statement->execute();$statement->fetch();assert($id > 0); } }); }// TCP-сервер и клиент php_stream с 12,8 тыс. запросов в одном процессе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',$errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $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=