Xây dựng các dịch vụ phụ trợ cần thiết cho ứng dụng WebRTC

Tín hiệu là gì?

Tín hiệu là quá trình điều phối hoạt động giao tiếp. Để ứng dụng WebRTC thiết lập cuộc gọi, ứng dụng đó cần trao đổi những thông tin sau:

  • Tin nhắn kiểm soát phiên dùng để mở hoặc đóng hoạt động giao tiếp
  • Thông báo lỗi
  • Siêu dữ liệu nội dung nghe nhìn, chẳng hạn như bộ mã hoá và giải mã, chế độ cài đặt bộ mã hoá và giải mã, băng thông và loại nội dung nghe nhìn
  • Dữ liệu khoá dùng để thiết lập kết nối an toàn
  • Dữ liệu mạng, chẳng hạn như địa chỉ IP và cổng của máy chủ mà bên ngoài nhìn thấy

Quá trình truyền tín hiệu này cần có một cách để các ứng dụng gửi tin nhắn qua lại. Cơ chế đó không được triển khai bằng API WebRTC. Bạn cần tự tạo. Trong phần sau của bài viết này, bạn sẽ tìm hiểu cách tạo dịch vụ báo hiệu. Tuy nhiên, trước tiên, bạn cần biết một chút về bối cảnh.

Tại sao WebRTC không xác định tín hiệu?

Để tránh tình trạng dư thừa và tối đa hoá khả năng tương thích với các công nghệ đã thiết lập, các phương thức và giao thức báo hiệu không được chỉ định theo tiêu chuẩn WebRTC. Phương pháp này được nêu trong Giao thức thiết lập phiên JavaScript (JSEP):

Cấu trúc của JSEP cũng giúp trình duyệt không phải lưu trạng thái, tức là hoạt động như một máy trạng thái báo hiệu. Điều này sẽ gây ra vấn đề nếu dữ liệu báo hiệu bị mất mỗi khi tải lại trang. Thay vào đó, bạn có thể lưu trạng thái báo hiệu trên máy chủ.

Sơ đồ cấu trúc JSEP
Cấu trúc JSEP

JSEP yêu cầu trao đổi giữa các ứng dụng ngang hàng về lời đề nghịcâu trả lời, siêu dữ liệu nội dung nghe nhìn được đề cập ở trên. Ưu đãi và câu trả lời được thông báo theo định dạng Giao thức mô tả phiên (SDP) như sau:

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

Bạn muốn biết tất cả những từ ngữ khó hiểu này trong SDP có ý nghĩa gì? Hãy xem các ví dụ của Lực lượng chuyên trách kỹ thuật Internet (IETF).

Xin lưu ý rằng WebRTC được thiết kế để bạn có thể điều chỉnh ưu đãi hoặc câu trả lời trước khi đặt làm nội dung mô tả cục bộ hoặc từ xa bằng cách chỉnh sửa các giá trị trong văn bản SDP. Ví dụ: bạn có thể sử dụng hàm preferAudioCodec() trong appr.tc để đặt bộ mã hoá và giải mã cũng như tốc độ bit mặc định. SDP hơi khó thao tác bằng JavaScript và có một cuộc thảo luận về việc liệu các phiên bản WebRTC trong tương lai có nên sử dụng JSON hay không, nhưng có một số lợi thế khi sử dụng SDP.

RTCPeerConnection API và tín hiệu: Ưu đãi, câu trả lời và đề xuất

RTCPeerConnection là API mà các ứng dụng WebRTC sử dụng để tạo kết nối giữa các máy ngang hàng và giao tiếp âm thanh và video.

Để khởi chạy quy trình này, RTCPeerConnection có hai nhiệm vụ:

  • Xác định các điều kiện của nội dung nghe nhìn cục bộ, chẳng hạn như độ phân giải và khả năng giải mã và mã hoá. Đây là siêu dữ liệu dùng cho cơ chế đưa ra câu hỏi và trả lời.
  • Nhận địa chỉ mạng tiềm năng cho máy chủ lưu trữ của ứng dụng, được gọi là ứng viên.

Sau khi xác định được dữ liệu cục bộ này, bạn phải trao đổi dữ liệu đó thông qua cơ chế báo hiệu với máy chủ từ xa.

