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

תאריך פרסום: 4 בנובמבר 2013

‫WebRTC מאפשר תקשורת מקצה לקצה (P2P). הוא דורש שרתים כדי שהלקוחות יוכלו להחליף מטא-נתונים ולתאם תקשורת באמצעות תהליך שנקרא איתות. השרתים גם עוזרים ללקוחות להתמודד עם מתרגמי כתובות רשת (NAT) וחומות אש.

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

מה זה איתות?

איתות הוא תהליך של תיאום התקשורת. כדי שאפליקציית WebRTC תוכל להגדיר שיחה, הלקוחות שלה צריכים להחליף את המידע הבא:

  • הודעות לבקרת סשן שמשמשות לפתיחה או לסגירה של תקשורת
  • הודעות שגיאה
  • מטא-נתונים של מדיה, כמו קודקים, הגדרות קודקים, רוחב פס וסוגי מדיה
  • נתוני מפתח שמשמשים ליצירת חיבורים מאובטחים
  • נתוני רשת, כמו כתובת ה-IP והיציאה של המארח כפי שהן נראות לעולם החיצון

תהליך האיתות הזה דורש דרך שבה הלקוחות יוכלו להעביר הודעות הלוך ושוב. המנגנון הזה לא מוטמע על ידי ממשקי ה-API של WebRTC. במקום זאת, אתם צריכים ליצור אותו בעצמכם.

למה איתות לא מוגדר על ידי WebRTC?

כדי למנוע כפילויות ולמקסם את התאימות לטכנולוגיות קיימות, שיטות ופרוטוקולים של איתות לא מוגדרים בתקני WebRTC. הגישה הזו מתוארת ב-JavaScript Session Establishment Protocol (JSEP):

הארכיטקטורה של JSEP גם מונעת מהדפדפן לשמור את המצב, כלומר לתפקד כמכונת מצבים של איתות. זה יכול להיות בעייתי אם, לדוגמה, נתוני האיתותים אובדים בכל פעם שדף נטען מחדש. במקום זאת, אפשר לשמור את מצב האיתות בשרת.

דיאגרמת ארכיטקטורה של JSEP

ב-JSEP נדרשת החלפה בין עמיתים של הצעה ותשובה, המטא-נתונים של המדיה. ההצעות והתשובות מועברות בפורמט Session Description Protocol ‏(SDP), שנראה כך:

v=0
o=- 7614219274584779017 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
c=IN IP4 0.0.0.0
a=rtcp:1 IN IP4 0.0.0.0
a=ice-ufrag:W2TGCZw2NZHuwlnf
a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=mid:audio
a=rtcp-mux
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
a=rtpmap:111 opus/48000/2
...

רוצים לדעת מה כל המונחים האלה אומרים? דוגמאות של ארגון התקינה בנושאי האינטרנט (IETF)

חשוב לזכור ש-WebRTC מתוכנן כך שאפשר לשנות את ההצעה או את התשובה לפני שמגדירים אותן כתיאור המקומי או המרוחק, על ידי עריכת הערכים בטקסט של ה-SDP. לדוגמה, אפשר להשתמש בפונקציה preferAudioCodec() ב-appr.tc כדי להגדיר את קצב העברת הנתונים ואת ה-codec שמוגדרים כברירת מחדל. קצת קשה לתפעל את SDP באמצעות JavaScript, ויש דיון בשאלה אם בגרסאות עתידיות של WebRTC כדאי להשתמש ב-JSON במקום זאת, אבל יש כמה יתרונות לשימוש ב-SDP.

‫RTCPeerConnection API ואיתות: הצעה, תשובה ומועמד

‫RTCPeerConnection הוא ה-API שמשמש אפליקציות WebRTC כדי ליצור חיבור בין עמיתים ולהעביר אודיו ווידאו.

כדי להתחיל את התהליך, RTCPeerConnection מבצע שתי משימות:

  • לבדוק את תנאי המדיה המקומיים, כמו רזולוציה ויכולות של קודקים. אלה המטא-נתונים שמשמשים למנגנון ההצעה והתשובה.
  • מקבלים כתובות רשת פוטנציאליות למארח של האפליקציה, שנקראות מועמדים.

אחרי שמוודאים את הנתונים המקומיים האלה, צריך להחליף אותם באמצעות מנגנון איתות עם המשתמש המרוחק.

