Swoole est une bibliothèque de concurrence événementielle, asynchrone et basée sur des coroutines, offrant de hautes performances pour PHP.
Exécuter le programme Swoole par Docker
docker run --rm phpswoole/swoole "php --ri swoole"
Pour plus de détails sur la façon de l'utiliser, voir : Comment utiliser cette image.
https://wiki.swoole.com/
$http = new SwooleHttpServer('127.0.0.1', 9501);$http->set(['hook_flags' => SWOLE_HOOK_ALL]);$http->on('request', function ($request, $response) { $résultat = []; 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, 'L'heure locale est ' . date('n/j/Y g:je a')); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);while(true) {$redis->subscribe(['test'], function ($instance, $channelName, $message) {echo 'Nouveau message 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 accroche la fonction io bloquante de PHP à la couche inférieure et la convertit automatiquement en fonction non bloquante, afin que ces fonctions puissent être appelées simultanément dans les coroutines.
ext-curl (Supporte Symfony et Guzzle)
ext-redis
ext-mysqli
ext-pdo_mysql
ext-pdo_pgsql
ext-pdo_sqlite
ext-pdo_oracle
ext-pdo_odbc
fonctions de flux (par exemple stream_socket_client/stream_socket_server), prend en charge TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
prises externes
savon ext
dormir/usleep/time_sleep_until
proc_open
gethostbyname/shell_exec/exec
fread/fopen/fsockopen/fwrite/flock
Aide et API IDE : https://github.com/swoole/ide-helper
Twitter : https://twitter.com/phpswoole
Discorde : https://discord.swoole.dev
Article suivant : https://wiki.swoole.com/#/other/discussion
Project Awesome Swoole maintient une liste organisée de choses géniales liées à Swoole, notamment
Frameworks et bibliothèques basés sur Swoole.
Packages pour intégrer Swoole aux frameworks PHP populaires, notamment Laravel, Symfony, Slim et Yii.
Livres, vidéos et autres supports d'apprentissage sur Swoole.
Outils de débogage, de profilage et de test pour développer des applications basées sur Swoole.
Packages et bibliothèques compatibles Coroutine.
Autres projets et ressources liés à Swoole.
La couche réseau de Swoole est basée sur les événements et tire pleinement parti de l'implémentation sous-jacente d'epoll/kqueue, ce qui facilite grandement le traitement de millions de requêtes.
Swoole 4.x utilise un tout nouveau noyau de moteur et dispose désormais d'une équipe de développeurs à plein temps. Nous entrons donc dans une période sans précédent dans l'histoire de PHP qui offre une possibilité unique d'évolution rapide des performances.
Swoole 4.x ou version ultérieure prend en charge la coroutine intégrée avec une haute disponibilité et vous pouvez utiliser du code entièrement synchronisé pour implémenter des performances asynchrones. Code PHP sans aucun mot-clé supplémentaire, la planification automatique de la coroutine sous-jacente.
Les développeurs peuvent comprendre les coroutines comme des threads ultra-légers, et vous pouvez facilement créer des milliers de coroutines en un seul processus.
Les requêtes simultanées 10 000 pour lire les données de MySQL ne prennent que 0,2 s !
$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () {$mysql = new SwooleCoroutineMySQL;$mysql->connect(['host' => '127.0.0.1','user' => 'root','password' => 'root','database' => 'test']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0 ); } }); } });echo 'utiliser ' . (microtime(true) - $s) . ' s';
Vous pouvez créer plusieurs services sur une seule boucle d'événements : TCP, HTTP, Websocket et HTTP2, et gérer facilement des milliers de requêtes.
function tcp_pack(string $data): string{return pack('N', strlen($data)) . $données ; }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('Bonjour ' . $request->rawcontent()); });// websocket$server->on('message', function (SwooleWebSocketServer $server, SwooleWebSocketFrame $frame) {$server->push($frame->fd, 'Bonjour ' . $frame->data); });// tcp$tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP);$tcp_server->set($tcp_options);$tcp_server->on('receive', fonction (SwooleServer $ serveur, int $fd, int $reactor_id, chaîne $data) {$server->send($fd, tcp_pack('Bonjour ' . tcp_unpack($data))); });$serveur->start();
Que vous effectuiez une requête DNS, envoyiez des requêtes ou receviez des réponses, tout cela est automatiquement planifié par coroutine.
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())); });
Le canal est le seul moyen d'échanger des données entre coroutines, la combinaison de développement Coroutine + Channel est le célèbre modèle de programmation CSP.
Dans le développement Swoole, Channel est généralement utilisé pour implémenter un pool de connexions ou planifier une coroutine simultanée.
Dans l’exemple suivant, nous avons mille requêtes simultanées vers Redis. Normalement, cela a dépassé le nombre maximum de connexions Redis et lèvera une exception de connexion, mais le pool de connexions basé sur Channel peut parfaitement planifier les requêtes. Nous n'avons pas à nous soucier de la surcharge de connexion.
classe RedisPool {/**@var SwooleCoroutineChannel */protected $pool;/** * Constructeur RedisPool. * @param int $size max connexions */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("échec de la connexion au serveur Redis."); } else {$this->put($redis); } } }fonction publique get() : SwooleCoroutineRedis{return $this->pool->pop(); }fonction publique put(SwooleCoroutineRedis $redis) {$this->pool->push($redis); }fonction publique close() : void{$this->pool->close();$this->pool = null; } }go(function () {$pool = new RedisPool();// le nombre maximum de concurrence est supérieur au nombre maximum de connexions// mais ce n'est pas un problème, le canal vous aidera à planifier ($c = 0; $c < 1000; $ c++) {go(function () use ($pool, $c) {for ($n = 0 ; $n < 100 ; $n++) {$redis = $pool->get();assert($redis->set("génial-{$c}-{$n}", 'swoole'));assert($redis->get("génial-{$c }-{$n}") === 'swoole');assert($redis->delete("awesome-{$c}-{$n}"));$pool->put($redis); } }); } });
Certains clients de Swoole implémentent le mode différé pour la concurrence, mais vous pouvez toujours l'implémenter de manière flexible avec une combinaison de coroutines et de canaux.
go(function () {// Utilisateur : J'ai besoin que vous me rapportiez des informations.// Channel : OK ! Je serai responsable de la planification.$channel = new SwooleCoroutineChannel;go(function () use ($channel) { // Coroutine A : Ok ! Je vais vous montrer l'adresse github info$addr_info = Co::getaddrinfo('github.com');$channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); });go(function () use ($channel) {// Coroutine B : Ok ! Je vais vous montrer à quoi ressemble votre code$mirror = Co::readFile(__FILE__);$channel->push(['B ', $miroir]); });go(function () use ($channel) {// Coroutine C : Ok ! Je vais vous montrer la date$channel->push(['C', date(DATE_W3C)]); });for ($i = 3; $i--;) {list($id, $data) = $channel->pop();echo "De {$id}:n {$data}n"; }// Utilisateur : Incroyable, j'ai obtenu toutes les informations le plus tôt possible !});
$id = SwooleTimer::tick(100, function () {echo " Faites quelque chose...n"; });SwooleTimer::after(500, function () use ($id) {SwooleTimer::clear($id);echo "⏰ Donen"; });SwooleTimer::after(1000, function () use ($id) {if (!SwooleTimer::exists($id)) {echo " Très bien !n"; } });
go(function () {$i = 0; while (true) { Co::sleep(0.1);echo " Faire quelque chose...n";if (++$i === 5) {echo " Donen";break; } }echo " Très bien ! n" ; });
Depuis Swoole v4.1.0, nous avons ajouté la possibilité de transformer des bibliothèques réseau PHP synchrones en bibliothèques co-routines en utilisant une seule ligne de code.
Appelez simplement la méthode SwooleRuntime::enableCoroutine() en haut de votre script. Dans l'exemple ci-dessous, nous nous connectons à php-redis et lisons simultanément 10 000 requêtes en 0,1 s :
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') === ' laine'); } }); } });echo 'utiliser ' . (microtime(true) - $s) . ' s';
En appelant cette méthode, le noyau Swoole remplace les pointeurs de fonction de flux ZendVM. Si vous utilisez des extensions basées sur php_stream, toutes les opérations de socket peuvent être converties dynamiquement en E/S asynchrones planifiées par coroutine au moment de l'exécution !
Dormez 10 000 fois, lisez, écrivez, vérifiez et supprimez des fichiers 10 000 fois, utilisez PDO et MySQLi pour communiquer avec la base de données 10 000 fois, créez un serveur TCP et plusieurs clients pour communiquer entre eux 10 000 fois, créez un serveur UDP et plusieurs clients pour communiquez entre vous 10 000 fois... Tout fonctionne bien en un seul processus !
Voyez simplement ce que le Swoole apporte, imaginez...
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// je veux juste dormir...for ($c = 100; $c--;) {go(function () {pour ($n = 100 ; $n-- ;) {usleep(1000); } }); }// Fichier de 10 000 lectures et écritures pour ($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); }); }// 10 000 lectures de pdo et 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`');pour ($n = 100; $n--;) {$statement->bind_result($id);$statement->execute();$statement->fetch();assert($id > 0); } }); }// Serveur et client TCP php_stream avec 12,8 000 requêtes dans un seul processus function tcp_pack(string $data): string{return pack('n', strlen($data)) . $données ; }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 | $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 === "Bonjour le serveur Swoole #{$n}!");fwrite($conn, <span class=