Hãy tưởng tượng Alice đang cố gắng gọi cho Eve. Dưới đây là toàn bộ cơ chế đưa ra/trả lời lời mời chi tiết đến từng chi tiết:

  1. Alice tạo một đối tượng RTCPeerConnection.
  2. Alice tạo một ưu đãi (mô tả phiên SDP) bằng phương thức RTCPeerConnection createOffer().
  3. Alice gọi setLocalDescription() để đưa ra ưu đãi.
  4. Alice chuyển đổi ưu đãi thành chuỗi và sử dụng cơ chế báo hiệu để gửi ưu đãi đó đến Eve.
  5. Eve gọi setRemoteDescription() với ưu đãi của Alice để RTCPeerConnection của cô biết về chế độ thiết lập của Alice.
  6. Eve gọi createAnswer() và lệnh gọi lại thành công cho lệnh gọi này được truyền nội dung mô tả phiên cục bộ – câu trả lời của Eve.
  7. Eve đặt câu trả lời của mình làm nội dung mô tả cục bộ bằng cách gọi setLocalDescription().
  8. Sau đó, Eve sử dụng cơ chế báo hiệu để gửi câu trả lời đã chuyển đổi thành chuỗi cho Alice.
  9. Alice đặt câu trả lời của Eve làm nội dung mô tả phiên từ xa bằng setRemoteDescription().

Alice và Eve cũng cần trao đổi thông tin mạng. Biểu thức "tìm ứng viên" đề cập đến quá trình tìm giao diện mạng và cổng bằng khung ICE.

  1. Alice tạo một đối tượng RTCPeerConnection bằng trình xử lý onicecandidate.
  2. Trình xử lý được gọi khi có đề xuất mạng.
  3. Trong trình xử lý, Alice gửi dữ liệu đề xuất đã chuyển đổi thành chuỗi cho Eve thông qua kênh báo hiệu.
  4. Khi nhận được thông báo đề xuất từ Alice, Eve sẽ gọi addIceCandidate() để thêm đề xuất vào nội dung mô tả máy ngang hàng từ xa.

JSEP hỗ trợ tính năng Tiết kiệm các đề xuất ICE, cho phép phương thức gọi cung cấp dần các đề xuất cho phương thức được gọi sau khi đưa ra đề xuất ban đầu, đồng thời cho phép phương thức được gọi bắt đầu hành động trên lệnh gọi và thiết lập kết nối mà không cần chờ tất cả các đề xuất đến.

Mã WebRTC để báo hiệu

Đoạn mã sau đây là ví dụ về mã W3C tóm tắt toàn bộ quy trình báo hiệu. Mã này giả định rằng có một số cơ chế báo hiệu, SignalingChannel. Chúng ta sẽ thảo luận chi tiết hơn về tín hiệu ở phần sau.

// 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);
  }
};

Để xem quy trình cung cấp/trả lời và trao đổi đề xuất, hãy xem simpl.info RTCPeerConnection và xem nhật ký bảng điều khiển để biết ví dụ về cuộc trò chuyện video trên một trang. Nếu bạn muốn biết thêm thông tin, hãy tải tệp báo lỗi đầy đủ về tín hiệu và số liệu thống kê WebRTC xuống từ trang about://webrtc-internals trong Google Chrome hoặc trang opera://webrtc-internals trong Opera.

Khám phá ngang hàng

Đây là cách hỏi "Làm cách nào để tìm người trò chuyện?" theo cách thú vị.

Đối với cuộc gọi điện thoại, bạn có số điện thoại và danh bạ. Để trò chuyện và nhắn tin qua video trực tuyến, bạn cần có hệ thống quản lý danh tính và trạng thái, cũng như một phương tiện để người dùng bắt đầu phiên. Các ứng dụng WebRTC cần có cách để khách hàng báo hiệu cho nhau rằng họ muốn bắt đầu hoặc tham gia cuộc gọi.

WebRTC không xác định cơ chế khám phá ngang hàng và bạn không cần xem xét các tuỳ chọn tại đây. Quy trình này có thể đơn giản như gửi email hoặc nhắn tin URL. Đối với các ứng dụng trò chuyện video, chẳng hạn như Talky, tawk.toBrowser Meeting, bạn có thể mời mọi người tham gia cuộc gọi bằng cách chia sẻ một đường liên kết tuỳ chỉnh. Nhà phát triển Chris Ball đã xây dựng một thử nghiệm serverless-webrtc thú vị, cho phép những người tham gia cuộc gọi WebRTC trao đổi siêu dữ liệu bằng bất kỳ dịch vụ nhắn tin nào họ muốn, chẳng hạn như tin nhắn nhanh, email hoặc chim bồ câu.

Làm cách nào để xây dựng dịch vụ báo hiệu?

Xin nhắc lại rằng các giao thức và cơ chế báo hiệu không được xác định theo tiêu chuẩn WebRTC. Dù chọn cách nào, bạn cũng cần có một máy chủ trung gian để trao đổi thông báo báo hiệu và dữ liệu ứng dụng giữa các ứng dụng. Đáng tiếc là ứng dụng web không thể chỉ cần hét vào Internet: "Kết nối tôi với bạn bè!"