נניח שאליס מנסה להתקשר לאווה. הנה מנגנון המענה המלא, על כל פרטיו:

  1. אליס יוצרת אובייקט RTCPeerConnection.
  2. אליס יוצרת הצעה (תיאור סשן SDP) באמצעות השיטה RTCPeerConnection createOffer().
  3. אליס מתקשרת אל setLocalDescription() עם ההצעה שלה.
  4. אליס ממירה את ההצעה למחרוזת ומשתמשת במנגנון איתות כדי לשלוח אותה לאווה.
  5. ‫Eve מתקשרת אל setRemoteDescription() עם ההצעה של Alice, כדי ש-RTCPeerConnection שלה ידע על ההגדרה של Alice.
  6. איב קוראת ל-createAnswer() והקריאה החוזרת (callback) המוצלחת של הפעולה הזו מועברת לתיאור סשן מקומי – התשובה של איב.
  7. איב מגדירה את התשובה שלה כתיאור המקומי באמצעות קריאה ל-setLocalDescription().
  8. לאחר מכן, חווה משתמשת במנגנון האיתות כדי לשלוח את התשובה שלה כמחרוזת לאליס.
  9. אליס מגדירה את התשובה של איב כתיאור הסשן המרוחק באמצעות setRemoteDescription().

גם אליס ואיב צריכות להחליף ביניהן מידע על הרשת. הביטוי "חיפוש מועמדים" מתייחס לתהליך של חיפוש ממשקי רשת ויציאות באמצעות מסגרת ICE.

  1. עינת יוצרת אובייקט RTCPeerConnection עם handler של onicecandidate.
  2. המערכת קוראת ל-handler כשהמועמדים לרשת הופכים לזמינים.
  3. ב-handler, עינת שולחת נתוני מועמדים שהומרו למחרוזת אל אלישבע דרך ערוץ האיתות שלהן.
  4. כש-Eve מקבלת הודעת מועמד מ-Alice, היא קוראת ל-addIceCandidate() כדי להוסיף את המועמד לתיאור של הצד המרוחק.

‫JSEP תומך ב-ICE Candidate Trickling, שמאפשר למתקשר לספק בהדרגה מועמדים למקבל השיחה אחרי ההצעה הראשונית, ולמקבל השיחה להתחיל לפעול בשיחה ולהגדיר חיבור בלי לחכות שכל המועמדים יגיעו.

קוד WebRTC לאיתות

קטע הקוד הבא הוא קוד לדוגמה של W3C שמסכם את כל תהליך האיתות. הקוד מניח שקיים מנגנון איתות כלשהו, SignalingChannel. בהמשך נרחיב על איתות.

// handles JSON.stringify/parse
const signaling = new SignalingChannel();
const constraints = {audio: true, video: true};
const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
const pc = new RTCPeerConnection(configuration);

// Send any ice candidates to the other peer.
pc.onicecandidate = ({candidate}) => signaling.send({candidate});

// Let the "negotiationneeded" event trigger offer generation.
pc.onnegotiationneeded = async () => {
  try {
    await pc.setLocalDescription(await pc.createOffer());
    // send the offer to the other peer
    signaling.send({desc: pc.localDescription});
  } catch (err) {
    console.error(err);
  }
};

// After remote track media arrives, show it in remote video element.
pc.ontrack = (event) => {
  // Don't set srcObject again if it is already set.
  if (remoteView.srcObject) return;
  remoteView.srcObject = event.streams[0];
};

// Call start() to initiate.
async function start() {
  try {
    // Get local stream, show it in self-view, and add it to be sent.
    const stream =
      await navigator.mediaDevices.getUserMedia(constraints);
    stream.getTracks().forEach((track) =>
      pc.addTrack(track, stream));
    selfView.srcObject = stream;
  } catch (err) {
    console.error(err);
  }
}

signaling.onmessage = async ({desc, candidate}) => {
  try {
    if (desc) {
      // If you get an offer, you need to reply with an answer.
      if (desc.type === 'offer') {
        await pc.setRemoteDescription(desc);
        const stream =
          await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) =>
          pc.addTrack(track, stream));
        await pc.setLocalDescription(await pc.createAnswer());
        signaling.send({desc: pc.localDescription});
      } else if (desc.type === 'answer') {
        await pc.setRemoteDescription(desc);
      } else {
        console.log('Unsupported SDP type.');
      }
    } else if (candidate) {
      await pc.addIceCandidate(candidate);
    }
  } catch (err) {
    console.error(err);
  }
};

כדי לראות את התהליכים של הצעה-תשובה והחלפת מועמדים בפעולה, היכנסו אל simpl.info RTCPeerConnection ועיינו ביומן המסוף לדוגמה של וידאו צ'אט בדף יחיד. אתם יכולים להוריד קובץ dump מלא של האותות והנתונים הסטטיסטיים של WebRTC מהדף about://webrtc-internals ב-Google Chrome או מהדף opera://webrtc-internals ב-Opera.

