انتقل إلى المحتوى
HexaTransfer
العودة إلى المدونة
تعمق تقني

WebRTC نقل الملفات: Browser-to-Browser Tutorial

انقل الملفات directly between browsers using WebRTC. Data channels, signaling, and peer connection setup for real-time مشاركة الملفات.

نقل ملفات WebRTC يُحرِّك البايتات مباشرةً بين متصفحين باستخدام RTCDataChannel، بدون خادم في مسار البيانات بعد المصافحة الأولية. القطع التي تحتاجها: قناة إشارة (WebSocket أو أي وسيط صغير) لتبادل عروض SDP ومرشحي ICE، وخوادم STUN لاكتشاف NAT، وبديل TURN لـ NATs المتماثلة، وقناة بيانات مهيأة للتسليم الموثوق المُرتَّب. حين يعمل اتصال النظير، تستدعي channel.send() بأجزاء 16-256 كيلوبايت وتراقب تدفق البايتات عبر ارتباط SCTP مُشفَّر بـ DTLS 1.2 بسرعة شبكة تقريبية.

كيف تُحقق WebRTC الاتصال المباشر بين النظراء

WebRTC ليست سحراً — هي ICE بالإضافة إلى SDP بالإضافة إلى DTLS بالإضافة إلى SCTP مكدَّسة معاً. المُرسِل ينشئ RTCPeerConnection، يفتح قناة بيانات، يُولِّد عرض SDP، ويُرسِله إلى المستلم عبر قناة الإشارة. المستلم يُجيب. ثم يتبادل الجانبان مرشحي ICE (IP محلي، IP انعكاسي عبر STUN، IP مُرحَّل عبر TURN) حتى يجدون مساراً يعمل. DTLS 1.2 يُصافح من طرف إلى طرف، SCTP يعمل فوقه للتدفق الموثوق، وتبدأ البايتات في التدفق.

التشفير إلزامي ومُدمَج. لا يمكنك رفضه. هذا فوز أمني مهم — على خلاف WebSockets، لا تحتاج تذكُّر تغليف أي شيء في تشفير طبقة التطبيق للحماية من متنصتي الشبكة. للتشفير الكامل من طرف إلى طرف ضد خادم الإشارة الخاص بك، أضِف طبقة ثانية من AES-256-GCM، لأن خادم إشارة خبيثاً يستطيع استبدال شهادة DTLS الخاصة به.

الإشارة: الجزء الذي لا تُعرِّفه WebRTC

WebRTC تترك الإشارة لك عمداً. WebSocket على خادمك جيد؛ وكذلك رمز غرفة مشترك مُنشَر في Firebase realtime DB، أو حتى سلاسل SDP مُلصَقة يدوياً. ما يهم هو أن كلا النظيرين يتبادلان في النهاية عرضاً وإجابةً وتدفقاً من مرشحي ICE.

خادم إشارة بسيط في Node:

const rooms = new Map();
wss.on('connection', (ws) => {
  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type === 'join') {
      const room = rooms.get(msg.room) ?? new Set();
      room.add(ws); rooms.set(msg.room, room);
    } else {
      for (const peer of rooms.get(msg.room) ?? []) {
        if (peer !== ws) peer.send(raw);
      }
    }
  });
});

أقل من 20 سطراً. الخادم لا يرى بايتات الملفات أبداً — فقط بيانات SDP وICE الوصفية. يمكنك استضافته على VPS بـ 5 دولارات أو Cloudflare Workers وخدمة مئات النقلات المتزامنة.

إقامة اتصال النظير

أنشئ الاتصال مع STUN العام من Google بالإضافة إلى بديل TURN:

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'turn:turn.example.com:3478',
      username: 'user', credential: 'pass' }
  ]
});

نحو 15-25% من الاتصالات المنزلية تجلس خلف NATs متماثلة لا يستطيع STUN اختراقها، لذا TURN ليس اختيارياً لخدمة إنتاجية. شغِّل coturn على VPS ببنية تحتية تحمِّلك نقل المرور المُرحَّل، أو ادفع لمزود TURN مُدار مثل Xirsys أو Twilio.

افتح قناة البيانات على جانب مُقدِّم العرض قبل إنشاء العرض:

const channel = pc.createDataChannel('file', {
  ordered: true, maxRetransmits: null
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer.sdp });

الترتيب مع إعادة إرسال غير محدودة يُعطيك موثوقية مكافئة لـ TCP. الوضع غير المُرتَّب أسرع لكن يحتاج إعادة تجميع على مستوى التطبيق.

تقطيع الملف للقناة

SCTP لديه حد عملي بـ 256 كيلوبايت لكل رسالة، والمتصفحات القديمة تكافح فوق 16 كيلوبايت. افتراضي آمن هو أجزاء 16 كيلوبايت تُقرَأ من الملف عبر File.slice():

const chunkSize = 16 * 1024;
let offset = 0;
channel.bufferedAmountLowThreshold = 1024 * 1024;
function sendNext() {
  while (offset < file.size && channel.bufferedAmount < 4 * 1024 * 1024) {
    const chunk = file.slice(offset, offset + chunkSize);
    chunk.arrayBuffer().then((buf) => channel.send(buf));
    offset += chunkSize;
  }
}
channel.onbufferedamountlow = sendNext;
sendNext();

