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

מה זה איתות?

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

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

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

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

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

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

תרשים ארכיטקטורה של JSEP
ארכיטקטורת JSEP

כדי להשתמש ב-JSEP צריך להמיר בין אפליקציות להשוואה של offer (הצעה) ו-answer (תשובה) – המטא-נתונים של המדיה שהוזכרו למעלה. המבצעים והתשובות מועברים בפורמט 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
…

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

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

ממשק API ואותות של RTCPeerConnection: הצעה, תשובה ומועמד

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

כדי לאתחל את התהליך הזה, יש ל-RTCPeerConnection שתי משימות:

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

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

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

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

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

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

JSEP תומך בTricking 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 ו-BrowserMeet, אפשר להזמין אנשים לשיחה על ידי שיתוף של קישור מותאם אישית. המפתח כריס כדור (Chris Ball) יצר ניסוי מסקרן serverless-webrtc שמאפשר למשתתפי שיחות WebRTC להעביר מטא-נתונים באמצעות כל שירות העברת הודעות שהם אוהבים, כמו הודעות מיידיות, אימייל או 'יונה'.

איך אפשר לבנות שירות אותות?

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

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

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

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

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

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

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

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

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

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

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

  • eXtensible Messaging and Presence Protocol (XMPP), שנקרא במקור פרוטוקול Jabber-a שפותח להעברת הודעות מיידיות, שיכול לשמש לאיתות (הטמעות של שרת כוללות את ejabberd ו-Openfire. לקוחות JavaScript, כגון Strophe.js, משתמשים ב-BOSH כדי לבצע אמולציה של סטרימינג דו-כיווני, אך מסיבות שונות, ייתכן ש-BOSH לא יהיה יעיל באותה מידה כמו WebSocket, ומאותן סיבות, ייתכן שקנה המידה שלו לא יהיה תקין). (בטנגנס, Jingle הוא תוסף XMPP להפעלת וידאו וקול. הפרויקט של WebRTC משתמש ברכיבי רשת והעברה מספריית libjingle – יישום C++ של Jingle.)

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

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

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

(המדריך לטכנולוגיות אינטרנט בזמן אמת (Real-Time Web Technologies) של המפתח Phil Leggetter מספק רשימה מקיפה של שירותים וספריות להעברת הודעות.)

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

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

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

בדוגמה הזו אין 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);

  });

});

(לא צריך ללמוד על צומת-סטטי לשם כך. פשוט משתמשים בו בדוגמה הזו).

כדי להריץ את האפליקציה הזו ב-localhost, צריך להתקין את Node, Socket.IO ו-node-static. ניתן להוריד את הצומת מ-Node.js (ההתקנה פשוטה ומהירה). כדי להתקין את Socket.IO ואת הצומת-סטטי, מריצים את Node Package Manager מטרמינל בספריית האפליקציות:

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).

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

אותות אותות של Nestchas

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

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

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

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

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

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

אבטחת האות

"אבטחה היא האמנות שלא לעשות כלום".

סלמן רורדי

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

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

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

לאחר איתות: משתמשים ב-ICE להתמודדות עם NATs וחומות אש

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

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

חיבור פשוט מקצה לקצה
עולם ללא NAT וחומות אש

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

אפליקציות להשוואה מאחורי חומות אש ו-NAT
העולם האמיתי

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

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

במילים אחרות, שרת STUN משמש לקבלת כתובת רשת חיצונית, ושרתים להפוך משמשים להעברת תעבורת נתונים במקרה של כישלון בחיבור ישיר (מקצה לקצה).

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

כתובות URL עבור STUN ו/או שרתי TURN מצוינות (אופציונלי) על ידי אפליקציית WebRTC באובייקט התצורה iceServers, שהוא הארגומנט הראשון של הבנאי 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 שרתים לפי הצורך.

הלם

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

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

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

רוב הקריאות ל-WebRTC מבצעות חיבור באמצעות STUN – 86% לפי Webrtcstats.com, אם כי שיחות בין רשתות להשוואה מאחורי חומות אש והגדרות NAT מורכבות יכולות להיות פחות מכך.

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

החלפה

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

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

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

חיבור עמית לעמית באמצעות שרת STUN
חוויית המשתמש המלאה של מונטי: STUN, TURN ואותות

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

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

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

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

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

מעבר לאחד לאחד: WebRTC למשתמשים מרובים

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

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

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

רשת: שיחת N-way קטנה
טופולוגיה מלאה של רשת: כולם מחוברים לכולם

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

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

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

אפשרות טובה יותר למספר גדול של נקודות קצה היא להשתמש ביחידת בקרה מרובת נקודות (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 מתווך כדי לתקשר עם נקודות קצה של Jingle, כמו לקוחות IM. Jingle פותחה על ידי Google כהרחבה ל-XMPP כדי לאפשר שימוש בקול ובווידאו לשירותי העברת הודעות. היישומים הנוכחיים של WebRTC מבוססים על ספריית libjingle ב-C++, יישום של Jingle שפותח במקור עבור Talk.

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

  • sipML5: לקוח JavaScript SIP בקוד פתוח
  • jsSIP: ספריית JavaScript SIP
  • Phono: ממשק API לטלפון JavaScript בקוד פתוח, שפותח כפלאגין
  • Zingaya: ווידג'ט טלפון שניתן להטמיע
  • Twilio: שיחות קוליות והעברת הודעות
  • Uberconference: שיחת ועידה

מפתחי sipML5 בנו גם את השער webrtc2sip. Tethr ו-Tropo הדגימו מסגרת לתקשורת במקרי חירום "בתיק מסמכים" באמצעות תא OpenBTS כדי לאפשר תקשורת בין טלפונים ניידים פשוטים למחשבים באמצעות WebRTC. מדובר בתקשורת טלפונית בלי ספק.

מידע נוסף

ב-codelab ניתן למצוא הוראות מפורטות לבניית אפליקציית וידאו צ'אט וטקסט באמצעות שירות איתות מסוג Socket.io שפועל ב-Node.

הצגת WebRTC של Google I/O משנת 2013 עם מנהל טכני של WebRTC, ג'סטין אורטי

מצגת SFHTML5 של כריס ווילסון - מבוא לאפליקציות WebRTC

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

WebRTC ואותות: מה למדנו בשנתיים – פוסט בבלוג של TokBox שמסביר למה כדאי להשאיר את האיתות מחוץ למפרט

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

הפרק של WebRTC ב-High Performance Browser Networking של Ilya Graigorik מתעמק בארכיטקטורת WebRTC, בתרחישים לדוגמה ובביצועים.

אלא אם צוין אחרת, התוכן של דף זה הוא ברישיון Creative Commons Attribution 4.0 ודוגמאות הקוד הן ברישיון Apache 2.0. לפרטים, ניתן לעיין במדיניות האתר Google Developers‏.‏ Java הוא סימן מסחרי רשום של חברת Oracle ו/או של השותפים העצמאיים שלה.

עדכון אחרון: 2013-11-04 (שעון UTC).