גילוי עמיתים

זו דרך מתוחכמת לשאול "איך אפשר למצוא מישהו לדבר איתו?"

לשיחות טלפון יש מספרי טלפון וספרי טלפונים. כדי להשתמש בווידאו צ'אט ובהודעות אונליין, אתם צריכים מערכות לניהול זהויות ונוכחות, ואמצעי שדרכו המשתמשים יוכלו להתחיל סשנים. באפליקציות WebRTC, הלקוחות צריכים דרך לאותת זה לזה שהם רוצים להתחיל שיחה או להצטרף אליה.

מנגנוני הגילוי של עמיתים לא מוגדרים על ידי WebRTC, ולא נפרט כאן את האפשרויות. התהליך יכול להיות פשוט כמו שליחת כתובת URL באימייל או בהודעה. באפליקציות של וידאו צ'אט, כמו Talky,‏ tawk.to ו-Browser Meeting, אתם יכולים להזמין אנשים לשיחה באמצעות שיתוף של קישור בהתאמה אישית. המפתח כריס בול בנה ניסוי מסקרן של serverless-webrtc שמאפשר למשתתפים בשיחת WebRTC להחליף מטא-נתונים באמצעות כל שירות הודעות שהם רוצים, כמו IM, אימייל או יונת דואר.

איך אפשר ליצור שירות איתות?

חשוב להדגיש: פרוטוקולים ומנגנונים של איתות לא מוגדרים בתקני WebRTC. לא משנה מה תבחרו, אתם צריכים שרת מתווך כדי להחליף הודעות איתות ונתוני אפליקציות בין לקוחות. לצערנו, אפליקציית אינטרנט לא יכולה לצעוק באינטרנט, "חבר אותי לחבר שלי!"

למרבה המזל, הודעות האיתות הן קטנות ומוחלפות בעיקר בתחילת השיחה. בבדיקה עם appr.tc לסשן של וידאו צ'אט, שירות האיתות טיפל בסך הכל בכ-30-45 הודעות, והגודל הכולל של כל ההודעות היה כ-10KB.

בנוסף לכך ששירותי האיתות של WebRTC לא דורשים רוחב פס רב, הם גם לא צורכים הרבה עיבוד או זיכרון, כי הם צריכים רק להעביר הודעות ולשמור כמות קטנה של נתוני מצב של סשן, כמו הלקוחות שמחוברים.

הודעות בדחיפה מהשרת ללקוח

Browser Support

  • Chrome: 6.
  • Edge: 79.
  • Firefox: 6.
  • Safari: 5.

Source

שירות ההודעות לאיתות צריך להיות דו-כיווני: מלקוח לשרת ומשרת ללקוח. תקשורת דו-כיוונית מנוגדת למודל הבקשה/תגובה של לקוח/שרת ב-HTTP, אבל במשך שנים פותחו פריצות שונות כמו long polling כדי להעביר נתונים משירות שפועל בשרת אינטרנט לאפליקציית אינטרנט שפועלת בדפדפן.

EventSource API מאפשר אירועים שנשלחים מהשרת, נתונים שנשלחים משרת אינטרנט ללקוח דפדפן באמצעות HTTP. ‫EventSource מיועד להעברת הודעות בכיוון אחד, אבל אפשר להשתמש בו בשילוב עם XHR כדי ליצור שירות להחלפת הודעות איתות. שירות איתות מעביר הודעה מהמתקשר, שנשלחת באמצעות בקשת XHR, על ידי דחיפה שלה דרך EventSource אל הנמען.

Browser Support

  • Chrome: 5.
  • Edge: 12.
  • Firefox: 11.
  • Safari: 5.

Source

‫WebSocket מיועד לתקשורת full-duplex בין לקוח לשרת, כלומר הודעות יכולות לזרום בשני הכיוונים בו-זמנית. אחד היתרונות של שירות איתות שמבוסס על WebSocket טהור או על אירועים שנשלחים מהשרת (EventSource) הוא שאפשר להטמיע את הקצה העורפי של ממשקי ה-API האלה במגוון סביבות פיתוח לאינטרנט, שמשותפות לרוב חבילות האירוח באינטרנט לשפות כמו PHP, ‏ Python ו-Ruby.

כל הדפדפנים שתומכים ב-WebRTC תומכים גם ב-WebSocket. כדאי להשתמש ב-TLS לכל החיבורים כדי לוודא שלא ניתן ליירט הודעות לא מוצפנות, וגם כדי לצמצם בעיות במעבר דרך שרת proxy. מידע נוסף על WebSocket ועל מעבר דרך שרת proxy זמין בפרק על WebRTC בספר High Performance Browser Networking של איליה גריגוריק.

