תחילת העבודה עם חיבורים של אפליקציות להשוואה

חיבורי Peer הם החלק במפרטי WebRTC שמתייחס לחיבור של שתי אפליקציות במחשבים שונים כדי לתקשר באמצעות פרוטוקול peer-to-peer. התקשורת בין השותפים יכולה להיות וידאו, אודיו או נתונים בינאריים שרירותיים (ללקוחות שתומכים ב-API RTCDataChannel). כדי לברר איך שני עמיתים יכולים להתחבר, שני הלקוחות צריכים לספק הגדרה של שרת ICE. זהו שרת STUN או שרת TURN, והתפקיד שלו הוא לספק לכל לקוח מועמדים ל-ICE, שמועברים לאחר מכן לשותף המרוחק. העברת המועמדים ל-ICE נקראת בדרך כלל איתות.

איתות

מפרט WebRTC כולל ממשקי API לתקשורת עם שרת ICE (יצירת קישוריות לאינטרנט), אבל רכיב האותות לא נכלל בו. איתות נדרש כדי ששני שותפים יוכלו לשתף איך הם צריכים להתחבר. בדרך כלל הבעיה הזו נפתרת באמצעות ממשק 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. ה-constructor של הכיתה מקבל אובייקט 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 כדי ליצור חיבורים בין שכננו. ממשק ה-API של WebRTC תומך ישירות גם ב-STUN וגם ב-TURN, והוא נכלל במונח המלא יותר Internet Connectivity Establishment. כשיוצרים חיבור WebRTC, בדרך כלל מספקים שרת ICE אחד או כמה שרתי ICE בהגדרה של האובייקט RTCPeerConnection.

Trickle ICE

אחרי שיוצרים אובייקט RTCPeerConnection, המסגרת הבסיסית משתמשת בשרתי ה-ICE שסופקו כדי לאסוף מועמדים ליצירת קישוריות (מועמדים ל-ICE). האירוע icegatheringstatechange ב-RTCPeerConnection מציין את המצב של איסוף ה-ICE (new, ‏ gathering או complete).

אפשר להמתין עד שהאיסוף של ICE יושלם, אבל בדרך כלל יעיל הרבה יותר להשתמש בשיטה של 'trickle 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 API