بدء استخدام اتصالات الزملاء

اتصالات الند للند هي جزء من مواصفات WebRTC التي تتناول ربط تطبيقَين على جهازَي كمبيوتر مختلفَين للتواصل باستخدام بروتوكول الند للند. يمكن أن يكون التواصل بين الأجهزة عبارة عن فيديو أو صوت أو بيانات ثنائية عشوائية (للعملاء الذين تتوافق أجهزتهم مع واجهة برمجة التطبيقات RTCDataChannel). لمعرفة كيفية ربط جهازين نظيرين، يجب أن يوفّر كلا العميلين إعدادات خادم ICE. ويكون هذا الخادم إما STUN أو TURN، ودوره هو توفير مرشّحَي ICE لكل عميل، ثم يتم نقل المرشّحَين إلى الجهاز البعيد. يُطلق على عملية نقل مرشّحي ICE اسم الإشارة.

إرسال الإشارات

يتضمّن مواصفات WebRTC واجهات برمجة تطبيقات للتواصل مع خادم ICE (إعداد الاتصال بالإنترنت)، ولكن لا يشكّل عنصر الإشارة جزءًا من هذه المواصفات. يجب إرسال إشارات لكي يتمكّن نظيران من مشاركة كيفية الاتصال. يتم عادةً حلّ هذه المشكلة من خلال واجهة برمجة تطبيقات عادية على الويب تستند إلى HTTP (أي خدمة REST أو آلية أخرى لإجراء استدعاء إجراء عن بُعد) حيث يمكن لتطبيقات الويب نقل المعلومات اللازمة قبل بدء اتصال الند للند.

يوضّح مقتطف الرمز التالي كيفية استخدام خدمة الإشارات الوهمية هذه لإرسال الرسائل وتلقّيها بشكل غير متزامن. وسيتم استخدام هذا الرمز في الأمثلة المتبقية في هذا الدليل عند الضرورة.

// Set up an asynchronous communication channel that will be
// used during the peer connection setup
const signalingChannel = new SignalingChannel(remoteClientId);
signalingChannel.addEventListener('message', message => {
    // New message from remote client received
});

// Send an asynchronous message to the remote client
signalingChannel.send('Hello!');

يمكن تنفيذ عملية إرسال الإشارات بعدة طرق مختلفة، ولا يفضّل مواصفات WebRTC أي حلّ معيّن.

بدء اتصالات الأجهزة المتجاورة

تتم معالجة كل اتصال بين الأجهزة من خلال كائن RTCPeerConnection. تستخدِم الدالة الإنشائية لهذه الفئة كائن RTCConfiguration واحدًا كمَعلمة. يحدّد هذا العنصر كيفية إعداد اتصال الند للند، ويجب أن يتضمّن معلومات حول خوادم ICE التي سيتم استخدامها.

بعد إنشاء RTCPeerConnection، علينا إنشاء عرض أو ردّ على بروتوكول وصف الجلسة (SDP)، وذلك حسب ما إذا كنا الطرف الذي يجري المكالمة أو الطرف الذي يتلقّاها. بعد إنشاء عرض أو ردّ SDP، يجب إرساله إلى الجهاز البعيد من خلال قناة مختلفة. تُعرف عملية تمرير عناصر SDP إلى الأجهزة البعيدة باسم الإشارة، وهي غير مشمولة بمواصفات WebRTC.

لبدء عملية إعداد اتصال الند للند من جانب المتصل، ننشئ كائن RTCPeerConnection ثم نستدعي createOffer() لإنشاء كائن RTCSessionDescription. يتم ضبط وصف الجلسة هذا كالوصف المحلي باستخدام setLocalDescription()، ثم يتم إرساله عبر قناة الإشارات إلى الطرف المستلِم. نضبط أيضًا أداة استماع لقناة الإشارات الخاصة بنا عندما نتلقّى ردًا من الطرف الآخر على وصف الجلسة الذي قدّمناه.

async function makeCall() {
    const configuration = {'iceServers': [{'urls': 'stun:stun.l.google.com:19302'}]}
    const peerConnection = new RTCPeerConnection(configuration);
    signalingChannel.addEventListener('message', async message => {
        if (message.answer) {
            const remoteDesc = new RTCSessionDescription(message.answer);
            await peerConnection.setRemoteDescription(remoteDesc);
        }
    });
    const offer = await peerConnection.createOffer();
    await peerConnection.setLocalDescription(offer);
    signalingChannel.send({'offer': offer});
}