Rất may, các thông báo báo hiệu có kích thước nhỏ và chủ yếu được trao đổi ở đầu cuộc gọi. Trong quá trình thử nghiệm với appr.tc cho một phiên trò chuyện video, dịch vụ báo hiệu đã xử lý tổng cộng khoảng 30 đến 45 tin nhắn với tổng kích thước của tất cả tin nhắn là khoảng 10 KB.

Ngoài việc tương đối không đòi hỏi nhiều về băng thông, các dịch vụ báo hiệu WebRTC không tiêu tốn nhiều bộ nhớ hoặc tài nguyên xử lý vì chúng chỉ cần chuyển tiếp thông báo và giữ lại một lượng nhỏ dữ liệu trạng thái phiên, chẳng hạn như ứng dụng nào được kết nối.

Đẩy thông báo từ máy chủ đến ứng dụng

Dịch vụ thông báo để báo hiệu cần phải có tính hai chiều: máy khách đến máy chủ và máy chủ đến máy khách. Giao tiếp hai chiều đi ngược lại mô hình yêu cầu/phản hồi máy khách/máy chủ HTTP, nhưng nhiều phương thức xâm nhập như thăm dò ý kiến dài hạn đã được phát triển trong nhiều năm để đẩy dữ liệu từ một dịch vụ chạy trên máy chủ web đến một ứng dụng web chạy trong trình duyệt.

Gần đây, API EventSource đã được triển khai rộng rãi. Điều này cho phép các sự kiện do máy chủ gửi – dữ liệu được gửi từ máy chủ web đến ứng dụng trình duyệt thông qua HTTP. EventSource được thiết kế để nhắn tin một chiều, nhưng bạn có thể sử dụng kết hợp với XHR để xây dựng một dịch vụ trao đổi tin nhắn báo hiệu. Dịch vụ báo hiệu chuyển một thông báo từ phương thức gọi, được phân phối bằng yêu cầu XHR, bằng cách đẩy thông báo đó qua EventSource đến phương thức được gọi.

WebSocket là một giải pháp tự nhiên hơn, được thiết kế để giao tiếp hai chiều đầy đủ giữa máy khách và máy chủ – các thông báo có thể chuyển theo cả hai hướng cùng một lúc. Một lợi thế của dịch vụ báo hiệu được tạo bằng WebSocket thuần tuý hoặc sự kiện do máy chủ gửi (EventSource) là phần phụ trợ cho các API này có thể được triển khai trên nhiều khung web phổ biến với hầu hết các gói lưu trữ web cho các ngôn ngữ như PHP, Python và Ruby.

Tất cả trình duyệt hiện đại ngoại trừ Opera Mini đều hỗ trợ WebSocket và quan trọng hơn, tất cả trình duyệt hỗ trợ WebRTC cũng hỗ trợ WebSocket, cả trên máy tính và thiết bị di động. Bạn nên sử dụng TLS cho tất cả các kết nối để đảm bảo không thể chặn thư chưa mã hoá, đồng thời giảm các vấn đề về việc chuyển tiếp qua proxy. (Để biết thêm thông tin về WebSocket và tính năng chuyển tiếp proxy, hãy xem chương WebRTC trong cuốn sách Mạng duyệt hiệu suất cao của Ilya Grigorik.)

Bạn cũng có thể xử lý tín hiệu bằng cách yêu cầu các ứng dụng WebRTC thăm dò ý kiến máy chủ nhắn tin nhiều lần thông qua Ajax, nhưng điều đó dẫn đến nhiều yêu cầu mạng thừa, đặc biệt là gây ra vấn đề cho thiết bị di động. Ngay cả sau khi một phiên được thiết lập, các máy ngang hàng cần thăm dò ý kiến về các thông báo báo hiệu trong trường hợp các máy ngang hàng khác thay đổi hoặc chấm dứt phiên. Ví dụ về ứng dụng WebRTC Book sử dụng tuỳ chọn này với một số tính năng tối ưu hoá cho tần suất thăm dò ý kiến.

Báo hiệu theo tỷ lệ

