كيفية تخصيص البروتوكول
في الواقع، فإن إنشاء بروتوكول خاص بك ليس بالأمر الصعب. عمومًا، تحتوي البروتوكولات البسيطة على جزئين:
- معرّف يحدد حدود البيانات
- تعريف تنسيق البيانات
مثال
تعريف البروتوكول
نفترض هنا أن معرّف حدود البيانات هو حرف الانتقال إلى السطر "\n" (مع ملاحظة أنه لا يمكن أن يحتوي البيانات المطلوبة نفسها على حرف الانتقال إلى السطر)، وأن تنسيق البيانات هو Json، على سبيل المثال، طلب يتوافق مع هذه القواعد يكون كالتالي:
{"type":"message","content":"hello"}
لاحظ أن هناك حرف انتقال إلى السطر في نهاية بيانات الطلب أعلاه (في PHP يتم تمثيله بسلسلة مزدوجة الاقتباس "\n")، والذي يمثل نهاية الطلب.
خطوات التنفيذ
إذا كنت ترغب في تنفيذ البروتوكول المذكور أعلاه في Workerman، نفترض أن اسم البروتوكول هو JsonNL، وأن المشروع هو MyApp، ستحتاج إلى اتباع الخطوات التالية:
-
ضع ملف البروتوكول في مجلد بروتوكولات المشروع، على سبيل المثال، ملف MyApp/Protocols/JsonNL.php.
-
نفّذ فئة JsonNL باستخدام
namespace Protocols;كمساحة اسم، يجب تنفيذ ثلاثة طرق ثابتة هي input وencode وdecode.
ملحوظة: Workerman سيتصل تلقائيًا بهذه الطرق الثلاثة الثابتة لتحسين عملية تقسيم الحزم، وفك الحزم، وتغليف الحزم. انتبه للتفاصيل في مراحل التنفيذ أدناه.
عملية التفاعل بين Workerman وفئة البروتوكول
- لنفترض أن العميل يرسل حزمة بيانات إلى الخادم، بعد استلام البيانات (ربما تكون جزءًا منها) سيقوم الخادم فورًا باستدعاء طريقة البروتوكول
input، للتحقق من طول هذه الحزمة، ستقوم طريقةinputبإرجاع قيمة الطول$lengthإلى إطار عمل Workerman. - بعد أن يحصل إطار العمل على قيمة
$length، سيتحقق مما إذا كانت بيانات المخزن المؤقت قد استقبلت بالفعل بيانات بطول$length، إذا لم يكن الأمر كذلك، فسيستمر في انتظار البيانات حتى يصبح طول البيانات في المخزن المؤقت لا يقل عن$length. - بمجرد أن يكون طول البيانات في المخزن المؤقت كافيًا، سيقوم Workerman باقتطاع بيانات بطول
$lengthمن المخزن المؤقت (أي تقسيم الحزمة)، واستدعاء طريقة البروتوكولdecodeلفك الحزمة، وتكون البيانات المفككة هي$data. - بعد فك الحزمة، سيقوم Workerman بتمرير البيانات
$dataإلى التشغيلonMessage($connection, $data)ليتم استخدامه في التعامل مع البيانات، ويمكن للعملية استخدام متغير$dataللحصول على البيانات الكاملة التي تم فكها المرسلة من العميل. - عندما تحتاج العملية في
onMessageإلى إرسال بيانات إلى العميل عن طريق استدعاء طريقة$connection->send($buffer), سيقوم Workerman تلقائيًا باستخدام طريقة البروتوكولencodeلتغليف$bufferقبل إرساله إلى العميل.
التنفيذ المحدد
تنفيذ MyApp/Protocols/JsonNL.php
namespace Protocols;
class JsonNL
{
/**
* تحقق من اكتمال الحزمة
* إذا كان بالإمكان قياس طول الحزمة، فيتم إرجاع طول الحزمة في المخزن المؤقت، وإلا فسيتم إرجاع 0 لمواصلة انتظار البيانات
* إذا كان هناك مشكلة في البروتوكول، يمكن إرجاع -1، مما سيؤدي إلى قطع الاتصال مع العميل الحالي
* @param string $buffer
* @return int
*/
public static function input($buffer)
{
// احصل على موضع حرف الانتقال إلى السطر "\n"
$pos = strpos($buffer, "\n");
// لا يوجد حرف انتقال إلى السطر، ولا يمكن معرفة طول الحزمة، إرجاع 0 لمواصلة الانتظار
if($pos === false)
{
return 0;
}
// يوجد حرف انتقال إلى السطر، إرجاع طول الحزمة الحالية (بما في ذلك حرف الانتقال إلى السطر)
return $pos+1;
}
/**
* تغليف عندما يتم إرسال البيانات إلى العميل سيتم الاتصال به تلقائيًا
* @param string $buffer
* @return string
*/
public static function encode($buffer)
{
// تسلسل json، مع إضافة حرف الانتقال إلى السطر كعلامة لإنهاء الطلب
return json_encode($buffer)."\n";
}
/**
* فك التغليف عندما تتساوى عدد بايت البيانات المستقبلة مع القيمة المرجعة من input (قيمة أكبر من 0) يتم الاتصال به تلقائيًا
* ويتم تمريرها إلى معلمة $data في دالة onMessage
* @param string $buffer
* @return string
*/
public static function decode($buffer)
{
// إزالة حرف الانتقال إلى السطر، واستعادة البيانات إلى مصفوفة
return json_decode(trim($buffer), true);
}
}
بهذا، يتم الانتهاء من تنفيذ بروتوكول JsonNL، ويمكن استخدامه في مشروع MyApp، كما في المثال أدناه.
الملف: MyApp\start.php
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
require_once __DIR__ . '/vendor/autoload.php';
$json_worker = new Worker('JsonNL://0.0.0.0:1234');
$json_worker->onMessage = function(TcpConnection $connection, $data) {
// $data هو البيانات المرسلة من العميل، وقد تمت معالجتها عبر JsonNL::decode
echo $data;
// ستقوم $connection->send بإرسال البيانات بعد تلقائيًا تغليفها بواسطة JsonNL::encode، ثم تُرسل إلى العميل
$connection->send(array('code'=>0, 'msg'=>'ok'));
};
Worker::runAll();
...
تنبيه
سيحاول Workerman تحميل البروتوكولات الموجودة في مساحة اسمProtocols، على سبيل المثال،new Worker('JsonNL://0.0.0.0:1234')سيحاول تحميل البروتوكولProtocols\JsonNL.
إذا واجهت خطأClass 'Protocols\JsonNL' not found، يرجى مراجعة التحميل التلقائي لتنفيذ التحميل التلقائي.
شرح واجهة البروتوكول
في Workerman، يجب أن تقوم فئات البروتوكول التي تم تطويرها بتنفيذ ثلاثة طرق ثابتة: input وencode وdecode، يرجى مراجعة شرح واجهة البروتوكول في Workerman/Protocols/ProtocolInterface.php، مع التعريف التالي:
namespace Workerman\Protocols;
use \Workerman\Connection\ConnectionInterface;
/**
* واجهة البروتوكول
* @author walkor <walkor@workerman.net>
*/
interface ProtocolInterface
{
/**
* تستخدم لتقسيم الحزم في recv_buffer المستلمة
*
* إذا كان بالإمكان قياس طول حزمة الطلب في $recv_buffer يجب إرجاع الطول الكامل للحزمة
* خلاف ذلك، يُرجع 0، مما يعني الحاجة إلى المزيد من البيانات للحصول على طول حزمة الطلب الحالية
* إذا تمت إعادة -1، فهذا يعني طلب خاطئ، وستقطع الاتصال
*
* @param ConnectionInterface $connection
* @param string $recv_buffer
* @return int
*/
public static function input($recv_buffer, ConnectionInterface $connection);
/**
* تستخدم لفك التغليف
*
* إذا كانت قيمة return من input أكبر من 0، وWorkerman قد استلمت بيانات كافية، سيتم استدعاء decode تلقائيًا
* ثم تسجل العملية في onMessage، مستعرضة البيانات التي تم فك تشفيرها إلى المعامل الثاني في onMessage
* بعبارة أخرى، عند استلام طلب العميل الكامل، سيتم استدعاء decode تلقائيًا، ولا حاجة لاستدعاء كود العمل يدوياً
* @param ConnectionInterface $connection
* @param string $recv_buffer
*/
public static function decode($recv_buffer, ConnectionInterface $connection);
/**
* تستخدم لتغليف الطلب
*
* عند الحاجة لإرسال البيانات إلى العميل عن طريق استدعاء $connection->send($data);
* سيتم تلقائيًا تغليف $data باستخدام encode، وتحويلها إلى تنسيق بيانات متوافق مع البروتوكول، ثم إرساله إلى العميل
* أي أن البيانات المرسلة إلى العميل ستغلف تلقائيًا باستخدام encode، ولا حاجة لاستدعاء كود العمل يدوياً
* @param ConnectionInterface $connection
* @param mixed $data
*/
public static function encode($data, ConnectionInterface $connection);
}
ملاحظة:
لا يتطلب Workerman بشكل صارم أن تستند فئات البروتوكول إلى ProtocolInterface، في الواقع، يكفي أن تحتوي الفئة على الطرق الثلاثة الثابتة input وencode وdecode.