Swoole عبارة عن مكتبة متزامنة تعتمد على الأحداث وغير متزامنة ومبنية على coroutine وتتميز بأداء عالٍ لـ PHP.
قم بتشغيل برنامج Swoole بواسطة Docker
تشغيل عامل ميناء --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', الوظيفة ($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'], وظيفة ($instance, $channelName, $message) {echo 'New redis message: '.$channelName, "==>", $message, PHP_EOL; }); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);$count = 0;while(true) {sleep(2);$redis- >نشر('test','hello, world, count='.$count++); } }); });
يربط Swoole وظيفة الحظر io الخاصة بـ PHP في الطبقة السفلية ويحولها تلقائيًا إلى وظيفة غير محظورة، بحيث يمكن استدعاء هذه الوظائف بشكل متزامن في coroutines.
ext-curl (دعم Symfony و guzzle)
تحويلة redis
تحويلة 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
مآخذ تحويلة
تحويلة الصابون
Sleep/usleep/time_sleep_until
proc_open
gethostbyname/shell_exec/exec
fread/fopen/fsockopen/fwrite/flock
مساعد IDE وواجهة برمجة التطبيقات: 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 أو الإصدارات الأحدث coroutine المدمج بتوفر عالٍ، ويمكنك استخدام تعليمات برمجية متزامنة بالكامل لتنفيذ الأداء غير المتزامن. كود PHP دون أي كلمات رئيسية إضافية، جدولة coroutine التلقائية الأساسية.
يمكن للمطورين فهم coroutines كخيوط خفيفة الوزن للغاية، ويمكنك بسهولة إنشاء الآلاف من coroutines في عملية واحدة.
طلبات التزامن 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','user' => 'root','password' => 'root','database' => 'test']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0 ); } }); } });صدى 'استخدام' . (ميكروتايم(صحيح) - $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', وظيفة ( طلب SwooleHttpRequest $، استجابة SwooleHttpResponse $) {$response->end('Hello' . $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', الوظيفة (SwooleServer $ الخادم، int $fd، int $reactor_id، سلسلة $data) {$server->send($fd, tcp_pack('Hello ' . tcp_unpack($data))); });$server->start();
سواء كنت تقوم بالاستعلام عن نظام أسماء النطاقات (DNS) أو إرسال طلبات أو تلقي استجابات، تتم جدولة كل ذلك بواسطة 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())); });
القناة هي الطريقة الوحيدة لتبادل البيانات بين coroutines، ومزيج التطوير لـ Coroutine + Channel هو نموذج برمجة CSP الشهير.
في تطوير Swoole، يتم استخدام القناة عادةً لتنفيذ تجمع الاتصال أو جدولة coroutine المتزامنة.
في المثال التالي، لدينا ألف طلب متزامن لـ redis. عادةً ما يتجاوز هذا الحد الأقصى لعدد اتصالات Redis وسيؤدي إلى استثناء اتصال، ولكن يمكن لتجمع الاتصال المستند إلى القناة جدولة الطلبات بشكل مثالي. لا داعي للقلق بشأن التحميل الزائد على الاتصال.
فئة RedisPool {/**@var SwooleCoroutineChannel */protected $pool;/** * مُنشئ RedisPool. * @param int $size max Connections */ 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(); }وضع الوظيفة العامة (SwooleCoroutineRedis $redis) {$this->pool->push($redis); } إغلاق الوظيفة العامة (): void{$this->pool->إغلاق();$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("awesome-{$c}-{$n}", 'swoole'));assert($redis->get("awesome-{$c" }-{$n}") === 'swoole');assert($redis->delete("awesome-{$c}-{$n}"));$pool->put($redis); } }); } });
يقوم بعض عملاء Swoole بتنفيذ وضع التأجيل للتزامن، ولكن لا يزال بإمكانك تنفيذه بمرونة من خلال مجموعة من القنوات والقنوات.
go(function () {// المستخدم: أريدك أن تحضر لي بعض المعلومات.// القناة: حسنًا! سأكون مسؤولاً عن الجدولة.$channel = new SwooleCoroutineChannel;go(function () use ($channel) { // كوروتين أ: حسنًا! سأعرض لك معلومات عنوان github$addr_info = Co::getaddrinfo('github.com');$channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); });go(function () use ($channel) {// Coroutine B: حسنًا! سأوضح لك الشكل الذي تبدو عليه شفرتك$mirror = Co::readFile(__FILE__);$channel->push(['B) '، $ مرآة])؛ });go(function () use ($channel) {// Coroutine 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 " All right!n"; } });
اذهب (وظيفة () {$i = 0؛بينما (صحيح) { Co::sleep(0.1);echo " افعل شيئًا...n";if (++$i === 5) {echo " Donen";break; } }echo " حسنًا! ن"; });
اعتبارًا من إصدار Swoole v4.1.0، أضفنا القدرة على تحويل مكتبات شبكة PHP المتزامنة إلى مكتبات روتينية مشتركة باستخدام سطر واحد من التعليمات البرمجية.
ما عليك سوى استدعاء طريقة SwooleRuntime::enableCoroutine() في الجزء العلوي من البرنامج النصي الخاص بك. في العينة أدناه، نتصل بـ php-redis ونقرأ بشكل متزامن 10 آلاف طلب في 0.1 ثانية:
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') === ' سوول)؛ } }); } });صدى 'استخدام' . (ميكروتايم(صحيح) - $s) . 's'؛
من خلال استدعاء هذه الطريقة، يستبدل Swoole kernel مؤشرات وظيفة دفق ZendVM. إذا كنت تستخدم امتدادات تعتمد على php_stream، فيمكن تحويل جميع عمليات المقبس ديناميكيًا إلى عمليات إدخال/إخراج غير متزامنة مجدولة بواسطة coroutine في وقت التشغيل!
النوم 10 آلاف مرة، وقراءة الملفات وكتابتها والتحقق منها وحذفها 10 آلاف مرة، واستخدام PDO وMySQLi للتواصل مع قاعدة البيانات 10 آلاف مرة، وإنشاء خادم TCP وعملاء متعددين للتواصل مع بعضهم البعض 10 آلاف مرة، وإنشاء خادم UDP وعملاء متعددين تواصلوا مع بعضكم البعض 10 آلاف مرة... كل شيء يعمل بشكل جيد في عملية واحدة!
فقط انظر إلى ما يجلبه Swoole، فقط تخيل...
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// أريد فقط أن أنام... for ($c = 100; $c--;) {go(function () {لـ ($n = 100؛ $n--;) {usleep(1000); } }); }// ملف 10 كيلو للقراءة والكتابة 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); }unlink($tmp_filename); }); }// 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('SELECT `id` FROM `user`');for ($n = 100; $n--;) {$statement->bind_result($id);$statement->execute();$statement->fetch();assert($id > 0); } }); }// خادم وعميل php_stream tcp مع 12.8 ألف طلب في عملية واحدة وظيفة 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=