אפשר לטפל באיתות על ידי הגדרת לקוחות WebRTC לדגום שרת העברת הודעות שוב ושוב דרך Ajax, אבל זה מוביל להרבה בקשות מיותרות לאחזור מהרשת, וזה בעייתי במיוחד במכשירים ניידים. גם אחרי שנוצר סשן, עמיתים צריכים לבצע דגימה של הודעות איתות למקרה שיהיו שינויים או שהסשן יסתיים על ידי עמיתים אחרים. באפליקציה לדוגמה WebRTC Book נעשה שימוש באפשרות הזו, עם כמה אופטימיזציות לתדירות הסקרים.

איתות בקנה מידה רחב

למרות ששירות איתות צורך מעט יחסית רוחב פס ו-CPU לכל לקוח, שרתי איתות של אפליקציה פופולרית עשויים לטפל בהרבה הודעות ממיקומים שונים עם רמות גבוהות של בו-זמניות. אפליקציות WebRTC עם הרבה תנועה צריכות שרתי איתות שיכולים להתמודד עם עומס משמעותי. לא נפרט כאן, אבל יש כמה אפשרויות להעברת הודעות בנפח גבוה ובביצועים גבוהים, כולל:

  • ‫eXtensible Messaging and Presence Protocol ‏(XMPP) הוא פרוטוקול שפותח להעברת הודעות מיידיות, ואפשר להשתמש בו לאיתות. הטמעות של שרתים כוללות את ejabberd ו-Openfire. לקוחות JavaScript, כמו Strophe.js, משתמשים ב-BOSH כדי לדמות סטרימינג דו-כיווני, אבל BOSH יכול להיות לא יעיל ועם יכולת התאמה נמוכה בהשוואה ל-WebSocket.

  • ספריות קוד פתוח, כמו ZeroMQ (שמשמשת את TokBox לשירות Rumour) ו-OpenMQ (NullMQ מיישמת את הקונספטים של ZeroMQ בפלטפורמות אינטרנט באמצעות פרוטוקול STOMP דרך WebSocket).

  • פלטפורמות מסחריות להעברת הודעות בענן שמשתמשות ב-WebSocket (אם כי הן עשויות לחזור ל-long polling), כמו Pusher ו-Kaazing.

  • פלטפורמות WebRTC מסחריות, כמו vLine

פיתוח שירות איתות באמצעות Socket.io ב-Node

הקוד הבא מיועד לאפליקציית אינטרנט שמשתמשת בשירות איתות שנבנה באמצעות Socket.io ב-Node. העיצוב של Socket.io מאפשר ליצור בקלות שירות להחלפת הודעות, והוא מתאים במיוחד לאיתות של WebRTC בגלל הקונספט המובנה של חדרים. הדוגמה הזו מיועדת למספר קטן יחסית של משתמשים, ולא לשימוש בסביבת ייצור.

‫Socket.io משתמש ב-WebSocket עם חלופות: דגימה ארוכה של AJAX, סטרימינג מרובה-חלקים של AJAX, ‏Forever Iframe ודגימת JSONP. היא הועברה לקצוות עורפיים שונים, אבל אולי היא מוכרת בעיקר בזכות גרסת Node שבה נעשה שימוש בדוגמה הזו.

בדוגמה הזו אין WebRTC. הוא נועד להראות איך ליצור איתות באפליקציית אינטרנט. אפשר להציג את יומן המסוף כדי לראות מה קורה כשלקוחות מצטרפים לחדר ומחליפים הודעות. ב-Codelab הזה של WebRTC יש הוראות מפורטות לשילוב של WebRTC באפליקציית וידאו צ'אט מלאה.

הנה הלקוח index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>WebRTC client</title>
  </head>
  <body>
    <script src='/socket.io/socket.io.js'></script>
    <script src='js/main.js'></script>
  </body>
</html>

זהו קובץ ה-JavaScript‏ main.js שאליו מפנה הלקוח:

const isInitiator;

room = prompt('Enter room name:');

const socket = io.connect();

if (room !== '') {
  console.log('Joining room ' + room);
  socket.emit('create or join', room);
}

socket.on('full', (room) => {
  console.log('Room ' + room + ' is full');
});

socket.on('empty', (room) => {
  isInitiator = true;
  console.log('Room ' + room + ' is empty');
});

socket.on('join', (room) => {
  console.log('Making request to join room ' + room);
  console.log('You are the initiator!');
});

socket.on('log', (array) => {
  console.log.apply(console, array);
});

