تخطَّ إلى المحتوى

فك ترميز البروتوكولات المخصصة

التقطت بروتوكول مقبس (socket) خاصًا أو داخليًا لا تتعرف عليه الصيغ المدمجة، ولم يتبقَ لديك سوى كومة من البايتات؟ يتيح لك فك الترميز المخصص تعليم الأداة كيفية قراءته بنص برمجي قصير: تقطيع تدفق بايتات مستمر إلى رسائل مستقلة، وتجريد ترويسة البروتوكول، وفك الضغط عند الحاجة، وترك الباقي للتعرّف البنيوي التلقائي في الأداة.

هذه الصفحة لمن يريد كتابة مُفكِّك ترميز بنفسه. تشرح كيفية كتابة الدالة، ومتى تُستدعى، وماذا تعني القيمة المُعادة.


1. ما هو: خطاف فك ترميز لكل إطار

Section titled “1. ما هو: خطاف فك ترميز لكل إطار”

تكتب خطاف فك ترميز، وتُغذّيه الأداة ببايتات اتصال، وتطرح عليه سؤالًا مرارًا: “ابتداءً من هنا، هل يمكنك تقطيع رسالة واحدة كاملة؟” تُقطّع رسالة وتُبلّغ عن عدد البايتات التي استخدمتها؛ تُسأل مجددًا مع الباقي، وهكذا حتى يُقطَّع التدفق بالكامل.

إن سبق لك كتابة مُفكِّك بايتات تراكمي في إطار عمل شبكي، فالنموذج الذهني نفسه: بالنظر إلى تدفق بايتات مستمر، تُحدد حدود الرسائل بنفسك. اتفاقيتان أساسيتان:

  • لا تقلق بشأن الاتجاه: يمر تدفق الإرسال وتدفق الاستقبال الخاصان باتصال ما كل منهما عبر خطافك بشكل منفصل، وتُسمي الأداة تلقائيًا كل رسالة مُقطَّعة باتجاهها. يتعامل نصك البرمجي فقط مع “كيفية التقطيع.”
  • بمجرد التقطيع، لا تتعامل مع العرض: كل رسالة تدفعها تمر عبر التعرّف التلقائي مجددًا. إذا كانت تحمل protobuf أو JSON أو plist بداخلها، يُحلَّل ذلك أيضًا إلى صورة بنيوية. أنت مسؤول فقط عن التأطير، وتجريد الترويسات، وفك الضغط.

2. دالة فك الترميز: decode(buf, out)

Section titled “2. دالة فك الترميز: decode(buf, out)”

مُفكِّك الترميز بأكمله دالة واحدة:

function decode(buf, out) {
// buf: تدفق البايتات المُعلَّق حاليًا بانتظار التحليل؛ out: مُجمِّع المخرجات؛ القيمة المُعادة: عدد البايتات المُستهلَكة هذه المرة
}

ما هي المدخلات والمخرجات والقيمة المُعادة

Section titled “ما هي المدخلات والمخرجات والقيمة المُعادة”

buf (المدخل): تدفق البايتات المُعلَّق بانتظار التحليل

  • النوع: ArrayBuffer. محتوياته كل البايتات المتبقية “منذ آخر نقطة انتهيت فيها من الاستهلاك، وحتى النهاية الحالية.”
  • الخاصية buf.byteLength: كم بايتًا يتبقى الآن (قد تنمو مع كل استدعاء؛ هذه هي طريقتك للحكم على وجود ما يكفي لتقطيع رسالة).
  • ⚠️ إنه مخزن بايتات خام، وليس Uint8Array، لذا لا يمكنك فهرسة البايتات مباشرة (buf[0] تُعيد undefined). لقراءة البايتات، استخدم المساعدات المدمجة في القسم 3 (u8 / u16be / sub …)، أو غلِّف عرضًا بنفسك: new Uint8Array(buf)، new DataView(buf).