Mặc dù dịch vụ báo hiệu tiêu thụ tương đối ít băng thông và CPU trên mỗi máy khách, nhưng máy chủ báo hiệu cho một ứng dụng phổ biến có thể phải xử lý nhiều thông báo từ nhiều vị trí với mức độ đồng thời cao. Các ứng dụng WebRTC có lưu lượng truy cập lớn cần có máy chủ báo hiệu có thể xử lý tải đáng kể. Bạn không cần đi sâu vào chi tiết ở đây, nhưng có một số lựa chọn để gửi tin nhắn với số lượng lớn và hiệu suất cao, bao gồm:

  • Giao thức nhắn tin và hiện diện có thể mở rộng (XMPP), ban đầu được gọi là Jabber – một giao thức được phát triển để nhắn tin tức thì và có thể dùng để báo hiệu (Các phương thức triển khai máy chủ bao gồm ejabberdOpenfire. Các ứng dụng JavaScript, chẳng hạn như Strophe.js, sử dụng BOSH để mô phỏng tính năng truyền trực tuyến hai chiều, nhưng vì nhiều lý do, BOSH có thể không hiệu quả bằng WebSocket và cũng vì những lý do tương tự, có thể không mở rộng quy mô tốt.) (Ngoài ra, Jingle là một tiện ích XMPP để bật tính năng thoại và video. Dự án WebRTC sử dụng các thành phần mạng và truyền tải từ thư viện libjingle – một cách triển khai Jingle bằng C++.)

  • Thư viện nguồn mở, chẳng hạn như ZeroMQ (do TokBox sử dụng cho dịch vụ Rumour) và OpenMQ (NullMQ áp dụng các khái niệm ZeroMQ cho các nền tảng web bằng cách sử dụng giao thức STOMP qua WebSocket.)

  • Các nền tảng nhắn tin trên đám mây thương mại sử dụng WebSocket (mặc dù có thể quay lại tính năng thăm dò ý kiến dài), chẳng hạn như Pusher, KaazingPubNub (PubNub cũng có API cho WebRTC.)

  • Nền tảng WebRTC thương mại, chẳng hạn như vLine

(Hướng dẫn về công nghệ web theo thời gian thực của nhà phát triển Phil Leggetter cung cấp danh sách đầy đủ các dịch vụ và thư viện nhắn tin.)

Tạo dịch vụ báo hiệu bằng Socket.io trên Node

Sau đây là mã cho một ứng dụng web đơn giản sử dụng dịch vụ báo hiệu được tạo bằng Socket.io trên Node. Thiết kế của Socket.io giúp bạn dễ dàng tạo một dịch vụ để trao đổi tin nhắn và Socket.io đặc biệt phù hợp với tín hiệu WebRTC nhờ khái niệm phòng tích hợp sẵn. Ví dụ này không được thiết kế để mở rộng quy mô như một dịch vụ báo hiệu cấp độ sản xuất, nhưng dễ hiểu đối với một số lượng người dùng tương đối nhỏ.

Socket.io sử dụng WebSocket với các phương thức dự phòng: thăm dò ý kiến dài hạn AJAX, truyền trực tuyến nhiều phần AJAX, Forever Iframe và thăm dò ý kiến JSONP. Thư viện này đã được chuyển sang nhiều phần phụ trợ, nhưng có lẽ được biết đến nhiều nhất là phiên bản Node được sử dụng trong ví dụ này.

Không có WebRTC trong ví dụ này. Ứng dụng này chỉ được thiết kế để cho thấy cách tạo tín hiệu vào một ứng dụng web. Hãy xem nhật ký bảng điều khiển để biết điều gì đang xảy ra khi ứng dụng khách tham gia một phòng và trao đổi tin nhắn. Lớp học lập trình WebRTC này cung cấp hướng dẫn từng bước về cách tích hợp tính năng này vào một ứng dụng trò chuyện video WebRTC hoàn chỉnh.

Sau đây là ứng dụng 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>

Dưới đây là tệp JavaScript main.js được tham chiếu trong ứng dụng:

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);
});

Dưới đây là ứng dụng máy chủ hoàn chỉnh:

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

  });

});

(Bạn không cần tìm hiểu về node-static cho việc này. Nó chỉ tình cờ được sử dụng trong ví dụ này.)

Để chạy ứng dụng này trên máy chủ cục bộ, bạn cần cài đặt Node, Socket.IO và node-static. Bạn có thể tải Node xuống từ Node.js (quy trình cài đặt đơn giản và nhanh chóng). Để cài đặt Socket.IO và node-static, hãy chạy Trình quản lý gói Node từ một thiết bị đầu cuối trong thư mục ứng dụng:

npm install socket.io
npm install node-static

Để khởi động máy chủ, hãy chạy lệnh sau từ một thiết bị đầu cuối trong thư mục ứng dụng:

node server.js

Mở localhost:2013 trên trình duyệt. Mở một thẻ hoặc cửa sổ mới trong bất kỳ trình duyệt nào rồi mở lại localhost:2013. Để xem điều gì đang xảy ra, hãy kiểm tra bảng điều khiển. Trong Chrome và Opera, bạn có thể truy cập vào bảng điều khiển thông qua Công cụ dành cho nhà phát triển Google Chrome bằng Ctrl+Shift+J (hoặc Command+Option+J trên máy Mac).

Bất kể bạn chọn phương pháp nào để báo hiệu, ít nhất thì phần phụ trợ và ứng dụng khách của bạn cần cung cấp các dịch vụ tương tự như ví dụ này.