הנה אפליקציית השרת המלאה:

const static = require('node-static');
const http = require('http');
const file = new(static.Server)();
const app = http.createServer(function (req, res) {
  file.serve(req, res);
}).listen(2013);

const io = require('socket.io').listen(app);

io.sockets.on('connection', (socket) => {

  // Convenience function to log server messages to the client
  function log(){
    const array = ['>>> Message from server: '];
    for (const i = 0; i < arguments.length; i++) {
      array.push(arguments[i]);
    }
      socket.emit('log', array);
  }

  socket.on('message', (message) => {
    log('Got message:', message);
    // For a real app, would be room only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', (room) => {
    const numClients = io.sockets.clients(room).length;

    log('Room ' + room + ' has ' + numClients + ' client(s)');
    log('Request to create or join room ' + room);

    if (numClients === 0){
      socket.join(room);
      socket.emit('created', room);
    } else if (numClients === 1) {
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room);
    } else { // max two clients
      socket.emit('full', room);
    }
    socket.emit('emit(): client ' + socket.id +
      ' joined room ' + room);
    socket.broadcast.emit('broadcast(): client ' + socket.id +
      ' joined room ' + room);

  });

});

כדי להריץ את האפליקציה הזו במארח מקומי, צריך להתקין את Node.js,‏ Socket.IO ו-node-static. כדי להתקין את Socket.IO ואת node-static, מריצים את npm מטרמינל בספריית האפליקציה:

npm install socket.io
npm install node-static

כדי להפעיל את השרת, מריצים את הפקודה הבאה מטרמינל בספריית האפליקציה:

node server.js

בדפדפן, פותחים את localhost:2013. פותחים כרטיסייה חדשה או חלון חדש בדפדפן כלשהו ופותחים שוב את localhost:2013. כדי לראות מה קורה, בודקים את המסוף. ב-Chrome וב-Opera, אפשר לגשת למסוף דרך הכלים למפתחים ב-Google Chrome באמצעות Ctrl+Shift+J (או Command+Option+J ב-Mac).

לא משנה באיזו גישה תבחרו לאיתות, ה-backend ואפליקציית הלקוח צריכים לספק שירותים דומים לאלה שבדוגמה הזו.

מלכודות באותות

  • ‫RTCPeerConnection לא יתחיל לאסוף מועמדים עד ש-setLocalDescription() יופעל. הדרישה הזו מופיעה בטיוטת JSEP IETF.
  • כדאי להשתמש ב-Trickle ICE. כדאי להתקשר אל addIceCandidate() ברגע שהמועמדים מגיעים.

שרתי איתות מוכנים מראש

אם אתם לא רוצים ליצור שרת משלכם, יש כמה שרתי איתות של WebRTC שמשתמשים ב-Socket.IO כמו בדוגמה הקודמת, ומשולבים עם ספריות JavaScript של לקוח WebRTC:

  • ‫webRTC.io היא אחת מספריות ההפשטה הראשונות של WebRTC.
  • ‫Signalmaster הוא שרת איתות שנוצר לשימוש עם ספריית הלקוח SimpleWebRTC ב-JavaScript.

אם אתם לא רוצים לכתוב קוד בכלל, יש פלטפורמות WebRTC מסחריות מלאות של חברות כמו vLine,‏ OpenTok ו-Asterisk.

לידיעתכם, חברת Ericsson בנתה שרת איתות באמצעות PHP ב-Apache בימים הראשונים של WebRTC. היום זה כבר קצת מיושן, אבל כדאי להסתכל על הקוד אם אתם שוקלים לעשות משהו דומה.

אבטחת Signal

ההצפנה היא חובה לכל רכיבי WebRTC.

עם זאת, מנגנוני האיתות לא מוגדרים בתקני WebRTC, ולכן אתם צריכים לדאוג לאבטחת האיתות. אם תוקף מצליח לפרוץ לאיתות, הוא יכול לעצור סשנים, להפנות מחדש חיבורים ולהקליט, לשנות או להזריק תוכן.

הגורם החשוב ביותר באבטחת האיתות הוא שימוש בפרוטוקולים מאובטחים: HTTPS ו-WSS, כמו TLS. כך אפשר לוודא שלא ניתן ליירט הודעות לא מוצפנות. בנוסף, חשוב להיזהר לא לשדר הודעות איתות באופן שמאפשר למתקשרים אחרים לגשת אליהן באמצעות אותו שרת איתות.

התמודדות עם NAT וחומות אש

כדי להעביר אותות של מטא-נתונים, אפליקציות WebRTC משתמשות בשרת מתווך, אבל כדי להעביר בפועל מדיה ונתונים אחרי שנוצר סשן, RTCPeerConnection מנסה לחבר לקוחות ישירות או בחיבור בין עמיתים (P2P).