out (المخرج): مُجمِّع المخرجات

  • النوع: مصفوفة عادية.
  • الدالة out.push(bytes الرسالة): ادفع رسالة واحدة؛ يمكنك دفع عدة رسائل في استدعاء واحد.
  • يمكن أن تكون “بايتات الرسالة”: ArrayBuffer مُعادًا من مساعد مدمج (مثل sub(...)، gunzip(...))، أو سلسلة نصية. الأنواع الأخرى تُتجاهَل.

القيمة المُعادة: كم بايتًا استهلكت

  • النوع: عدد صحيح (number)، عدد البايتات التي استخدمتها من بداية buf.
  • > 0: أكلت هذا العدد من البايتات من المقدمة وقطّعت رسالة ← تتجاهل الأداة تلك البايتات وتستدعيك مجددًا مع الباقي.
  • 0 / بلا return / رقم سالب: يعني “لا توجد رسالة كاملة في المقدمة بعد” أو “لا يمكن تقطيع المزيد” ← توقف الأداة الحلقة وتعرض البايتات المتبقية خامًا.
  • تُقيَّد القيمة المُعادة تلقائيًا ضمن طول المخزن (لمنع التجاوز)، لكن يُرجى إعادة العدد الحقيقي للبايتات التي استخدمتها فعليًا.

متى تُستدعى (دورة الحياة)

Section titled “متى تُستدعى (دورة الحياة)”
  • تُجري الأداة تمريرة واحدة عبر تدفق الإرسال لاتصال ما وتمريرة واحدة عبر تدفق استقباله. في كل تمريرة، تُستدعى decode في حلقة: في كل مرة، تُسلَّم البايتات المتبقية غير المُستهلكة، تُقطِّع رسالة واحدة وتُعيد عدد البايتات المُستهلكة، وهكذا حتى تُعيد 0.
  • لا تظهر الرسالة إلا عند تأكيد استهلاكها: الرسالة التي تدفعها تظهر فقط عندما تُعيد أيضًا قيمة > 0. إذا دفعت لكن أعدت 0، تتوقف الحلقة ويُتجاهَل الدفع، لذا يجب أن يقترن دفع رسالة دائمًا بإعادة عدد البايتات التي استغرقتها.
  • بعد انتهاء الحلقة، لا تُفقَد البايتات المتبقية غير المُستهلكة؛ تُعرَض كقطعة واحدة من البيانات الخام (على سبيل المثال، النصف الثاني من رسالة غير مكتملة).

الحالة والتكرارية (idempotence)

Section titled “الحالة والتكرارية (idempotence)”
  • تحصل كل حزمة (اتجاه) على بيئة نص برمجي منفصلة وجديدة تمامًا؛ لا يختلط تدفق الإرسال بتدفق الاستقبال أبدًا.
  • للحفاظ على الحالة عبر الاستدعاءات (عداد، نوع الرسالة السابقة، وما إلى ذلك)، أعلن المتغيرات خارج decode. تستمر عبر الاستدعاءات ضمن التدفق نفسه، وتُعاد ضبطها عند تغيّر الاتجاه أو عند إعادة فك الترميز.
  • يعمل فك الترميز على بيانات مُلتقَطة بالفعل، ويمكن إعادة تشغيله مرارًا: عدّل النص البرمجي، احفظ، وانقر مجددًا لإعادة فك ترميز الاتصال نفسه بالقواعد الجديدة.
  • إذا رمى النص البرمجي استثناءً أو تجاوز تشغيل واحد المهلة الزمنية، يُعامَل كأنه “لم يستهلك شيئًا هذه المرة”، وتُعرَض البايتات المتبقية كبيانات خام، ويستمر الالتقاط دون فقدان أي بيانات.
  • بعبارة أخرى، أسوأ ما يمكن أن يفعله نص برمجي معطوب هو الفشل في فك الترميز، تاركًا لك بايتات خامًا؛ ولن يُعطِّل الاتصال. عدّل بحرية.
  • والأخطاء لا تُبلَع: تُعرَض الاستثناءات والمهل الزمنية في لوحة مخرجات التصحيح أعلى النتائج (انظر القسم 5)، موسومة بما إذا كانت من تدفق الإرسال أو الاستقبال . تتبّعها مباشرة إلى الإصلاح.