Các lỗi thường gặp về tín hiệu

  • RTCPeerConnection sẽ không bắt đầu thu thập các đề xuất cho đến khi setLocalDescription() được gọi. Điều này được quy định trong bản nháp JSEP IETF.
  • Tận dụng tính năng Trickle ICE. Gọi addIceCandidate() ngay khi ứng viên đến.

Máy chủ báo hiệu tạo sẵn

Nếu bạn không muốn tự tạo, có một số máy chủ báo hiệu WebRTC sử dụng Socket.IO như ví dụ trước và được tích hợp với thư viện JavaScript của ứng dụng WebRTC:

  • webRTC.io là một trong những thư viện trừu tượng đầu tiên cho WebRTC.
  • Signalmaster là một máy chủ báo hiệu được tạo để sử dụng với thư viện ứng dụng JavaScript SimpleWebRTC.

Nếu bạn không muốn viết mã, các công ty như vLine, OpenTokAsterisk có cung cấp các nền tảng WebRTC thương mại hoàn chỉnh.

Xin lưu ý rằng Ericsson đã xây dựng một máy chủ báo hiệu bằng PHP trên Apache trong những ngày đầu của WebRTC. Mã này hiện đã lỗi thời, nhưng bạn nên xem xét mã này nếu đang cân nhắc một mã tương tự.

Bảo mật tín hiệu

"Bảo mật là nghệ thuật không để xảy ra điều gì".

Salman Rushdie

Tất cả thành phần WebRTC đều bắt buộc phải mã hoá.

Tuy nhiên, các cơ chế báo hiệu không được xác định theo tiêu chuẩn WebRTC, vì vậy, bạn có thể tự đảm bảo tính bảo mật của báo hiệu. Nếu có thể xâm nhập vào tín hiệu, kẻ tấn công có thể dừng các phiên, chuyển hướng kết nối, ghi lại, thay đổi hoặc chèn nội dung.

Yếu tố quan trọng nhất trong việc bảo mật tín hiệu là sử dụng các giao thức bảo mật – HTTPS và WSS (ví dụ: TLS) – để đảm bảo rằng không thể chặn các thông báo chưa mã hoá. Ngoài ra, hãy cẩn thận để không truyền tin nhắn báo hiệu theo cách mà các phương thức gọi khác có thể truy cập vào tin nhắn đó bằng cùng một máy chủ báo hiệu.

Sau khi gửi tín hiệu: Sử dụng ICE để đối phó với NAT và tường lửa

Đối với việc báo hiệu siêu dữ liệu, các ứng dụng WebRTC sử dụng một máy chủ trung gian, nhưng đối với nội dung đa phương tiện và truyền dữ liệu thực tế sau khi thiết lập phiên, RTCPeerConnection sẽ cố gắng kết nối trực tiếp với ứng dụng khách hoặc ngang hàng.

Trong một thế giới đơn giản hơn, mỗi điểm cuối WebRTC sẽ có một địa chỉ duy nhất mà nó có thể trao đổi với các điểm cuối khác để giao tiếp trực tiếp.

Kết nối ngang hàng đơn giản
Một thế giới không có NAT và tường lửa

Trên thực tế, hầu hết thiết bị đều nằm sau một hoặc nhiều lớp NAT, một số thiết bị có phần mềm diệt vi-rút chặn một số cổng và giao thức nhất định, nhiều thiết bị nằm sau proxy và tường lửa của công ty. Trên thực tế, tường lửa và NAT có thể được triển khai trên cùng một thiết bị, chẳng hạn như bộ định tuyến WIFI gia đình.

Các máy ngang hàng phía sau NAT và tường lửa
Thế giới thực

Các ứng dụng WebRTC có thể sử dụng khung ICE để khắc phục sự phức tạp của việc kết nối mạng trong thế giới thực. Để cho phép việc này xảy ra, ứng dụng của bạn phải chuyển URL máy chủ ICE đến RTCPeerConnection, như mô tả trong bài viết này.

ICE cố gắng tìm đường dẫn tốt nhất để kết nối các máy ngang hàng. Phương thức này thử song song tất cả các khả năng và chọn phương án hiệu quả nhất hoạt động. Trước tiên, ICE sẽ cố gắng kết nối bằng địa chỉ máy chủ lấy được từ hệ điều hành và thẻ mạng của thiết bị. Nếu không thành công (với các thiết bị phía sau NAT), ICE sẽ lấy địa chỉ bên ngoài bằng máy chủ STUN và nếu không thành công, lưu lượng truy cập sẽ được định tuyến thông qua máy chủ chuyển tiếp TURN.

Nói cách khác, máy chủ STUN dùng để lấy địa chỉ mạng bên ngoài và máy chủ TURN dùng để chuyển tiếp lưu lượng truy cập nếu không kết nối được trực tiếp (giữa các máy ngang hàng).