בעולם פשוט יותר, לכל נקודת קצה (endpoint) של WebRTC הייתה כתובת ייחודית שהיא הייתה יכולה להחליף עם עמיתים אחרים כדי לתקשר ישירות.

חיבור פשוט בין משתמשים
עולם בלי NAT וחומות אש

בפועל, רוב המכשירים נמצאים מאחורי שכבה אחת או יותר של NAT, בחלקם יש תוכנת אנטי-וירוס שחוסמת יציאות ופרוטוקולים מסוימים, ורבים מהם נמצאים מאחורי שרתי proxy וחומות אש ארגוניות. למעשה, יכול להיות שחומת אש ו-NAT יוטמעו באותו מכשיר, כמו נתב Wi-Fi ביתי.

עמיתים מאחורי NAT וחומות אש
העולם האמיתי

אפליקציות WebRTC יכולות להשתמש במסגרת ICE כדי להתגבר על המורכבות של רשתות בעולם האמיתי. כדי להפעיל את זה, האפליקציה צריכה להעביר את כתובות ה-URL של שרתי ה-ICE אל RTCPeerConnection.

‫ICE מנסה למצוא את הנתיב הטוב ביותר לחיבור בין עמיתים. הוא מנסה את כל האפשרויות במקביל ובוחר את האפשרות היעילה ביותר שעובדת. בשלב הראשון, ICE מנסה ליצור חיבור באמצעות כתובת המארח שמתקבלת ממערכת ההפעלה ומכרטיס הרשת של המכשיר. אם הניסיון נכשל (וזה מה שיקרה במכשירים שמאחורי NAT), פרוטוקול ICE מקבל כתובת חיצונית באמצעות שרת STUN. אם גם הניסיון הזה נכשל, התנועה מנותבת דרך שרת ממסר TURN.

במילים אחרות, שרת STUN משמש לקבלת כתובת רשת חיצונית, ושרתי TURN משמשים להעברת תנועה אם החיבור הישיר (peer-to-peer) נכשל.

כל שרת TURN תומך ב-STUN. שרת TURN הוא שרת STUN עם פונקציונליות מובנית נוספת של ממסר. ‫ICE גם מתמודד עם המורכבות של הגדרות NAT. בפועל, יכול להיות ש-NAT hole-punching ידרוש יותר מכתובת IP:port ציבורית.

כתובות ה-URL של שרתי STUN ו-TURN מצוינות (באופן אופציונלי) על ידי אפליקציית WebRTC באובייקט ההגדרות האישיות iceServers, שהוא הארגומנט הראשון של ה-constructor‏ RTCPeerConnection. ב-appr.tc, הערך הזה נראה כך:

{
  'iceServers': [
    {
      'urls': 'stun:stun.l.google.com:19302'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=udp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    },
    {
      'urls': 'turn:192.158.29.39:3478?transport=tcp',
      'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
      'username': '28224511:1379330808'
    }
  ]
}

אחרי ש-RTCPeerConnection מקבל את המידע הזה, הקסם של ICE קורה באופן אוטומטי. ‫RTCPeerConnection משתמש במסגרת ICE כדי למצוא את הנתיב הטוב ביותר בין עמיתים, ומשתמש בשרתי STUN ו-TURN לפי הצורך.

STUN

NAT מספק למכשיר כתובת IP לשימוש ברשת מקומית פרטית, אבל אי אפשר להשתמש בכתובת הזו מחוץ לרשת. בלי כתובת ציבורית, אין דרך לתקשר בין עמיתי WebRTC. כדי לעקוף את הבעיה הזו, WebRTC משתמש ב-STUN.

שרתי STUN נמצאים באינטרנט הציבורי ויש להם משימה אחת: לבדוק את כתובת ה-IP:port של בקשה נכנסת (מאפליקציה שפועלת מאחורי NAT) ולשלוח את הכתובת הזו בחזרה כתגובה. במילים אחרות, האפליקציה משתמשת בשרת STUN כדי לגלות את כתובת ה-IP והיציאה שלה מנקודת מבט ציבורית. התהליך הזה מאפשר לנקודת קצה ב-WebRTC לקבל כתובת שנגישה באופן ציבורי, ואז להעביר אותה לנקודת קצה אחרת באמצעות מנגנון איתות כדי להגדיר קישור ישיר.

שרתי STUN לא צריכים לעשות הרבה או לזכור הרבה, כך ששרתי STUN עם מפרט נמוך יחסית יכולים לטפל במספר גדול של בקשות.