3. المساعدات المدمجة (متاحة مباشرة في النص البرمجي)

Section titled “3. المساعدات المدمجة (متاحة مباشرة في النص البرمجي)”

المهام الشائعة، قراءة البايتات، وقراءة الأعداد الصحيحة، والتحويل إلى نص، وفك الضغط، كلها مدمجة، فلا تحتاج لإعادة اختراعها:

الفئة التوقيع الوصف
أخذ شريحة فرعية sub(buf, off[, len]) قطّع من off (احذف len للذهاب إلى النهاية)، يُعيد ArrayBuffer
قراءة عدد صحيح u8(buf, off) / u16be / u16le / u32be / u32le (buf, off) قراءة أعداد صحيحة غير موقّعة بترتيب big-endian / little-endian
التحويل إلى نص hex(buf) / ascii(buf) التحويل إلى سلسلة سداسية عشرية / القراءة كنص
كشف الضغط gzipMagic(buf) هل هو gzip (تُعيد قيمة منطقية)
فك الضغط gunzip / inflate / unzstd / lz4dtx (buf) فك الضغط؛ إذا تعذّر، تُعيد المُدخل دون تغيير ودون خطأ
تصحيح log(...args) / console.log(...args) الطباعة إلى لوحة التصحيح (انظر القسم 5)

قدرات قراءة/كتابة البايتات القياسية (DataView، Uint8Array، إلخ) قابلة للاستخدام مباشرة أيضًا.


① بادئة الطول [طول 4 بايت big-endian][الحمولة]، الشكل الأكثر شيوعًا للبروتوكولات الخاصة:

function decode(buf, out) {
if (buf.byteLength < 4) return 0 // ترويسة الطول لم تصل بالكامل بعد، انتظر
const total = 4 + u32be(buf, 0) // الرسالة الكاملة = ترويسة 4 بايت + الحمولة
if (buf.byteLength < total) return 0 // الرسالة الكاملة لم تصل بعد، انتظر
out.push(sub(buf, 4, total - 4)) // جرّد الترويسة، سلّم الحمولة للتعرّف التلقائي
return total // استهلك هذه الرسالة، تابع لتقطيع التالية
}

② حسب الفاصل / حسب السطر، تقطيع الإطارات على علامة مثل سطر جديد:

function decode(buf, out) {
const i = ascii(buf).indexOf('\n')
if (i < 0) return 0 // لا يوجد سطر جديد بعد، انتظر
out.push(sub(buf, 0, i)) // ادفع هذا السطر (دون السطر الجديد)
return i + 1 // استهلك السطر الجديد معه
}

③ معالجة الكل دفعة واحدة، معاملة التدفق بأكمله كرسالة واحدة، مثلًا فك ضغط الكل:

function decode(buf, out) {
out.push(gzipMagic(buf) ? gunzip(buf) : buf) // إذا كان gzip، فك ضغطه
return buf.byteLength // استهلك كل شيء، تنتهي الحلقة فورًا
}

④ التوزيع حسب النوع، بحقل نوع في الترويسة ومعالجة مختلفة لكل نوع:

function decode(buf, out) {
if (buf.byteLength < 4) return 0
const total = 4 + u32be(buf, 0)
if (buf.byteLength < total) return 0
const type = u8(buf, 4) // البايت الأول هو نوع الرسالة
const body = sub(buf, 5, total - 5)
out.push(type === 2 ? gunzip(body) : body) // النوع 2 مضغوط
return total
}

يحتوي المحرر على قوالب جاهزة لكل هذه الأنماط؛ أدرج واحدًا، وعدّل بضعة أرقام، ويعمل.

محرر فك الترميز المخصص: اكتب نص decode(buf, out) برمجيًا؛ “إدراج قالب” يوفر هياكل جاهزة لبادئة الطول / التوقيع السحري / الفاصل / الطول الثابت / gzip / التوزيع حسب النوع، بالإضافة إلى مرجع سريع للدوال