علامة المياه bufferedAmount تمنعك من تصفيف جيجابايتات في مخزن إرسال SCTP وتنفاد الذاكرة. حين ينخفض المخزن تحت 1 ميجابايت، أعِد الملء حتى 4 ميجابايت. هذا يُعطي إنتاجية قرب 40-80 ميجابايت/ثانية على الشبكات المحلية و5-20 ميجابايت/ثانية على النطاق العريض المنزلي النموذجي.

استقبال البايتات وكتابتها للقرص

على جانب المُجيب، استمع للقناة الواردة:

pc.ondatachannel = ({ channel }) => {
  const chunks = [];
  let received = 0;
  channel.onmessage = ({ data }) => {
    chunks.push(data);
    received += data.byteLength;
    updateProgress(received);
    if (received === expectedSize) finish(chunks);
  };
};

للملفات أكبر من 500 ميجابايت، لا تتراكم في الذاكرة. استخدم File System Access API للتدفق مباشرةً إلى القرص:

const handle = await window.showSaveFilePicker({ suggestedName: fileName });
const writable = await handle.createWritable();
channel.onmessage = async ({ data }) => writable.write(data);

Firefox وSafari لا يدعمان showSaveFilePicker بعد، لذا ارجع إلى Blob + URL.createObjectURL لتلك المتصفحات، محدودة بـ 2 جيجابايت.

إرسال بيانات وصفية الملف قبل البايتات

المستلم يحتاج اسم الملف وحجمه ونوع MIME قبل بدء تدفق البايتات. استخدم مصافحة JSON صغيرة على قناة البيانات:

channel.send(JSON.stringify({
  type: 'metadata', name: file.name,
  size: file.size, mime: file.type, sha256: fileHash
}));

ثم انتقل إلى الوضع الثنائي. المستلم يُبدِّل بناءً على ما إذا كانت data سلسلة نصية أو ArrayBuffer. ضمِّن SHA-256 للملف للتحقق من السلامة بعد النقل، وبصمة المفتاح اختيارياً إذا كنت تُضيف AES-GCM على مستوى التطبيق.

التعامل مع فشل الاتصال

قنوات بيانات WebRTC تفشل بثلاث طرق: ICE لا يكتمل (NAT، جدار ناري يحجب)، مصافحة DTLS تفشل (انحراف الساعة، مشاكل الشهادة)، أو الاتصال ينقطع في منتصف النقل (نوم الحاسوب المحمول، تغيير الشبكة). استمع لـ pc.oniceconnectionstatechange وتصرف على 'failed' أو 'disconnected'. Chrome يُبقي 'disconnected' لبضع ثوانٍ قبل الانتقال إلى 'failed'؛ Safari أقل صبراً.

عند الفشل في منتصف النقل، أعِد تشغيل ICE بدون إعادة بناء الاتصال كله:

await pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
// إعادة الإرسال عبر الإشارة

إذا فشلت إعادة التشغيل، ارجع إلى الرفع القابل للاستئناف عبر خادمك — بنية هجينة P2P + خادم. بعض أدوات النقل عبر WebRTC مثل Wormhole وjustbeamit تستخدم هذا النمط لأنه يتعامل مع 15% من أوضاع الشبكة التي لا يستطيع P2P النقي العمل فيها.

إضافة التشفير الكامل من طرف إلى طرف ضد خادمك

DTLS المُدمَجة في WebRTC تحمي ضد مهاجمي الشبكة لكن لا ضد خادم إشارة خبيث أو مُخترَق. للتشفير الكامل الحقيقي، اجعل كلا النظيرين يُولِّدان زوج مفاتيح ECDH P-256، يتبادلان المفاتيح العامة عبر رمز خارج النطاق قصير (QR أو عبارة مرور بـ 6 كلمات)، يشتقان سراً مشتركاً عبر HKDF-SHA256، ويُشفِّران كل رسالة قناة بيانات بـ AES-256-GCM قبل استدعاء send. بهذه الطريقة حتى لو استبدل خادم الإشارة شهادات DTLS، لا يستطيع قراءة ملفاتك.

HexaTransfer يستخدم بنية هجينة — نص مشفَّر مخزَّن من جانب الخادم بـ AES-256-GCM من جانب العميل، بدلاً من P2P — يتاجِر بالمباشرة مقابل المشاركة المتحملة للوضع غير المتصل.

أين تتفوق WebRTC وأين تُخفِق

نقل ملفات WebRTC يتألق حين يكون كلا النظيرين متصلَين في آنٍ، وحين تهم الخصوصية من خادمك، وحين تكون الملفات كبيرة بما يكفي (100 ميجابايت+) لتكون تكاليف نطاق الترحيل مُؤلِمة. تُخفِق حين يريد المستخدمون الإرسال والمغادرة، وحين يفتح المستلمون الرابط بعد ساعات، أو حين يكون المستلمون على شبكات مؤسسية مُقيِّدة تحجب STUN وTURN. لأداة نقل متعددة الأغراض، P2P الخالص يُغطي ربما 60% من حالات الاستخدام. الـ 40% الأخرى تحتاج بديلاً مدعوماً بخادم — لهذا تقريباً كل منتج "نقل ملفات P2P" لديه وسيط في البنية في مكان ما.

جرّبها على hexatransfer.com — مجاناً، بدون حساب، حتى 10 جيجابايت.

أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف

انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.

إرسال ملف