רוב שיחות ה-WebRTC מתחברות בהצלחה באמצעות STUN, אבל יכול להיות שיהיו פחות שיחות בין עמיתים מאחורי חומות אש והגדרות NAT מורכבות.

חיבור בין עמיתים באמצעות שרת STUN
שימוש בשרתי STUN כדי לקבל כתובות IP ציבוריות:יציאות

פנייה

‫RTCPeerConnection מנסה להגדיר תקשורת ישירה בין עמיתים באמצעות UDP. אם זה לא מצליח, RTCPeerConnection עובר ל-TCP. אם זה לא מצליח, אפשר להשתמש בשרתי TURN כגיבוי, כדי להעביר נתונים בין נקודות קצה.

חשוב להדגיש: שרת TURN משמש להעברת סטרימינג של אודיו, וידאו ונתונים בין עמיתים, ולא להעברת נתוני איתות.

לשרתי TURN יש כתובות ציבוריות, כך שניתן ליצור איתם קשר על ידי עמיתים גם אם העמיתים נמצאים מאחורי חומות אש או שרתי proxy. לשרתי TURN יש משימה אחת: להעביר שידור. בניגוד לשרתי STUN, הם צורכים הרבה רוחב פס.

חיבור בין עמיתים באמצעות שרת STUN
החבילה המלאה: STUN,‏ TURN ואיתות

התרשים הזה מראה איך TURN פועל. השימוש ב-STUN בלבד לא הצליח, ולכן כל אחד מהצדדים משתמש בשרת TURN.

פריסת שרתי STUN ו-TURN

לצורכי בדיקה, Google מפעילה שרת STUN ציבורי, stun.l.google.com:19302, שמשמש את appr.tc. בשירות STUN/TURN בסביבת ייצור, צריך להשתמש ב-rfc5766-turn-server. קוד המקור של שרתי STUN ו-TURN זמין ב-GitHub, שם אפשר גם למצוא קישורים לכמה מקורות מידע על התקנת שרתים. יש גם תמונת VM ל-Amazon Web Services.

שרת TURN חלופי הוא restund, שזמין כקוד מקור וגם ל-AWS. אתם יכולים להגדיר restund ב-Compute :

  1. פותחים את חומת האש לפי הצורך עבור tcp=443, udp/tcp=3478.
  2. יוצרים ארבע מכונות, אחת לכל כתובת IP ציבורית, קובץ אימג' רגיל של Ubuntu 12.06.
  3. מגדירים את חומת האש המקומית (מאפשרים כל דבר מכל מקום).
  4. מתקינים את הכלים: shell sudo apt-get install make sudo apt-get install gcc
  5. מתקינים את libre מהכתובת creytiv.com/re.html.
  6. מורידים את restund מהאתר creytiv.com/restund.html ופורקים את הקובץ.
  7. wget hancke.name/restund-auth.patch ולהחיל באמצעות patch -p1 < restund-auth.patch.
  8. מריצים את make, sudo make install עבור libre ו-restund.
  9. מתאימים את restund.conf לצרכים שלכם (מחליפים את כתובות ה-IP ומוודאים שהקובץ מכיל את אותו סוד משותף) ומעתיקים אל /etc.
  10. מעתיקים את restund/etc/restund אל /etc/init.d/.
  11. מגדירים את restund:
    1. מגדירים את LD_LIBRARY_PATH.
    2. מעתיקים את restund.conf אל /etc/restund.conf.
    3. מגדירים את restund.conf כך שישתמש ב-10 הימני. כתובת IP.
  12. הפעלת restund
  13. בדיקה באמצעות לקוח stund ממכונה מרוחקת: ./client IP:port

‫WebRTC עם כמה משתתפים

כדאי גם לעיין בתקן IETF שהציע ג'סטין אוברטי (Justin Uberti) ל-API בארכיטקטורת REST לגישה לשירותי TURN.

יש הרבה תרחישים לדוגמה של סטרימינג של מדיה שהם לא רק שיחה בין שני אנשים. לדוגמה, שיחת ועידה בווידאו בין קבוצת עמיתים או אירוע ציבורי עם דובר אחד ומאות או מיליוני צופים.

אפליקציית WebRTC יכולה להשתמש בכמה RTCPeerConnections כדי שכל נקודת קצה תתחבר לכל נקודת קצה אחרת בהגדרת רשת. זו הגישה שבה משתמשות אפליקציות כמו talky.io, והיא פועלת בצורה יוצאת מן הכלל עם מספר קטן של עמיתים. מעבר לכך, העיבוד וצריכת רוחב הפס הופכים למופרזים, במיוחד עבור לקוחות בנייד.