5. كيفية استخدامه، وكيفية تصحيحه

Section titled “5. كيفية استخدامه، وكيفية تصحيحه”
  • الكتابة والحفظ: أنشئ، سمِّ، واحفظ في محرر فك الترميز؛ أدرج قالبًا للبدء، مع الدوال المدمجة في مرجع سريع قريب المتناول.
  • التطبيق: على اتصال لا يمكنك فهمه، انقر بزر الفأرة الأيمن واختر “فك الترميز كـ” ← مُفكِّك ترميزك، ويُقطَّع إرسال واستقبال الاتصال بأكمله فورًا ويُعرَض بقواعدك.
  • التصحيح وفحص المتغيرات: داخل decode، استخدم log(...) أو console.log(...) لطباعة أي قيمة: عدد البايتات، حقول النوع، hex(sub(buf, 0, 8))، وما إلى ذلك. تظهر المخرجات في لوحة “مخرجات التصحيحأعلى نتائج “فك الترميز كـ”، كل سطر موسوم بما إذا كان من تدفق الإرسال أو الاستقبال . تظهر أخطاء النص البرمجي والمهل الزمنية في اللوحة نفسها، موسومة بالاتجاه أيضًا. اطبع وحدد الموقع بهذه الطريقة؛ إنها أسرع بكثير من التعديل بشكل أعمى.

    لا يحتوي البيئة المعزولة (sandbox) على console كامل للمتصفح / Node؛ فقط console.log وما يعادلها log موصولان بهذه اللوحة. TextDecoder، وfetch، وsetTimeout، وما شابه غير متوفرة. لقراءة البايتات كنص، استخدم ascii(buf).

  • التعديل والمشاهدة الفورية: لا حاجة لإعادة الالتقاط. احفظ نصك البرمجي، انقر على “فك الترميز كـ” مجددًا، ويُعاد فك ترميز الاتصال نفسه فورًا بالقواعد الجديدة. كرر حتى يُقطِّع بالشكل الذي تريده.
  • إلى البنية: كل قطعة مُفكَّكة الترميز تُسلَّم مجددًا للتعرّف التلقائي، فتُحلَّل protobuf وJSON وplist أيضًا إلى صورة بنيوية، قابلة للعرض عبر أوضاع العرض المتعددة في فحص البيانات وفك ترميزها.
  • يمكن تسمية مُفكِّكات الترميز والاحتفاظ بها كقائمة، لإضافتها أو تعديلها أو حذفها في أي وقت.

  • عندما تلتقط بروتوكول مقبس (socket) داخليًا أو خاصًا (شكل شائع هو بادئة الطول + protobuf / JSON / ثنائي) لا تتعرف عليه الصيغ المدمجة، اكتب مُفكِّك ترميز لتقطيعه واستعادته إلى بنية قابلة للقراءة.
  • عندما تريد تحويل قطعة من البايتات مُغلَّفة بترويسة مخصصة، أو مرّت بجولة من الضغط، مرة أخرى إلى محتوى قابل للقراءة.

7. مرجع واجهة برمجة التطبيقات (API)

Section titled “7. مرجع واجهة برمجة التطبيقات (API)”

مرجع سريع موحَّد: نقطة دخول فك الترميز، والدوال المساعدة المدمجة، وبيئة التشغيل. كل دالة تأخذ buf تقبل إما ArrayBuffer أو سلسلة نصية.

