WebSocket على PHP: من النظرية إلى حلول الإنتاج
\n\nأحدث بروتوكول WebSocket ثورة في تطوير الويب، مما سمح بإنشاء تطبيقات الوقت الفعلي الكاملة التي تعمل عبر اتصال TCP واحد. على عكس HTTP التقليدي، حيث يبدأ العميل الطلبات باستمرار، ينشئ WebSocket قناة اتصال ثنائية الاتجاه، يمكن للخادم من خلالها إرسال البيانات في أي لحظة. هذا أمر بالغ الأهمية للدردشات، منصات التداول، الألعاب عبر الإنترنت، وأنظمة المراقبة.
\n\nيعتقد العديد من المطورين خطأً أن PHP غير مناسب لمهام الوقت الفعلي بسبب طبيعته المتزامنة. ومع ذلك، فإن الأدوات الحديثة مثل Ratchet و ReactPHP تغير هذا المفهوم تمامًا. في هذه المقالة، سنستعرض التقنيات المتقدمة لإنشاء تطبيقات WebSocket قابلة للتوسع على PHP.
هندسة خادم WebSocket على PHP
\n\nاختيار مجموعة التقنيات الصحيحة
\n\nبالنسبة لحلول الإنتاج، لا تصلح المكتبات البسيطة التي تعمل عبر socket_create. هناك حاجة إلى نهج يعتمد على الأحداث (event-driven). مجموعة التقنيات الموصى بها:
- \n
- ReactPHP — حلقة الأحداث (event-loop) للإدخال/الإخراج غير المتزامن \n
- Ratchet — مكون WebSocket فوق ReactPHP \n
- Redis — pub/sub للتواصل بين العمليات \n
- Supervisor — مدير العمليات لضمان وقت التشغيل (uptime) \n
خادم أساسي باستخدام Ratchet
\n\nلننظر في إنشاء خادم WebSocket كامل قادر على معالجة آلاف الاتصالات:
\n\nuse Ratchet\\MessageComponentInterface;\nuse Ratchet\\ConnectionInterface;\nuse React\\EventLoop\\Factory;\nuse React\\Socket\\Server;\nuse Ratchet\\Server\\IoServer;\nuse Ratchet\\Http\\HttpServer;\nuse Ratchet\\WebSocket\\WsServer;\n\nclass ChatServer implements MessageComponentInterface {\n protected $clients;\n protected $redis;\n\n public function __construct() {\n $this->clients = new \\SplObjectStorage;\n $this->redis = new \\Predis\\Client([\n 'scheme' => 'tcp',\n 'host' => '127.0.0.1',\n 'port' => 6379,\n ]);\n // الاشتراك في قناة Redis لمزامنة بين الخوادم\n $this->redis->subscribe(['chat_messages'], function($redis, $channel, $message) {\n $this->broadcast(json_decode($message, true));\n });\n }\n\n public function onOpen(ConnectionInterface $conn) {\n $this->clients->attach($conn);\n echo "اتصال جديد: {$conn->resourceId}\\n";\n }\n\n public function onMessage(ConnectionInterface $from, $msg) {\n $data = json_decode($msg, true);\n \n // التحقق من صحة الرسالة\n if ($this->validateMessage($data)) {\n // النشر في Redis للخوادم الأخرى\n $this->redis->publish('chat_messages', json_encode($data));\n // الإرسال للعملاء المحليين\n $this->broadcast($data, $from);\n }\n }\n\n public function onClose(ConnectionInterface $conn) {\n $this->clients->detach($conn);\n echo "تم إغلاق الاتصال: {$conn->resourceId}\\n";\n }\n\n public function onError(ConnectionInterface $conn, \\Exception $e) {\n echo "خطأ: {$e->getMessage()}\\n";\n $conn->close();\n }\n\n protected function broadcast(array $data, ?ConnectionInterface $exclude = null) {\n foreach ($this->clients as $client) {\n if ($exclude === null || $client !== $exclude) {\n $client->send(json_encode($data));\n }\n }\n }\n\n protected function validateMessage(array $data): bool {\n return isset($data['type'], $data['payload']) && \n in_array($data['type'], ['message', 'typing', 'status']);\n }\n}\n\n// تشغيل الخادم\n$loop = Factory::create();\n$webSock = new Server('0.0.0.0:8080', $loop);\n$webServer = n