רשת: שיחה עם מספר קטן של משתתפים
טופולוגיית Mesh מלאה: כולם מחוברים לכולם

לחלופין, אפליקציית WebRTC יכולה לבחור נקודת קצה אחת להפצת שידורים לכל האחרים בתצורת כוכב. אפשר גם להריץ נקודת קצה של WebRTC בשרת וליצור מנגנון הפצה משלכם (אפליקציית לקוח לדוגמה זמינה ב-webrtc.org).

אפשר להשתמש ב-MediaStream מ-RTCPeerConnection אחד כקלט של אחר. כך אפשר ליצור ארכיטקטורות גמישות יותר, כי אפליקציית אינטרנט יכולה לטפל בניתוב שיחות על ידי בחירה של עמית אחר להתחבר אליו. כדי לראות איך זה עובד, אפשר לעיין בדוגמאות של WebRTC בנושאים ממסר חיבור בין עמיתים וחיבורים מרובים בין עמיתים.

יחידת בקרה מרובת נקודות (MCU)

אם יש מספר גדול של נקודות קצה, עדיף להשתמש ביחידת בקרה מרובת נקודות (MCU). זהו שרת שמשמש כגשר להפצת מדיה בין מספר גדול של משתתפים. יחידות MCU יכולות להתמודד עם רזולוציות, קודקים וקצבי פריימים שונים בשיחת ועידה בווידאו, לטפל בהמרת קידוד, לבצע העברת סטרימינג סלקטיבית ולערבב או להקליט אודיו ווידאו. בשיחות עם כמה משתתפים, יש כמה דברים שצריך להביא בחשבון, במיוחד איך להציג כמה קלט וידאו ולשלב אודיו מכמה מקורות. גם פלטפורמות ענן, כמו vLine, מנסות לבצע אופטימיזציה של ניתוב התנועה.

אפשר לקנות חבילת חומרה שלמה של MCU או להרכיב חבילה משלכם.

מבט אחורי על Cisco MCU5300
החלק האחורי של Cisco MCU

יש כמה אפשרויות לתוכנות MCU בקוד פתוח. לדוגמה, Licode (לשעבר Lynckia) מייצרת MCU בקוד פתוח ל-WebRTC. ל-OpenTok יש את Mantis.

מעבר לדפדפנים: VoIP, טלפונים והודעות

האופי הסטנדרטי של WebRTC מאפשר ליצור תקשורת בין אפליקציית WebRTC שפועלת בדפדפן לבין מכשיר או פלטפורמה שפועלים בפלטפורמת תקשורת אחרת, כמו טלפון או מערכת לשיחות ועידה בווידאו.

‫SIP הוא פרוטוקול איתות שמשמש מערכות VoIP ומערכות של שיחות ועידה בווידאו. כדי לאפשר תקשורת בין אפליקציית אינטרנט של WebRTC לבין לקוח SIP, כמו מערכת לשיחות ועידה בווידאו, WebRTC צריך שרת proxy שיעביר את האותות. האותות חייבים לעבור דרך השער, אבל אחרי שהתקשורת נוצרת, תנועת ה-SRTP (וידאו ואודיו) יכולה לעבור בין משתמשים.

מערכת הטלפוניה העולמית (PSTN) היא רשת מיתוג המעגלים של כל הטלפונים האנלוגיים "הישנים והטובים". כדי לבצע שיחות בין אפליקציות אינטרנט של WebRTC לבין טלפונים, תעבורת הנתונים צריכה לעבור דרך שער PSTN. באופן דומה, אפליקציות אינטרנט של WebRTC צריכות שרת XMPP מתווך כדי לתקשר עם נקודות קצה (endpoints) של Jingle, כמו לקוחות IM. ‫Jingle פותח על ידי Google כתוסף ל-XMPP כדי לאפשר שיחות קוליות ושיחות וידאו בשירותי הודעות. ההטמעות הנוכחיות של WebRTC מבוססות על ספריית libjingle ב-C++‎, הטמעה של Jingle שפותחה במקור ל-Talk.

יש הרבה אפליקציות, ספריות ופלטפורמות שמשתמשות ביכולת של WebRTC לתקשר עם העולם החיצון. לדוגמה:

  • ‫jsSIP: ספריית JavaScript SIP
  • Phono: ממשק API לטלפון בקוד פתוח שנבנה כתוסף
  • ‫Twilio: קול והודעות

מידע נוסף

בספר בן 350 העמודים WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web יש הרבה פרטים על נתיבי נתונים ואותות, וגם כמה תרשימים מפורטים של טופולוגיית הרשת.