Mọi máy chủ TURN đều hỗ trợ STUN. Máy chủ TURN là máy chủ STUN có chức năng chuyển tiếp tích hợp bổ sung. ICE cũng giải quyết được sự phức tạp của việc thiết lập NAT. Trong thực tế, việc tạo lỗ hổng NAT có thể đòi hỏi nhiều hơn một địa chỉ IP:cổng công khai.

URL cho máy chủ STUN và/hoặc TURN (không bắt buộc) được ứng dụng WebRTC chỉ định trong đối tượng cấu hình iceServers, đây là đối số đầu tiên cho hàm khởi tạo RTCPeerConnection. Đối với appr.tc, giá trị đó sẽ có dạng như sau:

{
  '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'
    }
  ]
}

Khi RTCPeerConnection có thông tin đó, phép thuật ICE sẽ tự động diễn ra. RTCPeerConnection sử dụng khung ICE để tìm ra đường dẫn tốt nhất giữa các máy ngang hàng, hoạt động với các máy chủ STUN và TURN nếu cần.

STUN

NAT cung cấp cho thiết bị một địa chỉ IP để sử dụng trong mạng cục bộ riêng tư, nhưng địa chỉ này không thể được sử dụng bên ngoài. Nếu không có địa chỉ công khai, các máy ngang hàng WebRTC sẽ không thể giao tiếp. Để giải quyết vấn đề này, WebRTC sử dụng STUN.

Máy chủ STUN hoạt động trên Internet công cộng và có một nhiệm vụ đơn giản – kiểm tra địa chỉ IP:cổng của một yêu cầu đến (từ một ứng dụng chạy sau NAT) và gửi địa chỉ đó trở lại dưới dạng phản hồi. Nói cách khác, ứng dụng sử dụng máy chủ STUN để khám phá IP:port từ góc độ công khai. Quy trình này cho phép một máy chủ ngang hàng WebRTC nhận địa chỉ truy cập công khai cho chính nó, sau đó chuyển địa chỉ đó cho một máy chủ ngang hàng khác thông qua cơ chế báo hiệu để thiết lập đường liên kết trực tiếp. (Trong thực tế, các NAT khác nhau hoạt động theo nhiều cách và có thể có nhiều lớp NAT, nhưng nguyên tắc vẫn giống nhau.)

Máy chủ STUN không phải làm nhiều việc hoặc ghi nhớ nhiều, vì vậy, các máy chủ STUN có thông số kỹ thuật tương đối thấp có thể xử lý một số lượng lớn yêu cầu.

Theo Webrtcstats.com, hầu hết các cuộc gọi WebRTC đều thành công trong việc kết nối bằng STUN – 86%, mặc dù tỷ lệ này có thể thấp hơn đối với các cuộc gọi giữa các máy ngang hàng phía sau tường lửa và cấu hình NAT phức tạp.

Kết nối ngang hàng bằng máy chủ STUN
Sử dụng máy chủ STUN để lấy địa chỉ IP:cổng công khai

BẬT

RTCPeerConnection cố gắng thiết lập giao tiếp trực tiếp giữa các máy ngang hàng qua UDP. Nếu không thành công, RTCPeerConnection sẽ chuyển sang TCP. Nếu không thành công, bạn có thể sử dụng máy chủ TURN làm phương án dự phòng, chuyển tiếp dữ liệu giữa các điểm cuối.

Xin nhắc lại, TURN được dùng để chuyển tiếp âm thanh, video và luồng dữ liệu giữa các máy ngang hàng, chứ không phải dữ liệu báo hiệu!

Máy chủ TURN có địa chỉ công khai, vì vậy, các máy chủ ngang hàng có thể liên hệ với các máy chủ này ngay cả khi các máy chủ ngang hàng đó nằm sau tường lửa hoặc proxy. Máy chủ TURN có một nhiệm vụ đơn giản về mặt khái niệm – chuyển tiếp luồng. Tuy nhiên, không giống như máy chủ STUN, các máy chủ này vốn tiêu tốn nhiều băng thông. Nói cách khác, máy chủ TURN cần mạnh hơn.

Kết nối ngang hàng bằng máy chủ STUN
Monty đầy đủ: STUN, TURN và tín hiệu

Sơ đồ này cho thấy cách hoạt động của TURN. STUN thuần tuý không thành công, vì vậy, mỗi máy khách đều chuyển sang sử dụng máy chủ TURN.

Triển khai máy chủ STUN và TURN

Để kiểm thử, Google chạy một máy chủ STUN công khai, stun.l.google.com:19302, như appr.tc sử dụng. Đối với dịch vụ STUN/TURN chính thức, hãy sử dụng rfc5766-turn-server. Bạn có thể tìm thấy mã nguồn cho máy chủ STUN và TURN trên GitHub, tại đây bạn cũng có thể tìm thấy các đường liên kết đến một số nguồn thông tin về cách cài đặt máy chủ. Bạn cũng có thể sử dụng hình ảnh máy ảo cho Amazon Web Services.

