מהי איתות?
איתות הוא תהליך תיאום התקשורת. כדי שאפליקציית WebRTC תוכל להגדיר שיחה, הלקוחות שלה צריכים להחליף את הפרטים הבאים:
- הודעות לבקרת סשן שמשמשות לפתיחה או לסגירה של התקשורת
- הודעות שגיאה
- מטא-נתונים של מדיה, כמו קודיקים, הגדרות קודיקים, רוחב פס וסוגים של מדיה
- נתוני מפתחות המשמשים ליצירת חיבורים מאובטחים
- נתוני רשת, כמו כתובת ה-IP והיציאה של מארח כפי שהם נראים לעולם החיצון
תהליך האותות הזה צריך דרך ללקוחות להעביר הודעות הלוך ושוב. המנגנון הזה לא מוטמע בממשקי ה-API של WebRTC. צריך לבנות אותו בעצמכם. בהמשך המאמר נסביר איך ליצור שירות איתות. אבל קודם כול, צריך קצת הקשר.
למה האותות לא מוגדרים על ידי WebRTC?
כדי למנוע יתירות ולשפר את התאימות לטכנולוגיות קיימות, שיטות האותות והפרוטוקולים לא מצוינים בתקני WebRTC. הגישה הזו מתוארת בפרוטוקול ליצירת סשנים של JavaScript (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
…
רוצים לדעת מה המשמעות של כל ה-mumbo jumbo הזה של SDP? כדאי לעיין בדוגמאות של ארגון התקינה בנושאי האינטרנט (IETF).
חשוב לזכור ש-WebRTC תוכנן כך שאפשר לשנות את ההצעה או התשובה לפני שמגדירים אותן כתיאור המקומי או המרוחק, על ידי עריכת הערכים בטקסט ה-SDP. לדוגמה, אפשר להשתמש בפונקציה preferAudioCodec()
ב-appr.tc כדי להגדיר את ה-codec ואת קצב הנתונים שמוגדרים כברירת מחדל. קשה יחסית לבצע מניפולציות על SDP באמצעות JavaScript, ויש דיון לגבי האפשרות שגרסאות עתידיות של WebRTC ישתמשו ב-JSON במקום זאת. עם זאת, יש כמה יתרונות להישארות עם SDP.
RTCPeerConnection
API ואיתות: הצעה, תשובה ומועמד
RTCPeerConnection
הוא ממשק ה-API שבו משתמשות אפליקציות WebRTC כדי ליצור חיבור בין משתמשים ולתקשר אודיו ווידאו.
כדי לאתחל את התהליך הזה, ל-RTCPeerConnection
יש שתי משימות:
- בודקים את תנאי המדיה המקומיים, כמו רזולוציה ויכולות של קודק. אלה המטא-נתונים שמשמשים למנגנון של הצעה ותשובה.
- אחזור כתובות רשת פוטנציאליות למארח של האפליקציה, שנקראות מועמדים.
אחרי שמאשרים את הנתונים המקומיים האלה, צריך להחליף אותם עם השותף המרוחק באמצעות מנגנון איתות.
נניח שאילנה מנסה להתקשר לאווה. זהו מנגנון ההצעה/התשובה המלא, בכל הפרטים המפורטים:
- עינת יוצרת אובייקט
RTCPeerConnection
. - אליס יוצרת הצעה (תיאור סשן SDP) באמצעות השיטה
RTCPeerConnection
createOffer()
. - עינת מתקשרת למספר
setLocalDescription()
עם המבצע שלה. - עינת ממירה את המבצע למחרוזת ומשתמשת במנגנון איתות כדי לשלוח אותו לאווה.
- נעמה מתקשרת אל
setRemoteDescription()
עם ההצעה של עינת, כדי ש-RTCPeerConnection
שלה יידע על ההגדרה של עינת. - נעמה קוראת ל-
createAnswer()
, והקריאה החוזרת (callback) לאחר הצלחה מועבר אליה תיאור של הסשן המקומי – התשובה של נעמה. - נעמה מגדירה את התשובה שלה כתיאור המקומי באמצעות קריאה ל-
setLocalDescription()
. - לאחר מכן, נעמה משתמשת במנגנון האותות כדי לשלוח את התשובה שלה כמחרוזת לאליס.
- עינת מגדירה את התשובה של נעמה כתיאור של הסשן מרחוק באמצעות
setRemoteDescription()
.
עינת וחנה צריכות גם להחליף פרטי רשת. הביטוי 'חיפוש מועמדים' מתייחס לתהליך של חיפוש ממשקי רשת ויציאות באמצעות מסגרת ICE.
- עינת יוצרת אובייקט
RTCPeerConnection
עם טיפולonicecandidate
. - הטיפול נקרא כשמועמדים לרשתות זמינים.
- בטיפול, עינת שולחת לנעמה את נתוני המועמדים כמחרוזות דרך ערוץ האותות שלהן.
- כשאיווה מקבלת הודעה על מועמדת מאיליס, היא קוראת לפונקציה
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 ולחפש ביומן המסוף דוגמה לשיחה בווידאו בדף אחד. אם אתם רוצים לקבל מידע נוסף, אתם יכולים להוריד דמפ מלא של האותות והנתונים הסטטיסטיים של WebRTC מהדף about://webrtc-internals ב-Google Chrome או מהדף opera://webrtc-internals ב-Opera.
גילוי עמיתים
זוהי דרך 'מתוחכמת' לשאול "איך מוצאים מישהו לדבר איתו?"
לשיחות טלפון, יש לכם מספרי טלפון וספריות. כדי לנהל צ'אטים בווידאו ובכתב אונליין, אתם צריכים מערכות לניהול זהויות ונוכחות, וגם דרך למשתמשים להתחיל סשנים. אפליקציות WebRTC צריכות דרך שבה לקוחות יוכלו לאותת זה לזה שהם רוצים להתחיל שיחה או להצטרף אליה.
מנגנוני גילוי עמיתים לא מוגדרים על ידי WebRTC, ולא נרחיב על האפשרויות כאן. התהליך יכול להיות פשוט כמו שליחת כתובת URL באימייל או בהודעה. באפליקציות של שיחות וידאו, כמו Talky, tawk.to ו-Browser Meeting, אפשר להזמין אנשים לשיחה על ידי שיתוף קישור מותאם אישית. המפתח כריס בול (Chris Ball) יצר ניסוי מעניין בנושא serverless-webrtc שמאפשר למשתתפים בשיחות WebRTC להחליף מטא-נתונים באמצעות כל שירות העברת הודעות שהם רוצים, כמו צ'אט, אימייל או יוני דואר.
איך אפשר ליצור שירות איתות?
שוב, הפרוטוקולים והמנגנונים של האותות לא מוגדרים לפי תקני WebRTC. בכל מקרה, צריך שרת ביניים כדי להחליף הודעות איתות ונתוני אפליקציות בין לקוחות. לצערנו, אפליקציית אינטרנט לא יכולה פשוט לצעוק באינטרנט "צריך לחבר אותי לחבר שלי".
למרבה המזל, הודעות האותת הן קטנות, והן מועברות בעיקר בתחילת השיחה. בבדיקה עם appr.tc לסשן של שיחת וידאו, שירות האותות טיפל ב-30 עד 45 הודעות בסך הכול, בגודל כולל של כ-10KB.
בנוסף לכך שהם לא דורשים הרבה ברוחב פס, שירותי האותות של WebRTC לא צורכים הרבה עיבוד או זיכרון, כי הם צריכים רק להעביר הודעות ולשמור כמות קטנה של נתוני מצב סשן, כמו אילו לקוחות מחוברים.
דחיפת הודעות מהשרת ללקוח
שירות שליחת הודעות לסימון צריך להיות דו-כיווני: מהלקוח לשרת ומהשרת ללקוח. תקשורת דו-כיוונית מנוגדת למודל הבקשה/תגובה של שרת/לקוח ב-HTTP, אבל במשך שנים רבות פותחו שיטות שונות, כמו Long Polling, כדי לדחוף נתונים משירות שפועל בשרת אינטרנט לאפליקציית אינטרנט שפועלת בדפדפן.
לאחרונה, הוטמע באופן נרחב EventSource
API. כך אפשר לשלוח אירועים מהשרת – נתונים שנשלחים משרת אינטרנט ללקוח דפדפן דרך HTTP. EventSource
מיועד להעברת הודעות בכיוון אחד, אבל אפשר להשתמש בו בשילוב עם XHR כדי ליצור שירות להחלפת הודעות איתות. שירות איתות מעביר הודעה מבעל קריאה נכנסת, שנשלחת באמצעות בקשת XHR, על ידי דחיפה שלה דרך EventSource
לנמען הקריאה.
WebSocket הוא פתרון טבעי יותר, שמיועד לתקשורת דו-כיוונית מלאה בין שרת ללקוח – הודעות שיכולות לזרום בשני הכיוונים בו-זמנית. אחד היתרונות של שירות איתות שנוצר באמצעות WebSocket או אירועים שנשלחים מהשרת (EventSource
) הוא שאפשר להטמיע את הקצה העורפי של ממשקי ה-API האלה במגוון של מסגרות אינטרנט שקיימות ברוב חבילות האירוח באינטרנט בשפות כמו PHP, Python ו-Ruby.
כל הדפדפנים המודרניים, מלבד Opera Mini, תומכים ב-WebSocket. חשוב יותר, כל הדפדפנים שתומכים ב-WebRTC תומכים גם ב-WebSocket, גם במחשבים וגם בניידים. צריך להשתמש ב-TLS בכל החיבורים כדי לוודא שלא ניתן יהיה ליירט הודעות ללא הצפנה, וגם כדי להפחית בעיות במעבר דרך שרת proxy. (מידע נוסף על WebSocket ועל ניתוב דרך שרת proxy זמין בפרק WebRTC בספר High Performance Browser Networking של Ilya Grigorik).
אפשר גם לטפל בסימון על ידי כך שמשתמשי WebRTC יבדקו שוב ושוב שרת שליחת הודעות דרך Ajax, אבל זה מוביל להרבה בקשות רשת מיותרות, וזה בעייתי במיוחד במכשירים ניידים. גם אחרי שהסשן הוקם, השותפים צריכים לבצע סקרים כדי לבדוק אם יש הודעות איתות במקרה של שינויים או סיום הסשן על ידי שותפים אחרים. בדוגמה של האפליקציה WebRTC Book נעשה שימוש באפשרות הזו עם אופטימיזציות מסוימות לתדירות הסקרים.
איתות בקנה מידה
שירות איתות צורך כמות קטנה יחסית של רוחב פס ועיבוד מרכזי לכל לקוח, אבל שרתי איתות של אפליקציה פופולרית עשויים להידרש לטפל בהרבה הודעות ממיקומים שונים עם רמות גבוהות של בו-זמניות. אפליקציות WebRTC שמקבלות הרבה תנועה זקוקות לשרתים של איתותים שיכולים לעמוד בעומס משמעותי. אין צורך להיכנס לפרטים, אבל יש כמה אפשרויות לשליחת הודעות בכמות גדולה וביצועים גבוהים, כולל:
פרוטוקול הודעות ונוכחות שניתן להרחבה (XMPP), שנקרא במקור Jabber – פרוטוקול שפותח להודעות מיידיות שאפשר להשתמש בו לסימון (הטמעות שרת כוללות את ejabberd ואת Openfire. לקוחות JavaScript, כמו Strophe.js, משתמשים ב-BOSH כדי לדמות סטרימינג דו-כיווני, אבל מסיבות שונות, יכול להיות ש-BOSH לא יעיל כמו WebSocket, ויכול להיות שגם לא יתאים להתאמה לעומס (scaling) מסיבות דומות.) (לידיעתכם, 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
(במדריך Real-Time Web Technologies של המפתח Phil Leggetter יש רשימה מקיפה של ספריות ושירותי שליחת הודעות).
פיתוח שירות איתות באמצעות Socket.io ב-Node
הקוד הבא הוא קוד של אפליקציית אינטרנט פשוטה שמשתמשת בשירות איתות שנוצר באמצעות Socket.io ב-Node. העיצוב של Socket.io מאפשר ליצור בקלות שירות להעברת הודעות, ו-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-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. הקוד הזה כבר לא רלוונטי, אבל כדאי לעיין בו אם אתם שוקלים לבצע משהו דומה.
אבטחת אותות
"אבטחה היא האמנות של מניעת אירועים".
סלמן רושדי (Salman Rushdie)
הצפנה היא חובה לכל רכיבי WebRTC.
עם זאת, מנגנוני האותות לא מוגדרים לפי תקני WebRTC, ולכן אתם אחראים על אבטחת האותות. אם תוקף מצליח לחטוף את האותות, הוא יכול להפסיק סשנים, להפנות חיבורים ולהקליט, לשנות או להחדיר תוכן.
הגורם החשוב ביותר לאבטחת האותות הוא שימוש בפרוטוקולים מאובטחים – HTTPS ו-WSS (למשל, TLS) – שמבטיחים שלא ניתן יהיה ליירט הודעות ללא הצפנה. בנוסף, חשוב להיזהר שלא לשדר הודעות איתות באופן שגורם לאנשים אחרים שקוראים לשרת האותות הזה לגשת להן.
אחרי האות: שימוש ב-ICE כדי להתמודד עם NAT וחומות אש
לאיתור מטא-נתונים, אפליקציות WebRTC משתמשות בשרת ביניים, אבל בסטרימינג של מדיה ונתונים בפועל, אחרי שהסשן נוצר, RTCPeerConnection
מנסה לחבר לקוחות ישירות או בצורה peer-to-peer.
בעולם פשוט יותר, לכל נקודת קצה של WebRTC תהיה כתובת ייחודית שאפשר להחליף עם עמיתים אחרים כדי לתקשר ישירות.
במציאות, רוב המכשירים נמצאים מאחורי שכבה אחת או יותר של NAT, בחלק מהם מותקנת תוכנת אנטי-וירוס שחוסמת יציאות ופרוטוקולים מסוימים, ורבים מהם נמצאים מאחורי שרתים proxy וחומות אש ארגוניות. למעשה, חומת אש ו-NAT עשויים להיות מיושמים על ידי אותו מכשיר, כמו נתב Wi-Fi ביתי.
אפליקציות WebRTC יכולות להשתמש במסגרת ICE כדי להתגבר על המורכבות של רשתות בעולם האמיתי. כדי שזה יקרה, האפליקציה צריכה להעביר את כתובות ה-URL של שרתי ה-ICE אל RTCPeerConnection
, כפי שמתואר במאמר הזה.
ICE מנסה למצוא את הנתיב הטוב ביותר לחיבור בין משתמשים. הוא מנסה את כל האפשרויות במקביל ובוחר את האפשרות היעילה ביותר שפועלת. ה-ICE מנסה קודם ליצור חיבור באמצעות כתובת המארח שהתקבלה ממערכת ההפעלה ומכרטיס הרשת של המכשיר. אם הניסיון הזה נכשל (והוא נכשל במכשירים שמאחורי NAT), ה-ICE מקבל כתובת חיצונית באמצעות שרת STUN. אם הניסיון הזה נכשל, התנועה מנותבת דרך שרת ממסר TURN.
במילים אחרות, שרת STUN משמש לקבלת כתובת רשת חיצונית, ושרתי TURN משמשים להעברת תנועה אם החיבור הישיר (מקצה לקצה) נכשל.
כל שרת TURN תומך ב-STUN. שרת TURN הוא שרת STUN עם פונקציונליות מובנית נוספת של העברה (relay). בנוסף, ICE מתמודד עם המורכבות של הגדרות NAT. בפועל, יכול להיות שיידרש יותר מכתובת IP ציבורית:יציאה כדי לבצע חור ב-NAT.
כתובות 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 לקבל כתובת נגישה לכולם, ולאחר מכן להעביר אותה לשותף אחר באמצעות מנגנון איתות כדי להגדיר קישור ישיר. (בפועל, שרתי NAT שונים פועלים בדרכים שונות ויכולות להיות כמה שכבות NAT, אבל העיקרון נשאר זהה).
שרתי STUN לא צריכים לבצע פעולות רבות או לזכור הרבה, ולכן שרתי STUN עם מפרט נמוך יחסית יכולים לטפל במספר גדול של בקשות.
רוב השיחות ב-WebRTC יוצרות חיבור באמצעות STUN – 86% לפי Webrtcstats.com, אבל שיעור ההצלחה יכול להיות נמוך יותר בשיחות בין עמיתים מאחורי חומות אש והגדרות NAT מורכבות.
TURN
RTCPeerConnection
מנסה להגדיר תקשורת ישירה בין עמיתים באמצעות UDP. אם הניסיון הזה נכשל, RTCPeerConnection
עובר ל-TCP. אם הניסיון הזה נכשל, אפשר להשתמש בשרתי TURN כחלופה, להעברת נתונים בין נקודות קצה.
רק רצינו להדגיש: ה-TURN משמש להעברת אודיו, וידאו ונתונים בסטרימינג בין שווים, ולא לנתוני איתות.
לשרתים של TURN יש כתובות ציבוריות, כך ששותפים יכולים ליצור איתם קשר גם אם הם מאחורי חומות אש או שרתים proxy. הרעיון של שרתי TURN הוא פשוט – להעביר סטרימינג. עם זאת, בניגוד לשרתים מסוג 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. בהוראות הבאות מוסבר איך מגדירים החזר כספי ב-Compute Engine.
- פותחים את חומת האש לפי הצורך עבור tcp=443, udp/tcp=3478.
- יוצרים ארבע מכונות, אחת לכל כתובת IP ציבורית, עם קובץ אימג' רגיל של Ubuntu 12.06.
- מגדירים את תצורת חומת האש המקומית (מתן הרשאה לכל התחברות מכל מקום).
- התקנת כלים:
shell sudo apt-get install make sudo apt-get install gcc
- מתקינים את libre מ-creytiv.com/re.html.
- מאחזרים את restund מ-creytiv.com/restund.html ומפרקים אותו./
wget
hancke.name/restund-auth.patch ומחילים באמצעותpatch -p1 < restund-auth.patch
.- מריצים את
make
,sudo make install
עבור libre ו-restund. - משנים את
restund.conf
בהתאם לצרכים שלכם (מחליפים את כתובות ה-IP ומוודאים שהוא מכיל את אותו סוד שיתוף) ומעתיקים אותו אל/etc
. - מעתיקים את
restund/etc/restund
אל/etc/init.d/
. - הגדרת restund:
- מגדירים את
LD_LIBRARY_PATH
. - מעתיקים את
restund.conf
אל/etc/restund.conf
. - מגדירים את
restund.conf
כך שישתמש ב-10 הנכון. כתובת IP.
- מגדירים את
- הפעלת restund
- בדיקה באמצעות לקוח STUN ממחשב מרוחק:
./client IP:port
מעבר לשיחות אחד על אחד: WebRTC עם מספר משתתפים
מומלץ גם לעיין בהצעה של Justin Uberti לתקן IETF ל-API בארכיטקטורת REST לגישה לשירותי TURN.
קל לדמיין תרחישים לדוגמה של סטרימינג של מדיה, שמעבר לשיחה פשוטה בארבע עיניים. לדוגמה, שיחת ועידה בווידאו בין קבוצת עמיתים או אירוע ציבורי עם דובר אחד ומאות או מיליוני צופים.
אפליקציית WebRTC יכולה להשתמש בכמה RTCPeerConnections כדי שכל נקודת קצה תתחבר לכל נקודת קצה אחרת בהגדרת רשתות רשתות (mesh). זו הגישה של אפליקציות כמו talky.io, והיא עובדת מצוין עבור קבוצה קטנה של עמיתים. מעבר לכך, צריכת העיבוד והרוחב הפס הופכת למוגזמת, במיוחד אצל לקוחות בנייד.
לחלופין, אפליקציית WebRTC יכולה לבחור נקודת קצה אחת כדי להפיץ את הסטרימינג לכל הנקודות האחרות בהגדרה של כוכב. אפשר גם להריץ נקודת קצה (endpoint) של WebRTC בשרת ולבנות מנגנון הפצה משלכם (אפליקציית לקוח לדוגמה זמינה ב-webrtc.org).
החל מגרסה 31 של Chrome וגרסה 18 של Opera, אפשר להשתמש ב-MediaStream
מ-RTCPeerConnection
אחד כקלט ל-RTCPeerConnection
אחר. כך אפשר ליצור ארכיטקטורות גמישות יותר, כי אפליקציית אינטרנט יכולה לטפל בחיבור השיחות על ידי בחירה של השותף הנוסף שאליו היא תתחבר. כדי לראות את זה בפעולה, אפשר לעיין בדוגמאות ל-WebRTC: העברה של חיבור בין שווים ובדוגמאות ל-WebRTC: מספר חיבורים בין שווים.
יחידת בקרה עם תמיכה בכמה מכשירים
אם יש לכם מספר גדול של נקודות קצה, עדיף להשתמש ביחידת בקרה מרובה נקודות (MCU). זהו שרת שמשמש כגשר להפצת מדיה בין מספר גדול של משתתפים. מערכי MCU יכולים להתמודד עם רזולוציות, קודקים וקצב פריימים שונים בווידאו ועידה, לטפל בהמרת קוד, לבצע העברה סלקטיבית של סטרימינג ולערבב או להקליט אודיו ווידאו. בשיחות עם מספר משתתפים, יש כמה בעיות שצריך לקחת בחשבון, במיוחד איך להציג כמה מקורות וידאו ולערבב אודיו ממספר מקורות. פלטפורמות ענן, כמו vLine, גם מנסים לבצע אופטימיזציה של ניתוב התנועה.
אפשר לקנות חבילת חומרה מלאה של MCU או ליצור חבילת חומרה משלכם.
יש כמה אפשרויות לתוכנות MCU בקוד פתוח. לדוגמה, Licode (לשעבר Lynckia) מייצרת MCU בקוד פתוח ל-WebRTC. ב-OpenTok יש את Mantis.
מעבר לדפדפנים: VoIP, טלפונים והודעות
הטבע הסטנדרטי של WebRTC מאפשר ליצור תקשורת בין אפליקציית WebRTC שפועלת בדפדפן לבין מכשיר או פלטפורמה שפועלים בפלטפורמת תקשורת אחרת, כמו טלפון או מערכת של כנס וידאו.
SIP הוא פרוטוקול איתותים שמשמש מערכות VoIP ומערכות לשיחות ועידה בווידאו. כדי לאפשר תקשורת בין אפליקציית אינטרנט של WebRTC לבין לקוח SIP, כמו מערכת של כנס וידאו, ל-WebRTC נדרש שרת proxy שיתווך את האותות. האותות חייבים לעבור דרך השער, אבל אחרי שהתקשורת מופעלת, תעבורת הנתונים של SRTP (ווידאו ואודיו) יכולה לעבור ישירות מקצה לקצה.
מערכת הטלפוניה העולמית (PSTN) היא הרשת המחוברת בכבלים של כל הטלפונים האנלוגיים הרגילים. בשיחות בין אפליקציות אינטרנט של WebRTC לבין טלפונים, התנועה חייבת לעבור דרך שער PSTN. באופן דומה, אפליקציות אינטרנט של WebRTC זקוקות לשרת XMPP ביניים כדי לתקשר עם נקודות קצה של Jingle, כמו לקוחות של הודעות מיידיות. Google פיתחה את Jingle כתוסף ל-XMPP כדי לאפשר שיחות קוליות ושיחות וידאו בשירותי העברת הודעות. ההטמעות הנוכחיות של WebRTC מבוססות על הספרייה libjingle ב-C++, הטמעה של Jingle שפותחה במקור ל-Talk.
מספר אפליקציות, ספריות ופלטפורמות משתמשות ביכולת של WebRTC לתקשר עם העולם החיצון:
- sipML5: לקוח SIP ב-JavaScript בקוד פתוח
- jsSIP: ספריית SIP ב-JavaScript
- Phono: ממשק API של טלפון ב-JavaScript בקוד פתוח שנוצר כפלאגין
- Zingaya: ווידג'ט לנייד שניתן להטמיע
- Twilio: קול והודעות
- Uberconference: שיחת ועידה
מפתחי sipML5 יצרו גם את השער webrtc2sip. Tethr ו-Tropo הדגימו מסגרת לתקשורת במקרי אסון "בתיקי גב" באמצעות תא OpenBTS כדי לאפשר תקשורת בין טלפונים רגילים למחשבים באמצעות WebRTC. זו תקשורת טלפונית ללא ספק!
למידע נוסף
בcodelab של WebRTC מפורטות הוראות מפורטות ליצירת אפליקציית וידאו וצ'אט טקסט באמצעות שירות איתות של Socket.io שפועל ב-Node.
הצגת WebRTC ב-Google I/O מ-2013 עם מנהל הטכנולוגיה של WebRTC, ג'סטין אוברטי (Justin Uberti)
ההרצאה של Chris Wilson ב-SFHTML5 – מבוא לאפליקציות WebRTC
בספר בן 350 הדפים WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web יש הרבה פרטים על נתונים ונתיבי איתות, והוא כולל מספר תרשימים מפורטים של טופולוגיית רשתות.
WebRTC ו-Signaling: מה למדנו במשך שנתיים – פוסט בבלוג של TokBox על הסיבה לכך שהחלטנו לא לכלול את האותת במפרט
המדריך המעשי ליצירת אפליקציות WebRTC של Ben Strong מכיל מידע רב על התשתית ועל הטופולוגיות של WebRTC.
הפרק בנושא WebRTC בספר High Performance Browser Networking של Ilya Grigorik עוסק לעומק בארכיטקטורה, בתרחישי לדוגמה ובביצועים של WebRTC.