مقبس عميل TCP
إنشاء مقبس يستخدم TCP (Transmission Control Protocol)
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
تأكّد من أنّ المقبس (socket) أُنشئ بنجاح، تُستخدم الدالة onSocketFailure لمعالجة أخطاء المقبس، مثال:
if(!is_resource($socket)) onSocketFailure("Failed to create socket");
اتصال المقبس بعنوان محدد
يفشل السطر الثاني بأمان إذا فشل الاتصال:
socket_connect($socket, "chat.stackoverflow.com", 6667) or onSocketFailure("Failed to connect to chat.stackoverflow.com:6667", $socket);
إرسال بيانات إلى الخادم
ترسل الدالة socket_write البايتات عبر مقبس، تُمثَّل مصفوفة البايت في PHP بسلسلة نصية والتي هي غير حساسة للترميز بشكلٍ طبيعي.
socket_write($socket, "NICK Alice\r\nUSER alice 0 * :Alice\r\n");
إرسال بيانات من الخادم
تستقبل الشيفرة التالية بعض البيانات من الخادم باستخدام الدالة socket_read، إنّ تمرير قيمة المعامل الثالث على أنّها PHP_NORMAL_READ يقرأ حتى البايت \r/\n ويُضمَّن في القيمة المُعادة، أما تمريرها على أنّها PHP_BINARY_READ يقرأ الكمية المطلوبة من بيانات المجرى.
تُعيد الدالة socket_read القيمة false مباشرةً إذا اُستدعيت الدالة socket_set_nonblock قبلها واُستخدمَت القيمة PHP_BINARY_READ، وإلا يُعطَّل التابع حتى تُستقبَل بيانات كافية (الوصول إلى الطول المحدد في المعامل الثاني أو الوصول إلى نهاية السطر) أو يُغلَق المقبس.
يقرأ المثال التالي بيانات من خادم IRC (Internet Relay Chat) بشكلٍ افتراضي:
while(true) { // قراءة سطر من المقبس $line = socket_read($socket, 1024, PHP_NORMAL_READ); if(substr($line, -1) === "\r") { // (1) socket_read($socket, 1, PHP_BINARY_READ); } $message = parseLine($line); if($message->type === "QUIT") break; }
يُقرأ في الموضع (1) بايت واحد من المقبس أو يتم تجاوزه، نفرض أنّ البايت التالي في المجرى يجب أن يكون \n ويعدّ هذا ممارسة سيئة ويجعل السكربت ضعيفًا ومعرّضًا لقيم غير متوقعة.
إغلاق المقبس
يؤدي إغلاق المقبس إلى تحريره وتحرير الموارد المرتبطة به.
socket_close($socket);
مقبس خادم TCP
إنشاء المقبس
إنشاء مقبس يستخدم TCP نفس طريقة إنشاء مقبس عميل.
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
ربط المقبس
يكون اتصال الربط من شبكة ما (المعامل 2) إلى منفذ معين للمقبس (المعامل 3)، المعامل الثاني عادةً هو "0.0.0.0" والذي يقبل الاتصال من كل الشبكات. أحد أشهر الأسباب التي تؤدي إلى حدوث خطأ نتيجة التابع socket_bind هو أن يكون العنوان المحدد مقيد مسبقًا بعملية أخرى. تُنهى عادةً العمليات الأخرى يدويًا (لمنع إنهاء العمليات الحرجة دون قصد) لذا تُحرَّر المقابس.
socket_bind($socket, "0.0.0.0", 6667) or onSocketFailure("Failed to bind to 0.0.0.0:6667");
ضبط المقبس للاستماع
نستخدم التابع socket_listen لجعل المقبس يستمع إلى الاتصالات الواردة، يعبر المعامل الثاني عن عدد الاتصالات الأعظمي المسموح لها أن تكون في قائمة الانتظار قبل قبولها.
socket_listen($socket, 5);
معالجة الاتصال
خادم TCP هو خادم يعالج الاتصالات الأبناء، يُنشئ التابع socket_accept اتصال ابن جديد.
$conn = socket_accept($socket);
نقل بيانات الاتصال من التابع socket_accept هو نفسه في مقبس عميل TCP.
يمكنك استدعاء التابع socket_close($conn); مباشرةً عندما تريد إغلاق الاتصال، ولا يؤثر هذا على مقبس خادم TCP الأصلي.
إغلاق الخادم
يجب استدعاء التابع socket_close($socket); عند الانتهاء من استخدام الخادم، مما سيؤدي إلى تحرير عنوان TCP وبالتالي يُسمح لعمليات أخرى بالربط معه.
مقبس خادم UDP
خادم UDP (user datagram protocol) لا يعتمد على المجرى بل على الرزمة (packet-based) على عكس TCP، مثلًا عميل يرسل بيانات في وحدات تدعى رزم (packets) إلى الخادم ويعرف العميل العملاء من خلال عناوينهم، لا يوجد دالة مضمَّنة تربط الرزم المختلفة المُرسلة من نفس العميل (على عكس TCP حيث تُعالج البيانات المُرسلة من نفس العميل بمورد محدد ينشأه التابع socket_accept)، يمكننا التفكير أنّه عند كل وصول لرزمة UDP فإنّ اتصال TCP جديد يُقبَل ويُغلق.
إنشاء مقبس خادم UDP
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
ربط المقبس إلى عنوان
نفس المعاملات الموجودة لخادم TCP.
socket_bind($socket, "0.0.0.0", 9000) or onSocketFailure("Failed to bind to 0.0.0.0:9000",$socket);
إرسال رزمة
ترسل الشيفرة التالية المتغير $data في رزمة UDP إلى $address:$port.
socket_sendto($socket, $data, strlen($data), 0, $address, $port);
استقبال رزمة
تحاول الشيفرة التالية إدارة رزم UDP بطريقة تعتمد على فهرسة العميل.
$clients = []; while (true){ socket_recvfrom($socket, $buffer, 32768, 0, $ip, $port) === true or onSocketFailure("Failed to receive packet", $socket); $address = "$ip:$port"; if (!isset($clients[$address])) $clients[$address] = new Client(); $clients[$address]->handlePacket($buffer); }
إغلاق الخادم
يمكن استخدام التابع socket_close على مورد مقبس خادم UDP. سيحرر هذا عنوان UDP مما يسمح بربط العمليات الأخرى إلى هذا العنوان.
معالجة أخطاء المقبس
نستخدم التابع socket_last_error للحصول على رقم معرِّف الخطأ الأخير من إضافة المقابس، ونستخدم التابع socket_strerror لتحويل هذا الرقم إلى سلسلة نصيّة قابلة للقراءة من قِبل الإنسان.
function onSocketFailure(string $message, $socket = null) { if(is_resource($socket)) { $message .= ": " . socket_strerror(socket_last_error($socket)); } die($message); }
مقابس الويب (Webscockets)
ينفّذ استخدام إضافة المقبس (socket) واجهة منخفضة المستوى لدوال اتصال المقبس بالاعتماد على مقابس BSD (Berkeley Software Distribution) الشائعة، مما يوفر إمكانية العمل كخادم مقبس وعميل.
خادم TCP/IP بسيط
يمكنك أن تجد هنا مثالًا بسيطًا يعتمد على توثيق PHP الرسمي.
أنشئ سكربت مقبس ويب يستمع إلى المنفذ 5000 باستخدام putty والطرفية لتنفيذ الأمر telnet 127.0.0.1 5000 (المضيف المحلي)، يرد هذا السكربت بالرسالة التي أرسلتها (كتعقب عكسي):
<?php // تعطيل المهلة set_time_limit(0); // تعطيل التخزين المؤقت للخرج ob_implicit_flush(); // الإعدادات $address = '127.0.0.1'; $port = 5000; // (1) if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) { echo "Couldn't create socket".socket_strerror(socket_last_error())."\n"; } // (2) if (socket_bind($socket, $address, $port) === false) { echo "Bind Error ".socket_strerror(socket_last_error($sock)) ."\n"; } if (socket_listen($socket, 5) === false) { echo "Listen Failed ".socket_strerror(socket_last_error($socket)) . "\n"; } do { if (($msgsock = socket_accept($socket)) === false) { echo "Error: socket_accept: " . socket_strerror(socket_last_error($socket)) . "\n"; break; } /* إرسال رسالة ترحيب */ $msg = "\nPHP Websocket \n"; // الاستماع إلى دخل المستخدم do { if (false === ($buf = socket_read($msgsock, 2048, PHP_NORMAL_READ))) { echo "socket read error: ".socket_strerror(socket_last_error($msgsock)) . "\n"; break 2; } if (!$buf = trim($buf)) { continue; } // الرد على المستخدم برسالته $talkback = "PHP: You said '$buf'.\n"; socket_write($msgsock, $talkback, strlen($talkback)); // طباعة الرسالة على الطرفية echo "$buf\n"; } while (true); socket_close($msgsock); } while (true); socket_close($socket); ?>
في الموضع (1) لدينا الدالة socket_create لها الشكل العام ( int $domain , int $type , int $protocol ):
-
يمكن أن يكون المتغير
$domainهوAF_INETأوAF_INET6من أجلIPV6أوAF_UNIXمن أجل بروتوكول الاتصال المحلي. -
يمكن أن يكون المتغير
$protocolإماSOL_TCPأوSOL_UDP(TCP/UDP) تعيد هذه الدالة القيمةtrueفي حالة النجاح.
في الموضع (2) نستخدم الدالة socket_bind التي لها الشكل العام socket_bind ( resource $socket , string $address [, int $port = 0 ] )، تربط هذه الدالة المقبس ليستمع إلى عنوان ومنفذ محددين.
استيثاق HTTP
سنكتب سكربت استيثاق ترويسة HTTP بسيط، ولاحظ أنّه يجب وضع هذه الشيفرة في ترويسة الصفحة وإلا لن يعمل:
<?php if (!isset($_SERVER['PHP_AUTH_USER'])) { header('WWW-Authenticate: Basic realm="My Realm"'); header('HTTP/1.0 401 Unauthorized'); echo 'Text to send if user hits Cancel button'; exit; } echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>"; // حفظ المعلومات $user = $_SERVER['PHP_AUTH_USER']; echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>"; // حفظ كلمة المرور (يمكن إضافة التشفير اختياريًا) $pass = $_SERVER['PHP_AUTH_PW']; //Save the password(optionally add encryption)! ?> // صفحة html
ترجمة -وبتصرف- للفصول [ WebSockets - HTTP Authentication - Sockets] من كتاب PHP Notes for Professionals book
اقرأ أيضًا
- المقال التالي: إضافة PHP MySQLi ونظام إدارة قواعد البيانات SQLite3
- المقال السابق: معالجة بيانات طلبيات HTTP والتعامل مع أخطاء رفع الملفات في PHP

أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.