เผยแพร่เมื่อ: 4 พฤศจิกายน 2013
WebRTC ช่วยให้สามารถสื่อสารแบบเพียร์ทูเพียร์ได้ โดยต้องใช้เซิร์ฟเวอร์เพื่อให้ไคลเอ็นต์สามารถแลกเปลี่ยนข้อมูลเมตาและประสานงานการสื่อสารผ่านกระบวนการที่เรียกว่าการส่งสัญญาณ นอกจากนี้ เซิร์ฟเวอร์ยังช่วยให้ไคลเอ็นต์รับมือกับการเปลี่ยนค่าที่อยู่เครือข่าย (NAT) และไฟร์วอลล์ได้ด้วย
ในส่วนนี้ คุณจะได้เรียนรู้วิธีสร้างบริการส่งสัญญาณและจัดการกับความผิดปกติของการเชื่อมต่อในชีวิตจริงด้วยเซิร์ฟเวอร์ STUN และ TURN นอกจากนี้ คุณยังจะได้ทราบวิธีที่แอป WebRTC จัดการกับการโทรแบบหลายฝ่ายและโต้ตอบกับบริการต่างๆ เช่น VoIP และ PSTN (หรือที่เรียกว่าโทรศัพท์)
การส่งสัญญาณคืออะไร
การส่งสัญญาณคือกระบวนการประสานงานการสื่อสาร เพื่อให้แอป WebRTC ตั้งค่าการโทรได้ ไคลเอ็นต์ของแอปจะต้องแลกเปลี่ยนข้อมูลต่อไปนี้
- ข้อความควบคุมเซสชันที่ใช้เพื่อเปิดหรือปิดการสื่อสาร
- ข้อความแสดงข้อผิดพลาด
- ข้อมูลเมตาของสื่อ เช่น ตัวแปลงรหัส การตั้งค่าตัวแปลงรหัส แบนด์วิดท์ และประเภทสื่อ
- ข้อมูลคีย์ที่ใช้เพื่อสร้างการเชื่อมต่อที่ปลอดภัย
- ข้อมูลเครือข่าย เช่น ที่อยู่ IP และพอร์ตของโฮสต์ตามที่โลกภายนอกเห็น
กระบวนการส่งสัญญาณนี้ต้องมีวิธีให้ไคลเอ็นต์ส่งข้อความไปมาได้ WebRTC API ไม่ได้ใช้กลไกดังกล่าว แต่คุณต้องสร้างขึ้นเอง
เหตุใด WebRTC จึงไม่กำหนดการส่งสัญญาณ
มาตรฐาน WebRTC ไม่ได้ระบุวิธีการและโปรโตคอลการส่งสัญญาณเพื่อหลีกเลี่ยงความซ้ำซ้อนและเพิ่มความเข้ากันได้กับเทคโนโลยีที่มีอยู่ให้มากที่สุด แนวทางนี้ระบุไว้ในโปรโตคอลการสร้างเซสชัน JavaScript (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 และการส่งสัญญาณ: ข้อเสนอ คำตอบ และผู้สมัคร
RTCPeerConnection คือ API ที่แอป WebRTC ใช้เพื่อสร้างการเชื่อมต่อระหว่างเพียร์ และสื่อสารเสียงและวิดีโอ
ในการเริ่มต้นกระบวนการนี้ RTCPeerConnection มีงาน 2 อย่างดังนี้
- ตรวจสอบเงื่อนไขของสื่อในเครื่อง เช่น ความสามารถด้านความละเอียดและตัวแปลงรหัส นี่คือข้อมูลเมตาที่ใช้สำหรับกลไกข้อเสนอและคำตอบ
- รับที่อยู่เครือข่ายที่เป็นไปได้สำหรับโฮสต์ของแอป ซึ่งเรียกว่าผู้สมัคร
เมื่อตรวจสอบข้อมูลในเครื่องนี้แล้ว จะต้องแลกเปลี่ยนข้อมูลผ่านกลไกการส่งสัญญาณกับเพียร์ระยะไกล
สมมติว่าอลิซพยายามโทรหาอีฟ ต่อไปนี้คือกลไกการตอบรับแบบเต็มพร้อมรายละเอียดทั้งหมด
- Alice สร้างออบเจ็กต์
RTCPeerConnection - Alice สร้างข้อเสนอ (คำอธิบายเซสชัน SDP) ด้วยเมธอด
RTCPeerConnectioncreateOffer() - Alice โทรหา
setLocalDescription()เพื่อเสนอราคา - Alice แปลงข้อเสนอเป็นสตริงและใช้กลไกการส่งสัญญาณเพื่อส่งข้อเสนอให้ Eve
- อีฟโทรหา
setRemoteDescription()ด้วยข้อเสนอของอลิซ เพื่อให้RTCPeerConnectionของเธอทราบเกี่ยวกับการตั้งค่าของอลิซ - อีฟเรียก
createAnswer()และการเรียกกลับที่สำเร็จสำหรับขั้นตอนนี้จะส่งคำอธิบายเซสชันในเครื่อง ซึ่งก็คือคำตอบของอีฟ - อีฟตั้งค่าคำตอบเป็นคำอธิบายในเครื่องโดยเรียกใช้
setLocalDescription() - จากนั้น Eve จะใช้กลไกการส่งสัญญาณเพื่อส่งคำตอบที่แปลงเป็นสตริงไปยัง Alice
- Alice ตั้งค่าคำตอบของ Eve เป็นคำอธิบายเซสชันระยะไกลโดยใช้
setRemoteDescription()
Alice และ Eve ยังต้องแลกเปลี่ยนข้อมูลเครือข่ายด้วย สำนวน "การค้นหาผู้สมัคร" หมายถึงกระบวนการค้นหาอินเทอร์เฟซเครือข่ายและพอร์ตโดยใช้เฟรมเวิร์ก ICE
- Alice สร้างออบเจ็กต์
RTCPeerConnectionที่มีแฮนเดิลonicecandidate - ระบบจะเรียกใช้แฮนเดิลเมื่อมีเครือข่ายที่พร้อมใช้งาน
- ในเครื่องจัดการ Alice จะส่งข้อมูลผู้สมัครที่แปลงเป็นสตริงไปยัง Eve ผ่านช่องทางการส่งสัญญาณ
- เมื่อ 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);
}
};
หากต้องการดูกระบวนการ Offer-Answer และ Candidate-Exchange ที่ทำงานอยู่ ให้ไปที่ 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 แลกเปลี่ยนข้อมูลเมตาผ่านบริการรับส่งข้อความที่ต้องการได้ เช่น IM, อีเมล หรือนกพิราบสื่อสาร
คุณจะสร้างบริการส่งสัญญาณได้อย่างไร
ขอย้ำอีกครั้งว่ามาตรฐาน WebRTC ไม่ได้กำหนดโปรโตคอลและกลไกการส่งสัญญาณ ไม่ว่าจะเลือกวิธีใด คุณต้องมีเซิร์ฟเวอร์สื่อกลางเพื่อแลกเปลี่ยนข้อความส่งสัญญาณและข้อมูลแอประหว่างไคลเอ็นต์ น่าเสียดายที่เว็บแอปไม่สามารถตะโกน บอกอินเทอร์เน็ตว่า "เชื่อมต่อฉันกับเพื่อนหน่อย"
โชคดีที่ข้อความส่งสัญญาณมีขนาดเล็กและส่วนใหญ่จะแลกเปลี่ยนกันเมื่อเริ่มการโทร ในการทดสอบกับ appr.tc สำหรับเซสชันวิดีโอแชท บริการส่งสัญญาณจะจัดการข้อความทั้งหมดประมาณ 30-45 ข้อความ โดยมีขนาดรวมของข้อความทั้งหมดประมาณ 10 KB
นอกจากจะใช้แบนด์วิดท์ค่อนข้างน้อยแล้ว บริการส่งสัญญาณ WebRTC ยังใช้การประมวลผลหรือหน่วยความจำไม่มากนัก เนื่องจากบริการเหล่านี้จำเป็นต้องส่งต่อข้อความและเก็บรักษาข้อมูลสถานะเซสชันจำนวนเล็กน้อยเท่านั้น เช่น ไคลเอ็นต์ที่เชื่อมต่ออยู่
พุชข้อความจากเซิร์ฟเวอร์ไปยังไคลเอ็นต์
บริการรับส่งข้อความสำหรับการส่งสัญญาณต้องเป็นแบบ 2 ทิศทาง ได้แก่ ไคลเอ็นต์ไปยังเซิร์ฟเวอร์และเซิร์ฟเวอร์ไปยังไคลเอ็นต์ การสื่อสารแบบ 2 ทิศทางขัดแย้งกับโมเดลคำขอ/การตอบกลับของไคลเอ็นต์ HTTP/เซิร์ฟเวอร์ แต่มีการพัฒนาแฮ็กต่างๆ เช่น Long Polling มาเป็นเวลาหลายปีเพื่อพุชข้อมูลจากบริการที่ทำงานบนเว็บเซิร์ฟเวอร์ไปยังเว็บแอปที่ทำงานในเบราว์เซอร์
EventSource API ช่วยให้เกิดเหตุการณ์ที่เซิร์ฟเวอร์ส่ง ซึ่งเป็นข้อมูลที่ส่งจากเว็บเซิร์ฟเวอร์ไปยังไคลเอ็นต์เบราว์เซอร์ผ่าน HTTP EventSource ออกแบบมาสำหรับการรับส่งข้อความทางเดียว แต่สามารถใช้ร่วมกับ XHR เพื่อสร้างบริการสำหรับการแลกเปลี่ยนข้อความส่งสัญญาณได้ บริการส่งสัญญาณจะส่งต่อข้อความจากผู้โทรที่ส่งโดยคำขอ XHR โดยพุชผ่าน EventSource ไปยังผู้รับสาย
WebSocket ออกแบบมาสำหรับการสื่อสารแบบฟูลดูเพล็กซ์ระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์ ซึ่งหมายความว่าข้อความสามารถส่งไปมาได้ทั้ง 2 ทิศทางพร้อมกัน ข้อดีอย่างหนึ่งของบริการส่งสัญญาณที่สร้างขึ้นด้วย WebSocket หรือเหตุการณ์ที่เซิร์ฟเวอร์ส่ง (EventSource) คือสามารถใช้แบ็กเอนด์สำหรับ API เหล่านี้ในเฟรมเวิร์กเว็บที่หลากหลายซึ่งพบได้ทั่วไปในแพ็กเกจเว็บโฮสติ้งส่วนใหญ่สำหรับภาษาต่างๆ เช่น PHP, Python และ Ruby
เบราว์เซอร์ทั้งหมดที่รองรับ WebRTC จะรองรับ WebSocket ด้วย ควรใช้ TLS สำหรับการเชื่อมต่อทั้งหมดเพื่อให้แน่ใจว่าข้อความจะไม่ถูกดักจับโดยไม่มีการเข้ารหัส และเพื่อลดปัญหาเกี่ยวกับการส่งผ่านพร็อกซีด้วย ดูข้อมูลเพิ่มเติมเกี่ยวกับ WebSocket และการส่งผ่านพร็อกซีได้ที่บท WebRTC ในหนังสือ High Performance Browser Networking ของ Ilya Grigorik
คุณสามารถจัดการการส่งสัญญาณได้โดยให้ไคลเอ็นต์ WebRTC ทำการสำรวจเซิร์ฟเวอร์การรับส่งข้อความซ้ำๆ ผ่าน Ajax แต่วิธีนี้จะทำให้เกิดคำขอเครือข่ายที่ซ้ำซ้อนจำนวนมาก ซึ่งเป็นปัญหาอย่างยิ่งสำหรับอุปกรณ์เคลื่อนที่ แม้ว่าจะสร้างเซสชันแล้ว แต่เพียร์ก็ยังต้องทำการสำรวจข้อความการส่งสัญญาณในกรณีที่มีการเปลี่ยนแปลงหรือการสิ้นสุดเซสชันโดยเพียร์อื่นๆ ตัวอย่างแอป WebRTC Book ใช้ตัวเลือกนี้โดยมีการเพิ่มประสิทธิภาพบางอย่างสำหรับความถี่ในการทำโพล
ปรับขนาดการส่งสัญญาณ
แม้ว่าบริการส่งสัญญาณจะใช้แบนด์วิดท์และ CPU ต่อไคลเอ็นต์ค่อนข้างน้อย แต่เซิร์ฟเวอร์ส่งสัญญาณสำหรับแอปยอดนิยมอาจต้องจัดการข้อความจำนวนมากจากตำแหน่งต่างๆ ที่มีการทำงานพร้อมกันในระดับสูง แอป WebRTC ที่มีการเข้าชมจำนวนมากต้องใช้เซิร์ฟเวอร์การส่งสัญญาณที่สามารถรองรับโหลดจำนวนมากได้ เราจะไม่ลงรายละเอียดในส่วนนี้ แต่มีตัวเลือกมากมายสำหรับการรับส่งข้อความปริมาณมากและประสิทธิภาพสูง ซึ่งรวมถึงตัวเลือกต่อไปนี้
eXtensible Messaging and Presence Protocol (XMPP) เป็นโปรโตคอลที่พัฒนาขึ้นสำหรับการรับส่งข้อความโต้ตอบแบบทันทีที่สามารถใช้สำหรับการส่งสัญญาณได้ การติดตั้งใช้งานเซิร์ฟเวอร์ประกอบด้วย ejabberd และ Openfire ไคลเอ็นต์ JavaScript เช่น Strophe.js ใช้ BOSH เพื่อจำลองการสตรีมแบบ 2 ทิศทาง แต่ 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 ช่วยให้สร้างบริการเพื่อแลกเปลี่ยนข้อความได้ง่ายขึ้น และ Socket.io เหมาะอย่างยิ่งสำหรับการส่งสัญญาณ WebRTC เนื่องจากมีแนวคิดเรื่องห้องในตัว ตัวอย่างนี้ออกแบบมาสำหรับผู้ใช้จำนวนค่อนข้างน้อย ไม่ใช่สำหรับการใช้งานจริง
Socket.io ใช้ WebSocket ที่มีทางเลือกสำรอง ได้แก่ การทำ AJAX Long Polling, การทำ AJAX Multipart Streaming, Forever Iframe และการทำ JSONP Polling มีการย้ายไปยังแบ็กเอนด์ต่างๆ แต่ที่รู้จักกันดีที่สุดคือเวอร์ชัน 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);
});
});
หากต้องการเรียกใช้แอปนี้ใน Localhost คุณต้องมี 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)
ไม่ว่าคุณจะเลือกแนวทางใดสำหรับการส่งสัญญาณ แบ็กเอนด์และแอปฝั่งไคลเอ็นต์จะต้องให้บริการที่คล้ายกับตัวอย่างนี้
ข้อควรระวังเกี่ยวกับสัญญาณ
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 ซึ่งจะช่วยให้มั่นใจได้ว่าข้อความจะไม่ถูกดักฟังโดยไม่มีการเข้ารหัส นอกจากนี้ โปรดระวังอย่าเผยแพร่ข้อความการส่งสัญญาณในลักษณะที่ผู้โทรรายอื่นสามารถเข้าถึงได้โดยใช้เซิร์ฟเวอร์การส่งสัญญาณเดียวกัน
รับมือกับ NAT และไฟร์วอลล์
สำหรับการส่งสัญญาณข้อมูลเมตา แอป WebRTC จะใช้เซิร์ฟเวอร์สื่อกลาง แต่สำหรับการสตรีมสื่อและข้อมูลจริงเมื่อสร้างเซสชันแล้ว RTCPeerConnection จะพยายามเชื่อมต่อไคลเอ็นต์โดยตรงหรือแบบเพียร์ทูเพียร์
ในโลกที่เรียบง่ายกว่านี้ ปลายทาง WebRTC ทุกแห่งจะมีที่อยู่ที่ไม่ซ้ำกันซึ่งสามารถแลกเปลี่ยนกับเพียร์อื่นๆ เพื่อสื่อสารโดยตรงได้
ในความเป็นจริง อุปกรณ์ส่วนใหญ่อยู่หลัง NAT อย่างน้อย 1 ชั้น บางอุปกรณ์มีซอฟต์แวร์ป้องกันไวรัสที่บล็อกพอร์ตและโปรโตคอลบางอย่าง และหลายอุปกรณ์อยู่หลังพร็อกซีและไฟร์วอลล์ขององค์กร ในความเป็นจริงแล้ว ไฟร์วอลล์และ NAT อาจติดตั้งใช้งานโดยอุปกรณ์เดียวกัน เช่น เราเตอร์ Wi-Fi ในบ้าน
แอป WebRTC สามารถใช้เฟรมเวิร์ก ICE เพื่อเอาชนะความซับซ้อนของเครือข่ายในโลกแห่งความเป็นจริงได้ หากต้องการเปิดใช้ฟีเจอร์นี้ แอปของคุณต้องส่ง URL ของเซิร์ฟเวอร์ ICE ไปยัง RTCPeerConnection
ICE จะพยายามค้นหาเส้นทางที่ดีที่สุดในการเชื่อมต่อเพียร์ โดยจะลองใช้ความเป็นไปได้ทั้งหมดพร้อมกันและเลือกตัวเลือกที่มีประสิทธิภาพมากที่สุดที่ใช้ได้ ICE จะพยายามสร้างการเชื่อมต่อโดยใช้ที่อยู่โฮสต์ที่ได้จากระบบปฏิบัติการและการ์ดเครือข่ายของอุปกรณ์ก่อน หากไม่สำเร็จ (ซึ่งจะเกิดขึ้นกับอุปกรณ์ที่อยู่หลัง NAT) ICE จะรับที่อยู่ภายนอกโดยใช้เซิร์ฟเวอร์ STUN และหากไม่สำเร็จ การรับส่งข้อมูลจะได้รับการกำหนดเส้นทางผ่านเซิร์ฟเวอร์ Relay TURN
กล่าวคือ เซิร์ฟเวอร์ STUN ใช้เพื่อรับที่อยู่เครือข่ายภายนอก และเซิร์ฟเวอร์ TURN ใช้เพื่อส่งต่อการรับส่งข้อมูลหากการเชื่อมต่อโดยตรง (แบบเพียร์ทูเพียร์) ล้มเหลว
เซิร์ฟเวอร์ 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 ตามความจำเป็น
STUN
NAT จะให้ที่อยู่ IP แก่อุปกรณ์เพื่อใช้ภายในเครือข่ายส่วนตัวในพื้นที่ แต่จะใช้ที่อยู่นี้ภายนอกไม่ได้ หากไม่มีที่อยู่สาธารณะ เพียร์ WebRTC จะสื่อสารกันไม่ได้ WebRTC จึงใช้ STUN เพื่อแก้ไขปัญหานี้
เซิร์ฟเวอร์ STUN ทำงานบนอินเทอร์เน็ตสาธารณะและมีหน้าที่เดียวคือตรวจสอบที่อยู่ IP:พอร์ตของคำขอที่เข้ามา (จากแอปที่ทำงานอยู่เบื้องหลัง NAT) และส่งที่อยู่นั้นกลับไปเป็นคำตอบ กล่าวคือ แอปจะใช้เซิร์ฟเวอร์ STUN เพื่อค้นหา IP:พอร์ตจากมุมมองสาธารณะ กระบวนการนี้ช่วยให้เพียร์ WebRTC ได้รับที่อยู่ที่เข้าถึงได้แบบสาธารณะสำหรับตัวเอง แล้วส่งต่อให้เพียร์อื่นผ่านกลไกการส่งสัญญาณเพื่อตั้งค่าลิงก์โดยตรง
เซิร์ฟเวอร์ STUN ไม่จำเป็นต้องทำงานหรือจดจำข้อมูลมากนัก ดังนั้นเซิร์ฟเวอร์ STUN ที่มีสเปกค่อนข้างต่ำจึงสามารถจัดการคำขอจำนวนมากได้
การโทร WebRTC ส่วนใหญ่จะเชื่อมต่อได้สำเร็จโดยใช้ STUN แต่อาจมีจำนวนน้อยกว่าสำหรับการโทรระหว่างเพียร์ที่อยู่หลังไฟร์วอลล์และการกำหนดค่า NAT ที่ซับซ้อน
TURN
RTCPeerConnection พยายามตั้งค่าการสื่อสารโดยตรงระหว่างเพียร์ผ่าน UDP
หากไม่สำเร็จ RTCPeerConnection จะเปลี่ยนไปใช้ TCP หากไม่สำเร็จ ระบบจะใช้เซิร์ฟเวอร์ TURN เป็นตัวเลือกสำรองเพื่อส่งต่อข้อมูลระหว่างปลายทาง
ขอย้ำอีกครั้งว่า TURN ใช้เพื่อส่งต่อสตรีมมิงเสียง วิดีโอ และข้อมูลระหว่างเพียร์ ไม่ใช่ข้อมูลการส่งสัญญาณ
เซิร์ฟเวอร์ TURN มีที่อยู่สาธารณะ ดังนั้นเพียร์จึงติดต่อได้แม้ว่าเพียร์จะอยู่หลังไฟร์วอลล์หรือพร็อกซีก็ตาม เซิร์ฟเวอร์ TURN มีหน้าที่เดียวคือส่งต่อสตรีม ซึ่งต่างจากเซิร์ฟเวอร์ STUN ที่ใช้แบนด์วิดท์จำนวนมากโดยเนื้อแท้
แผนภาพนี้แสดงการทำงานของ 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 ได้โดยทำดังนี้
- เปิดไฟร์วอลล์ตามความจำเป็นสำหรับ tcp=443, udp/tcp=3478
- สร้างอินสแตนซ์ 4 รายการ โดยแต่ละรายการมี IP สาธารณะ 1 รายการ และอิมเมจ Ubuntu 12.06 มาตรฐาน
- ตั้งค่าการกำหนดค่าไฟร์วอลล์ในเครื่อง (อนุญาตทุกอย่างจากทุกที่)
- ติดตั้งเครื่องมือ:
shell sudo apt-get install make sudo apt-get install gcc - ติดตั้ง libre จาก creytiv.com/re.html
- ดึงข้อมูล restund จาก creytiv.com/restund.html และแตกไฟล์
wgethancke.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
- ทดสอบโดยใช้ไคลเอ็นต์ stund จากเครื่องระยะไกล:
./client IP:port
WebRTC แบบหลายฝ่าย
นอกจากนี้ คุณอาจต้องการดูมาตรฐาน IETF ที่ Justin Uberti เสนอสำหรับ REST API สำหรับการเข้าถึงบริการ TURN
กรณีการใช้งานสำหรับการสตรีมสื่อมีมากมายนอกเหนือจากการโทรแบบตัวต่อตัว เช่น การประชุมทางวิดีโอระหว่างกลุ่มเพื่อนร่วมงานหรือกิจกรรมสาธารณะที่มีผู้พูด 1 คนและผู้ชมหลายร้อยหรือหลายล้านคน
แอป WebRTC สามารถใช้ RTCPeerConnection หลายรายการเพื่อให้ปลายทางทุกแห่งเชื่อมต่อกับปลายทางอื่นๆ ทั้งหมดในการกำหนดค่าแบบเมช ซึ่งเป็นแนวทางที่แอปต่างๆ เช่น talky.io ใช้ และทำงานได้ดีอย่างน่าทึ่งสำหรับแอปเทียบเท่าจำนวนไม่มาก หากเกินกว่านั้น การประมวลผลและการใช้แบนด์วิดท์จะมากเกินไป โดยเฉพาะสำหรับไคลเอ็นต์มือถือ
หรือแอป WebRTC อาจเลือกปลายทางเดียวเพื่อกระจายสตรีมไปยังปลายทางอื่นๆ ทั้งหมดในการกำหนดค่าแบบดาว นอกจากนี้ คุณยังเรียกใช้ปลายทาง WebRTC บนเซิร์ฟเวอร์และสร้างกลไกการกระจายซ้ำของคุณเองได้ (webrtc.org มีแอปไคลเอ็นต์ตัวอย่างให้)
MediaStream จาก RTCPeerConnection หนึ่งสามารถใช้เป็นอินพุตสำหรับอีกรายการหนึ่งได้ ซึ่งจะช่วยให้สถาปัตยกรรมมีความยืดหยุ่นมากขึ้น เนื่องจากช่วยให้เว็บแอปจัดการการกำหนดเส้นทางการโทรได้โดยเลือกเพียร์อื่นๆ ที่จะเชื่อมต่อ หากต้องการดูการทำงานนี้ โปรดดูตัวอย่าง WebRTC ของการส่งต่อการเชื่อมต่อแบบเพียร์และตัวอย่าง WebRTC ของการเชื่อมต่อแบบเพียร์หลายรายการ
หน่วยควบคุมแบบหลายจุด
ตัวเลือกที่ดีกว่าสำหรับอุปกรณ์ปลายทางจำนวนมากคือการใช้หน่วยควบคุมแบบหลายจุด (MCU) เซิร์ฟเวอร์นี้ทำหน้าที่เป็นสะพานเชื่อมเพื่อกระจายสื่อระหว่างผู้เข้าร่วมจำนวนมาก MCU สามารถรองรับความละเอียด ตัวแปลงรหัส และอัตราเฟรมที่แตกต่างกันในการประชุมทางวิดีโอ จัดการการแปลงรหัส ส่งต่อสตรีมแบบเลือกได้ และผสมหรือบันทึกเสียงและวิดีโอ สำหรับการโทรแบบหลายฝ่าย มีปัญหาหลายประการที่ต้องพิจารณา โดยเฉพาะวิธีแสดงอินพุตวิดีโอหลายรายการและมิกซ์เสียงจากหลายแหล่ง แพลตฟอร์มระบบคลาวด์ เช่น vLine ก็พยายามเพิ่มประสิทธิภาพการกำหนดเส้นทางการเข้าชมด้วยเช่นกัน
คุณสามารถซื้อแพ็กเกจฮาร์ดแวร์ MCU แบบสมบูรณ์หรือสร้างขึ้นเองก็ได้
มีตัวเลือกซอฟต์แวร์ MCU แบบโอเพนซอร์สให้เลือกใช้หลายรายการ ตัวอย่างเช่น Licode (เดิมชื่อ Lynckia) ผลิต MCU แบบโอเพนซอร์สสำหรับ WebRTC OpenTok มี Mantis
นอกเหนือจากเบราว์เซอร์: VoIP, โทรศัพท์ และการรับส่งข้อความ
ลักษณะที่เป็นมาตรฐานของ WebRTC ช่วยให้สามารถสร้างการสื่อสารระหว่างแอป WebRTC ที่ทำงานในเบราว์เซอร์กับอุปกรณ์หรือแพลตฟอร์มที่ทำงานบนแพลตฟอร์มการสื่อสารอื่น เช่น โทรศัพท์หรือระบบการประชุมทางวิดีโอ
SIP คือโปรโตคอลการส่งสัญญาณที่ระบบ VoIP และการประชุมทางวิดีโอใช้ หากต้องการเปิดใช้การสื่อสารระหว่างเว็บแอป WebRTC กับไคลเอ็นต์ SIP เช่น ระบบการประชุมทางวิดีโอ WebRTC ต้องใช้พร็อกซีเซิร์ฟเวอร์เพื่อเป็นสื่อกลางในการส่งสัญญาณ การส่งสัญญาณต้องผ่านเกตเวย์ แต่เมื่อสร้างการสื่อสารแล้ว การรับส่งข้อมูล SRTP (วิดีโอและเสียง) จะส่งแบบเพียร์ทูเพียร์ได้
เครือข่ายโทรศัพท์พื้นฐาน (PSTN) คือเครือข่ายแบบสลับวงจร ของโทรศัพท์แอนะล็อก "แบบเก่า" ทั้งหมด สำหรับการโทรระหว่างเว็บแอป WebRTC กับโทรศัพท์ การรับส่งข้อมูลต้องผ่านเกตเวย์ PSTN ในทำนองเดียวกัน เว็บแอป WebRTC ต้องใช้เซิร์ฟเวอร์ XMPP ระดับกลางเพื่อสื่อสารกับปลายทาง Jingle เช่น ไคลเอ็นต์ IM Jingle ได้รับการพัฒนาโดย Google เพื่อเป็นส่วนขยายของ XMPP เพื่อเปิดใช้เสียงและวิดีโอสำหรับบริการรับส่งข้อความ การใช้งาน WebRTC ในปัจจุบันอิงตามไลบรารี libjingle ของ C++ ซึ่งเป็นการใช้งาน Jingle ที่พัฒนาขึ้นสำหรับ Talk ในตอนแรก
แอป ไลบรารี และแพลตฟอร์มจำนวนมากใช้ความสามารถของ WebRTC ในการสื่อสารกับโลกภายนอก เช่น
- jsSIP: ไลบรารี SIP ของ JavaScript
- Phono: API โทรศัพท์ JavaScript แบบโอเพนซอร์สที่สร้างขึ้นเป็นปลั๊กอิน
- Twilio: เสียงและการรับส่งข้อความ
ดูข้อมูลเพิ่มเติม
- อ่านเอกสารประกอบเกี่ยวกับ WebRTC API
- Codelab ของ WebRTC มีวิธีการทีละขั้นตอนเกี่ยวกับวิธีสร้างแอปแชทวิดีโอและข้อความโดยใช้บริการส่งสัญญาณ Socket.io ที่ทำงานบน Node
- การนำเสนอ SFHTML5 ของ Chris Wilson: ข้อมูลเบื้องต้นเกี่ยวกับแอป WebRTC
หนังสือ 350 หน้าเรื่อง WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web ให้รายละเอียดมากมายเกี่ยวกับเส้นทางข้อมูลและการส่งสัญญาณ รวมถึงแผนภาพโทโพโลยีเครือข่ายโดยละเอียดจำนวนมาก