اذهب إلى المحتوى

المقابس (sockets) في PHP


سارة محمد2

مقبس عميل 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

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

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

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...