مقبس عميل 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
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.