decode(buf, out) → number
الغرض نقطة الدخول الوحيدة لمُفكِّك الترميز، يجب تنفيذها. قطّع الرسائل من مقدمة buf، وادفعها إلى out، وأعد عدد البايتات المُستهلكة هذه المرة.
buf ArrayBuffer، تدفق البايتات المتبقي بانتظار التحليل. استخدم buf.byteLength لطوله؛ لا يمكنك فهرسة البايتات، استخدم المساعدات أدناه أو new DataView(buf) / new Uint8Array(buf).
out Array، مُجمِّع المخرجات. out.push(bytes) يدفع رسالة واحدة (bytes هي ArrayBuffer أو سلسلة نصية؛ الأنواع الأخرى تُتجاهَل)؛ يمكنك دفع عدة رسائل دفعة واحدة.
القيمة المُعادة number، البايتات المُستهلكة من مقدمة buf. > 0 يتابع؛ 0 / سالب / بلا إعادة ← توقف، وتُعرَض البايتات المتبقية كخام.
اتفاقية الاستدعاء تُجرى تمريرة واحدة عبر تدفق الإرسال والاستقبال للاتصال؛ كل تمريرة تُستدعى مرارًا، تُمرِّر البايتات المتبقية غير المُستهلكة في كل مرة، حتى تُعيد 0.

الدوال المساعدة المدمجة

Section titled “الدوال المساعدة المدمجة”

أخذ شريحة فرعية

التوقيع يُعيد الوصف
sub(buf, off) ArrayBuffer قطّع من off إلى النهاية
sub(buf, off, len) ArrayBuffer قطّع len بايت من off؛ off / len خارج النطاق يُقيَّد تلقائيًا، دون خطأ

قراءة عدد صحيح (غير موقّع؛ يُعيد 0 عندما يكون off خارج النطاق)

التوقيع يُعيد الوصف
u8(buf, off) number قراءة بايت واحد
u16be(buf, off) / u16le(buf, off) number قراءة بايتين، big-endian / little-endian
u32be(buf, off) / u32le(buf, off) number قراءة 4 بايتات، big-endian / little-endian

التحويل إلى نص

التوقيع يُعيد الوصف
hex(buf) string التحويل إلى سداسي عشري بأحرف صغيرة
ascii(buf) string القراءة كنص (مناسب لمحتوى ASCII / نصي)

الضغط / فك الضغط (يُعيد المُدخل دون تغيير عندما يتعذّر فك الضغط؛ سقف فك الضغط الواحد نحو 16 ميغابايت)

التوقيع يُعيد الوصف
gzipMagic(buf) boolean هل يبدأ برقم gzip السحري
gunzip(buf) ArrayBuffer فك ضغط gzip
inflate(buf) ArrayBuffer فك ضغط zlib / deflate
unzstd(buf) ArrayBuffer فك ضغط zstd
lz4dtx(buf) ArrayBuffer فك ضغط تدفق LZ4 المجزّأ

تصحيح

التوقيع يُعيد الوصف
log(...args) (لا شيء) يضم المعاملات في سطر واحد ويطبعه في لوحة “مخرجات التصحيح”؛ يُعرَض ArrayBuffer بالسداسي العشري
console.log(...args) (لا شيء) مثل log (مريح لمن اعتاد console.log)
  • JavaScript قياسي: الكائنات المدمجة الشائعة مثل ArrayBuffer، والمصفوفات المكتوبة (Uint8Array وغيرها)، وDataView، وJSON، وMath، وRegExp، وDate كلها متاحة؛ عندما تحتاج قراءة/كتابة بايتات على مستوى أدنى، فإن new DataView(buf) / new Uint8Array(buf) في متناول اليد.
  • تصحيح: log(...) / console.log(...) تطبع في لوحة “مخرجات التصحيح”؛ تُطبع أخطاء النص البرمجي والمهل الزمنية هناك أيضًا، موسومة بما إذا كانت من تدفق الإرسال أو الاستقبال .
  • غير المتوفر هي القدرات الأخرى الخاصة بالمتصفح / Node: طرق console غير log، وTextDecoder / TextEncoder، وfetch، وsetTimeout، وrequire، إلخ، كلها غير متاحة. لقراءة البايتات كنص، استخدم ascii(buf).
  • لكل استدعاء لـdecode سقف زمن تنفيذ: تُقاطَع الحلقة اللانهائية أو تجاوز المهلة وتُعامَل كـ“لم تستهلك شيئًا”، فلن تُعلِّق الأداة.

العودة إلى التقاط عبر البروكسي · ذو صلة: فحص البيانات وفك ترميزها · الالتقاط المحلي من بطاقة الشبكة