Một máy chủ TURN thay thế là restund, có sẵn dưới dạng mã nguồn và cũng dành cho AWS. Dưới đây là hướng dẫn cách thiết lập tính năng hoàn tiền trên Compute Engine.

  1. Mở tường lửa nếu cần cho tcp=443, udp/tcp=3478.
  2. Tạo 4 thực thể, mỗi thực thể cho một địa chỉ IP công khai, hình ảnh Ubuntu 12.06 tiêu chuẩn.
  3. Thiết lập cấu hình tường lửa cục bộ (cho phép BẤT KỲ từ BẤT KỲ).
  4. Cài đặt công cụ: shell sudo apt-get install make sudo apt-get install gcc
  5. Cài đặt libre từ creytiv.com/re.html.
  6. Tìm nạp restund từ creytiv.com/restund.html và giải nén./
  7. wget hancke.name/restund-auth.patch và áp dụng bằng patch -p1 < restund-auth.patch.
  8. Chạy make, sudo make install cho libre và restund.
  9. Điều chỉnh restund.conf theo nhu cầu của bạn (thay thế địa chỉ IP và đảm bảo rằng tệp này chứa cùng một khoá bí mật dùng chung) rồi sao chép vào /etc.
  10. Sao chép restund/etc/restund vào /etc/init.d/.
  11. Định cấu hình restund:
    1. Đặt LD_LIBRARY_PATH.
    2. Sao chép restund.conf vào /etc/restund.conf.
    3. Đặt restund.conf để sử dụng 10 bên phải. Địa chỉ IP.
  12. Chạy restund
  13. Kiểm thử bằng ứng dụng từ xa từ máy từ xa: ./client IP:port

Không chỉ một với một: WebRTC nhiều bên

Bạn cũng nên xem tiêu chuẩn IETF do Justin Uberti đề xuất cho API REST để truy cập vào Dịch vụ TURN.

Bạn có thể dễ dàng hình dung các trường hợp sử dụng cho tính năng truyền phát nội dung nghe nhìn không chỉ là một cuộc gọi một với một đơn giản. Ví dụ: hội nghị truyền hình giữa một nhóm đồng nghiệp hoặc một sự kiện công khai có một diễn giả và hàng trăm hoặc hàng triệu người xem.

Một ứng dụng WebRTC có thể sử dụng nhiều RTCPeerConnections để mọi điểm cuối kết nối với mọi điểm cuối khác trong cấu hình lưới. Đây là phương pháp mà các ứng dụng như talky.io sử dụng và hoạt động rất hiệu quả đối với một số ít ứng dụng tương tự. Ngoài ra, mức tiêu thụ băng thông và xử lý trở nên quá mức, đặc biệt là đối với ứng dụng di động.

Lưới: lệnh gọi N chiều nhỏ
Cấu trúc liên kết lưới đầy đủ: Mọi người đều kết nối với nhau

Ngoài ra, ứng dụng WebRTC có thể chọn một điểm cuối để phân phối luồng đến tất cả các điểm cuối khác trong cấu hình hình sao. Bạn cũng có thể chạy một điểm cuối WebRTC trên máy chủ và xây dựng cơ chế phân phối lại của riêng mình (ứng dụng khách mẫu do webrtc.org cung cấp).

Kể từ Chrome 31 và Opera 18, bạn có thể dùng MediaStream từ một RTCPeerConnection làm dữ liệu đầu vào cho một RTCPeerConnection khác. Điều này có thể hỗ trợ các cấu trúc linh hoạt hơn vì cho phép ứng dụng web xử lý việc định tuyến cuộc gọi bằng cách chọn ứng dụng ngang hàng khác để kết nối. Để xem cách hoạt động này, hãy xem Mẫu WebRTC Chuyển tiếp kết nối ngang hàngMẫu WebRTC Nhiều kết nối ngang hàng.

Bộ điều khiển đa điểm

Một lựa chọn tốt hơn cho nhiều điểm cuối là sử dụng Bộ điều khiển đa điểm (MCU). Đây là một máy chủ hoạt động như một cầu nối để phân phối nội dung nghe nhìn giữa một số lượng lớn người tham gia. MCU có thể xử lý nhiều độ phân giải, bộ mã hoá và tốc độ khung hình trong một cuộc họp video; xử lý việc chuyển mã; chuyển tiếp luồng có chọn lọc; cũng như kết hợp hoặc ghi âm thanh và video. Đối với cuộc gọi nhiều bên, bạn cần cân nhắc một số vấn đề, đặc biệt là cách hiển thị nhiều đầu vào video và kết hợp âm thanh từ nhiều nguồn. Các nền tảng đám mây, chẳng hạn như vLine, cũng cố gắng tối ưu hoá việc định tuyến lưu lượng truy cập.