من جهة الاستقبال، ننتظر عرضًا واردًا قبل إنشاء مثيل RTCPeerConnection. بعد ذلك، نضبط العرض الترويجي الذي تم تلقّيه باستخدام setRemoteDescription(). بعد ذلك، نطلب createAnswer() لإنشاء رد على العرض الذي تم تلقّيه. يتم ضبط هذا الردّ كالوصف المحلي باستخدام setLocalDescription() ثم يتم إرساله إلى الطرف المتصل عبر خادم الإشارات.

const peerConnection = new RTCPeerConnection(configuration);
signalingChannel.addEventListener('message', async message => {
    if (message.offer) {
        peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer));
        const answer = await peerConnection.createAnswer();
        await peerConnection.setLocalDescription(answer);
        signalingChannel.send({'answer': answer});
    }
});

بعد أن يضبط كلا النظيرين أوصاف الجلسة المحلية والبعيدة، سيعرفان إمكانات النظير البعيد. ولا يعني ذلك أنّ الاتصال بين الأجهزة النظيرة جاهز. لكي تعمل هذه الميزة، علينا جمع مرشّحي ICE لدى كل جهاز نظير ونقلهم (عبر قناة الإشارة) إلى الجهاز النظير الآخر.

المرشحون لشهادة ICE

قبل أن يتمكّن نظيران من التواصل باستخدام WebRTC، عليهما تبادل معلومات الاتصال. بما أنّ ظروف الشبكة يمكن أن تختلف استنادًا إلى عدد من العوامل، يتم عادةً استخدام خدمة خارجية للعثور على المرشحين المحتملين للاتصال بنظير. تُعرف هذه الخدمة باسم ICE وتستخدم خادم STUN أو TURN. ‫STUN هي اختصار لعبارة Session Traversal Utilities for NAT، ويتم استخدامها عادةً بشكل غير مباشر في معظم تطبيقات WebRTC.

‫TURN (Traversal Using Relay NAT) هو الحل الأكثر تقدّمًا الذي يتضمّن بروتوكولات STUN، وتستخدم معظم الخدمات التجارية المستندة إلى WebRTC خادم TURN لإنشاء اتصالات بين الأجهزة المتصلة بشبكة الند للند. تتيح واجهة برمجة التطبيقات WebRTC كلاً من STUN وTURN مباشرةً، ويتم تجميعها ضمن المصطلح الأكثر شمولاً وهو "إعداد الاتصال بالإنترنت". عند إنشاء اتصال WebRTC، نقدّم عادةً خادمًا واحدًا أو عدة خوادم ICE في الإعدادات الخاصة بالكائن RTCPeerConnection.

Trickle ICE

بعد إنشاء عنصر RTCPeerConnection، يستخدم الإطار الأساسي خوادم ICE المتوفّرة لجمع المرشّحين من أجل إنشاء اتصال (مرشّحو ICE). يشير الحدث icegatheringstatechange في RTCPeerConnection إلى حالة عملية جمع معلومات ICE (new أو gathering أو complete).

مع أنّه يمكن لأحد النظراء الانتظار إلى حين اكتمال عملية جمع معلومات ICE، يكون من الأجدى عادةً استخدام تقنية "trickle ice" وإرسال كل مرشح ICE إلى النظير البعيد عند اكتشافه. سيؤدي ذلك إلى تقليل وقت الإعداد بشكل كبير لاتصال الأجهزة المجاورة، كما سيسمح ببدء مكالمة فيديو مع تأخير أقل.

لجمع مرشّحي ICE، ما عليك سوى إضافة أداة معالجة للحدث icecandidate. سيحتوي حدث RTCPeerConnectionIceEvent الذي تم إطلاقه على هذا المستمع على السمة candidate التي تمثّل مرشحًا جديدًا يجب إرساله إلى النظير البعيد (راجِع Signaling).

// Listen for local ICE candidates on the local RTCPeerConnection
peerConnection.addEventListener('icecandidate', event => {
    if (event.candidate) {
        signalingChannel.send({'new-ice-candidate': event.candidate});
    }
});

// Listen for remote ICE candidates and add them to the local RTCPeerConnection
signalingChannel.addEventListener('message', async message => {
    if (message.iceCandidate) {
        try {
            await peerConnection.addIceCandidate(message.iceCandidate);
        } catch (e) {
            console.error('Error adding received ice candidate', e);
        }
    }
});

تم إنشاء اتصال

بعد تلقّي مرشّحي ICE، من المفترض أن تتغيّر حالة اتصال الند للند في النهاية إلى حالة اتصال. لرصد ذلك، نضيف أداة معالجة إلى RTCPeerConnection حيث نستمع إلى أحداث connectionstatechange.

// Listen for connectionstatechange on the local RTCPeerConnection
peerConnection.addEventListener('connectionstatechange', event => {
    if (peerConnection.connectionState === 'connected') {
        // Peers connected!
    }
});

مستندات واجهة برمجة التطبيقات RTCPeerConnection