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

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

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

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

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

// 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

قبل أن يتمكّن جهازان من التواصل باستخدام 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 إلى حالة عملية جمع عناوين IP للاتصال بالإنترنت (new أو gathering أو complete).

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

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

// 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