Bạn có thể mua một gói phần cứng MCU hoàn chỉnh hoặc tự tạo gói phần cứng MCU.

Hình ảnh mặt sau của Cisco MCU5300
Mặt sau của MCU Cisco

Có một số lựa chọn phần mềm MCU nguồn mở. Ví dụ: Licode (trước đây là Lynckia) tạo ra một MCU nguồn mở cho WebRTC. OpenTok có Mantis.

Không chỉ trình duyệt: VoIP, điện thoại và nhắn tin

Bản chất chuẩn hoá của WebRTC cho phép thiết lập giao tiếp giữa một ứng dụng WebRTC chạy trong trình duyệt và một thiết bị hoặc nền tảng chạy trên một nền tảng giao tiếp khác, chẳng hạn như điện thoại hoặc hệ thống hội nghị truyền hình.

SIP là một giao thức báo hiệu được các hệ thống VoIP và hội nghị truyền hình sử dụng. Để cho phép giao tiếp giữa ứng dụng web WebRTC và ứng dụng SIP, chẳng hạn như hệ thống hội nghị truyền hình, WebRTC cần có máy chủ proxy để dàn xếp tín hiệu. Tín hiệu phải đi qua cổng, nhưng sau khi thiết lập giao tiếp, lưu lượng truy cập SRTP (video và âm thanh) có thể chuyển trực tiếp giữa các máy ngang hàng.

Mạng điện thoại chuyển mạch công cộng (PSTN) là mạng chuyển mạch vòng của tất cả điện thoại analog "cũ". Đối với các cuộc gọi giữa ứng dụng web WebRTC và điện thoại, lưu lượng truy cập phải đi qua một cổng PSTN. Tương tự, các ứng dụng web WebRTC cần có một máy chủ XMPP trung gian để giao tiếp với các điểm cuối Jingle, chẳng hạn như ứng dụng nhắn tin nhanh. Jingle được Google phát triển dưới dạng một tiện ích mở rộng cho XMPP để hỗ trợ tính năng gọi thoại và video cho các dịch vụ nhắn tin. Các phương thức triển khai WebRTC hiện tại dựa trên thư viện libjingle C++, một phương thức triển khai Jingle ban đầu được phát triển cho Talk.

Một số ứng dụng, thư viện và nền tảng tận dụng khả năng giao tiếp của WebRTC với thế giới bên ngoài:

  • sipML5: một ứng dụng SIP JavaScript nguồn mở
  • jsSIP: Thư viện SIP JavaScript
  • Phono: API điện thoại JavaScript nguồn mở được tạo dưới dạng trình bổ trợ
  • Zingaya: một tiện ích điện thoại có thể nhúng
  • Twilio: thoại và nhắn tin
  • Uberconference: hội nghị

Các nhà phát triển sipML5 cũng đã xây dựng cổng webrtc2sip. Tethr và Tropo đã trình bày một khung giao tiếp trong trường hợp thiên tai "trong một chiếc cặp" bằng cách sử dụng thiết bị OpenBTS để cho phép giao tiếp giữa điện thoại phổ thông và máy tính thông qua WebRTC. Đó là giao tiếp qua điện thoại mà không cần nhà mạng!

Tìm hiểu thêm

Lớp học lập trình WebRTC cung cấp hướng dẫn từng bước về cách tạo ứng dụng trò chuyện bằng văn bản và video bằng dịch vụ báo hiệu Socket.io chạy trên Node.

Bài trình bày về WebRTC tại Google I/O năm 2013 với trưởng nhóm kỹ thuật WebRTC, Justin Uberti

Bài trình bày của Chris Wilson về SFHTML5 – Giới thiệu về ứng dụng WebRTC

Cuốn sách WebRTC: API và giao thức RTCWEB của web theo thời gian thực HTML5 gồm 350 trang cung cấp nhiều thông tin chi tiết về dữ liệu và đường dẫn tín hiệu, đồng thời bao gồm một số sơ đồ chi tiết về cấu trúc liên kết mạng.

WebRTC và tín hiệu: Những gì chúng tôi đã học được trong hai năm qua – Bài đăng trên blog của TokBox về lý do việc loại bỏ tín hiệu khỏi thông số kỹ thuật là một ý tưởng hay

Hướng dẫn thực tế về cách xây dựng ứng dụng WebRTC của Ben Strong cung cấp nhiều thông tin về cấu trúc và hạ tầng WebRTC.

Chương WebRTC trong cuốn Mạng duyệt web hiệu suất cao của Ilya Grigorik đi sâu vào cấu trúc, trường hợp sử dụng và hiệu suất của WebRTC.