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

מהו סימון?

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

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

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

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

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

בנוסף, הארכיטקטורה של 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

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

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

RTCPeerConnection API ואיתות: Offer, answer ו-candidate

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

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

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

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

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

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

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

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

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

קידוד 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 להחליף מטא-נתונים באמצעות כל שירות הודעות שירצו, כמו צ'אט, אימייל או יונת דואר.

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

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

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

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

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

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

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

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

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

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

איתות של שינוי קנה מידה

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

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

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

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

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

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

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

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

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

בדוגמה הזו אין WebRTC. הוא נועד רק להראות איך משלבים איתות באפליקציית אינטרנט. אפשר לראות את יומן המסוף כדי להבין מה קורה כשלקוחות מצטרפים לחדר ומחליפים הודעות. ב-WebRTC codelab מוסבר איך לשלב את זה באפליקציה מלאה לשיחות וידאו ב-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-static בשביל זה. במקרה, הוא משמש בדוגמה הזו).

כדי להריץ את האפליקציה הזו ב-localhost, צריך להתקין את Node,‏ Socket.IO ו-node-static. אפשר להוריד את Node מ-Node.js (ההתקנה פשוטה ומהירה). כדי להתקין את Socket.IO ואת node-static, מריצים את 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).

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

נקודות חשובות לגבי איתות

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

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

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

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

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

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

אבטחת האיתות

"אבטחה היא האומנות של אי-התרחשות של שום דבר".

סלמאן רושדי

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

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

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

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

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

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

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

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

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

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

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

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

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

כתובות ה-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 לפי הצורך.

STUN

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

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

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

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

חיבור ישיר (Peer to peer) באמצעות שרת STUN
שימוש בשרתי STUN כדי לקבל כתובות IP:יציאה ציבוריות

TURN

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

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

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

חיבור ישיר (Peer to peer) באמצעות שרת 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, שם אפשר למצוא גם קישורים לכמה מקורות מידע על התקנת שרתים. יש גם תמונת מכונה וירטואלית ל-Amazon Web Services.

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

  1. פותחים את חומת האש לפי הצורך עבור tcp=443, ‏ udp/tcp=3478.
  2. יוצרים ארבעה מקרים, אחד לכל כתובת IP ציבורית, תמונת Standard 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 המוצע של ג'סטין אוברטי בנושא REST API לגישה לשירותי TURN.

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

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

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

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

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

יחידת בקרה לכמה מכשירים

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

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

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

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

למידע נוסף

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

מצגת WebRTC מ-Google I/O משנת 2013 עם Justin Uberti, מוביל טכנולוגיית WebRTC

מצגת SFHTML5 של Chris Wilson – מבוא לאפליקציות WebRTC

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

WebRTC and Signaling: What Two Years Has Taught Us – פוסט בבלוג של TokBox שמסביר למה הייתה זו החלטה טובה להשאיר את האיתות מחוץ למפרט

בספר A Practical Guide to Building WebRTC Apps של Ben Strong יש מידע רב על טופולוגיות ותשתית של WebRTC.

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