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

Báo hiệu là gì?

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

  • Thông báo kiểm soát phiên được dùng để mở hoặc đóng giao tiếp
  • Thông báo lỗi
  • Siêu dữ liệu về 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 đa phương tiện
  • Dữ liệu chính 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ủ lưu trữ mà thế giới bên ngoài nhìn thấy

Quá trình báo hiệu này cần có cách để khách hàng truyền thông điệp qua lại. Cơ chế đó không được API WebRTC triển khai. Bạn cần tự xây dựng chiến dịch đó. Trong phần sau của bài viết này, bạn sẽ tìm hiểu các cách xây dựng dịch vụ tín hiệu. Tuy nhiên, trước hết bạn cần một chút 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ệ có sẵn, các phương thức báo hiệu và giao thức không được chỉ định theo tiêu chuẩn WebRTC. Phương pháp này được trình bày trong Giao thức thiết lập phiên JavaScript (JSEP):

Kiến trúc của JSEP cũng tránh được việc trình duyệt 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 vấn đề nếu dữ liệu báo hiệu bị mất mỗi lần tải lại một trang. Thay vào đó, trạng thái báo hiệu có thể được lư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ề ưu đãicâu trả lời, siêu dữ liệu nội dung đa phương tiện nêu trên. Ưu đãi và câu trả lời được thông báo ở đị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 điều khó nhằn của SDP này thực sự có nghĩa là gì? Hãy xem các ví dụ về Lực lượng chuyên trách kỹ thuật Internet (IETF).

Lưu ý rằng WebRTC được thiết kế sao cho 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 giá trị trong văn bản SDP. Ví dụ: bạn có thể dùng hàm preferAudioCodec() trong appr.tc để thiết lập bộ mã hoá và giải mã mặc định và tốc độ bit. SDP hơi khó thao tác bằng JavaScript và có cuộc thảo luận về việc 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 gắn bó với SDP.

API RTCPeerConnection 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 dùng để tạo kết nối giữa các ứng dụng ngang hàng cũng như giao tiếp âm thanh và video.

Để khởi chạy quá trình này, RTCPeerConnection có hai tác vụ:

  • Xác định các điều kiện truyền thông tại địa phương, chẳng hạn như độ phân giải và khả năng của bộ mã hoá và giải mã. Đây là siêu dữ liệu dùng cho cơ chế đưa ra 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òn gọi là ứng viên.

Sau khi dữ liệu cục bộ này đã được chắc chắn, dữ liệu đó phải được trao đổi thông qua cơ chế báo hiệu với ứng dụng ngang hàng từ xa.

Hãy tưởng tượng Alice đang cố gọi cho Eve. Sau đây là toàn bộ cơ chế ưu đãi/trả lời đầy đủ và chi tiết đẫm máu:

  1. Alice tạo một đối tượng RTCPeerConnection.
  2. Alice tạo một ưu đãi (nội dung mô tả phiên SDP) bằng phương thức RTCPeerConnection createOffer().
  3. Alice gọi cho setLocalDescription() để cung cấp lời đề nghị.
  4. Alice xâu chuỗi nội dung lời đề nghị rồi dùng một cơ chế báo hiệu để gửi lời đề nghị đó cho Eve.
  5. Eve gọi cho setRemoteDescription() bằng lời đề nghị của Alice để RTCPeerConnection của cô ấy 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 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 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 dạng chuỗi của cô 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 cách sử dụ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" là quá trình tìm kiếm các giao diện và cổng mạ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ó sẵn các mạng đề xuất.
  3. Trong trình xử lý, Alice gửi dữ liệu ứng viên đã chuỗi thành chuỗi cho Eve thông qua kênh tín hiệu của họ.
  4. Khi nhận được tin nhắn về ứng viên từ Alice, cô gọi addIceCandidate() để thêm ứng viên đó vào phần mô tả từ xa về ứng viên.

JSEP hỗ trợ ICE Suggested Trickling (Báo cáo ứng viên ICE), cho phép phương thức gọi cung cấp dần các ứng viên cho phương thức gọi sau lời đề nghị ban đầu và để người được gọi bắt đầu hành động trong cuộc gọi cũng như thiết lập kết nối mà không cần chờ tất cả ứng viên đến.

Mã WebRTC để báo hiệu

Đoạn mã sau đây là một ví dụ về mã W3C tóm tắt toàn bộ quá trình truyền tín hiệu. Mã này giả định sự tồn tại của một cơ chế báo hiệu nào đó là SignalingChannel. Tín hiệu sẽ được thảo luận chi tiết hơn ở 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 cách thực hiện của các quy trình đưa ra lời đề nghị/câu trả lời và trao đổi ứng viên, hãy truy cập vào simpl.info RTCPeerConnection và xem nhật ký bảng điều khiển để xem ví dụ về cuộc trò chuyện video một trang. Nếu muốn biết thêm, bạn có thể tải tệp kết xuất đầy đủ về tín hiệu và số liệu thống kê WebRTC xuống trên trang about://webrtc-internals trong Google Chrome hoặc trang Opera://webrtc-internals trong Opera.

Khám phá ứng dụng ngang hàng

Đây là một cách thú vị để hỏi "Làm cách nào để tìm ai đó để trò chuyện?"

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

Cơ chế khám phá ứng dụng ngang hàng không được WebRTC xác định và bạn sẽ không chuyển đến các tuỳ chọn ở đây. Quy trình này có thể đơn giản như gửi email hoặc nhắn tin cho một URL. Đối với các ứng dụng trò chuyện video, chẳng hạn như Talky, tawk.toBrowser Testing, 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 tùy chỉnh. Nhà phát triển Chris Ball đã xây dựng một thử nghiệm hấp dẫn serverless-webrtc, 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ọ thích, chẳng hạn như tin nhắn nhanh, email hoặc chim bồ câu thuê.

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

Xin nhắc lại rằng những 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 thì bạn cũng cần có một máy chủ trung gian để trao đổi thông báo tín hiệu và dữ liệu ứng dụng giữa các ứng dụng. Thật đáng tiếc, một ứng dụng web không thể chỉ đơn giản kêu gọi mọi người trên Internet: "Hãy kết nối tôi với bạn bè!"

Tin nhắn báo hiệu rất may là các tin nhắn nhỏ và chủ yếu được trao đổi khi bắt đầ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-45 tin nhắn, với tổng kích thước của tất cả các tin nhắn là khoảng 10 KB.

Ngoài việc tương đối không đòi hỏi về băng thông, các dịch vụ báo hiệu WebRTC không tốn nhiều quá trình xử lý hoặc bộ nhớ vì các dịch vụ này chỉ cần chuyển tiếp tin nhắn và giữ lại một lượng nhỏ dữ liệu về 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 máy khách

Dịch vụ thông báo để báo hiệu cần phải diễn ra hai chiều: máy khách đến máy chủ và máy chủ đến máy khách. Hoạt động giao tiếp hai chiều đi ngược lại với mô hình yêu cầu/phản hồi HTTP giữa máy khách/máy chủ, nhưng nhiều hình thức tấn công khác nhau (chẳng hạn như thăm dò ý kiến trong thời gian dài) đã đượ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 ứng dụng web đang chạy trong trình duyệt.

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

WebSocket là một giải pháp tự nhiên hơn, được thiết kế cho giao tiếp giữa máy khách–máy chủ song công – các thông báo có thể chuyển theo cả hai hướng cùng lúc. Một ưu điểm của dịch vụ báo hiệu được xây dựng 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 cho 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ả các trình duyệt hiện đại, ngoại trừ Opera Mini, đều hỗ trợ WebSocket và quan trọng hơn là tất cả cá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 mọi kết nối để đảm bảo thông báo không bị chặn mà không bị mã hoá, đồng thời để giảm các sự cố khi truyền tải qua proxy. (Để biết thêm thông tin về WebSocket và truyền tải proxy, hãy xem chương WebRTC trong Mạng trình 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 ứng dụng WebRTC thăm dò máy chủ nhắn tin nhiều lần qua Ajax. Tuy nhiên, điều đó dẫn đến rất nhiều yêu cầu mạng dư thừa, đặc biệt là vấn đề xảy ra với thiết bị di động. Ngay cả sau khi đã thiết lập một phiên, các ứng dụng ngang hàng vẫn cần thăm dò ý kiến để xem thông báo báo hiệu trong trường hợp có thay đổi hoặc chấm dứt phiên bởi các ứng dụng ngang hàng khác. 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ò.

Báo hiệu 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 các 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 điệp từ các vị trí khác nhau với mức độ đồng thời cao. Các ứng dụng WebRTC có nhiều lưu lượng truy cập cần máy chủ báo hiệu có khả năng xử lý tải đáng kể. Bạn không đi vào chi tiết ở đây, nhưng có một số cách để gửi tin nhắn khối lượng lớn và hiệu suất cao, bao gồm các lựa chọn sau đây:

  • Giao thức hiện diện và thông báo mở rộng (XMPP), ban đầu được gọi là giao thức Jabber-a, được phát triển để gửi tin nhắn nhanh có thể dùng để báo hiệu (Các hoạt động 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 hoạt động phát trực tuyến hai chiều, nhưng vì nhiều lý do, BOSH có thể không hiệu quả như WebSocket và có thể sẽ không mở rộng hiệu quả vì cùng lý do.) (Trên đường tiếp tuyến, Jingle là một tiện ích SAML để 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 – bản triển khai C++ của Jingle.)

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

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

  • Các 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 Lesgetter cung cấp danh sách toàn diện về các dịch vụ và thư viện nhắn tin.)

Xây dựng dịch vụ báo hiệu bằng Socket.io trên Node

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

Socket.io sử dụng WebSocket với tính năng dự phòng: thăm dò ý kiến dài AJAX, truyền trực tuyến nhiều phần AJAX, Iframe Forever và thăm dò JSONP. API này đã được chuyển sang nhiều phần phụ trợ, nhưng có lẽ nổi tiếng nhất với phiên bản Nút được sử dụng trong ví dụ này.

Không có WebRTC trong ví dụ này. Hộp cát về quyền riêng tư được thiết kế để chỉ cho thấy cách xây dựng tín hiệu thành ứng dụng web. Hãy xem nhật ký bảng điều khiển để biết những gì đang xảy ra khi khách hàng tham gia một phòng và trao đổi tin nhắn. Lớp học lập trình về WebRTC này cung cấp hướng dẫn từng bước về cách tích hợp tiện ích 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);
});

Sau đâ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 phải tìm hiểu về nút tĩnh cho việc này. Nó chỉ được sử dụng trong ví dụ này.)

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

npm install socket.io
npm install node-static

Để khởi động máy chủ, hãy chạy lệnh sau từ một cửa sổ dòng lệnh trong thư mục ứng dụng của bạn:

node server.js

Trên trình duyệt, hãy mở localhost:2013. Mở thẻ hoặc cửa sổ mới trong trình duyệt bất kỳ 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 này thông qua Công cụ cho nhà phát triển Google Chrome bằng Ctrl+Shift+J (hoặc Command+Option+J trên máy Mac).

Dù bạn chọn phương pháp báo hiệu nào, chương trình phụ trợ và ứng dụng khách của bạn (ít nhất) cũng cần phải cung cấp các dịch vụ tương tự như ví dụ này.

Vấn đề về tín hiệu

  • RTCPeerConnection sẽ không bắt đầu thu thập ứng viên cho đến khi setLocalDescription() được gọi. Đây là yêu cầu bắt buộc trong bản nháp IETF của JSEP.
  • Hãy tận dụng Trickle ICE. Gọi addIceCandidate() ngay khi ứng viên đến.

Máy chủ báo hiệu được thiết lập sẵn

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

  • webRTC.io là một trong những thư viện trừu tượng đầu tiên dành cho WebRTC.
  • Signalmaster là một máy chủ tín 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ã nào cả, bạn có thể cung cấp các nền tảng WebRTC thương mại hoàn chỉnh do các công ty cung cấp, chẳng hạn như vLine, OpenTokAsterisk.

Để đảm bảo lưu trữ hồ sơ, Eric đã xây dựng máy chủ tín hiệu bằng PHP trên Apache trong thời gian đầu của WebRTC. Mã này hiện đã lỗi thời, nhưng bạn vẫn nên xem xét mã này nếu đang xem xét một mã tương tự.

Bảo mật bằng tín hiệu

"Bảo mật là nghệ thuật làm cho không có gì xảy ra."

Salman Rushdie

Việc mã hoá là bắt buộc đối với mọi thành phần WebRTC.

Tuy nhiên, các cơ chế báo hiệu không được xác định theo tiêu chuẩn WebRTC. Do đó, bạn có quyền quyết định việc bảo mật tín hiệu. Nếu xâm nhập được tín hiệu, kẻ tấn công có thể dừng các phiên truy cập, chuyển hướng kết nối, cũng như 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 các thông báo không thể bị chặn không mã hoá. Ngoài ra, hãy cẩn thận để không phát đi thông báo tín hiệu theo cách mà những người gọi khác có thể truy cập chúng qua cùng một máy chủ tín hiệu.

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

Để 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. Tuy nhiên, đối với hoạt động truyền trực tuyến dữ liệu và nội dung nghe nhìn thực tế sau khi một phiên được thiết lập, RTCPeerConnection sẽ cố gắng kết nối trực tiếp hoặc ngang hàng với các ứng dụ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 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

Trong thực tế, hầu hết thiết bị đều hoạt động 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, cũng như nhiều thiết bị cài đặt 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 cùng một thiết bị triển khai, chẳng hạn như bộ định tuyến WIFI tại nhà.

Các ứng dụng ngang hàng sử dụng NAT và tường lửa
Thế giới thực

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

ICE cố gắng tìm ra cách tốt nhất để kết nối với đồng nghiệp. Thử nghiệm song song tất cả các khả năng và chọn tuỳ chọn hiệu quả nhất và hoạt động hiệu quả. Trước tiên, ICE cố gắng kết nối bằng địa chỉ máy chủ lưu trữ 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ị cài đặt NAT), ICE sẽ nhận được một đị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 qua máy chủ chuyển tiếp TURN.

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

Mọi máy chủ TURN đều hỗ trợ STUN. Máy chủ TURN là một máy chủ STUN có thêm chức năng chuyển tiếp tích hợp sẵn. ICE cũng đối phó với sự phức tạp của việc thiết lập NAT. Trên thực tế, kỹ thuật "phá lỗ hổng" NAT có thể không chỉ đòi hỏi địa chỉ IP:cổng công khai.

URL cho máy chủ STUN và/hoặc TURN là (không bắt buộc) do một ứ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'
    }
  ]
}

Sau khi RTCPeerConnection có thông tin đó, ICE sẽ tự động diễn ra. RTCPeerConnection sử dụng khung ICE để xác định lộ trình tốt nhất giữa các ứng dụng ngang hàng bằng cách làm việc với các máy chủ STUN và TURN nếu cần.

NGỰC

NAT cung cấp cho thiết bị địa chỉ IP để sử dụng trong mạng cục bộ riêng tư, nhưng không thể dùng địa chỉ này bên ngoài. Nếu không có địa chỉ công khai, thì các ứng dụng WebRTC sẽ không có cách nào giao tiếp với nhau. Để khắc phục sự cố 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 tác 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 lại địa chỉ đó 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:cổng từ góc nhìn công khai. Quy trình này cho phép ứng dụng ngang hàng WebRTC nhận địa chỉ có thể truy cập công khai cho chính mình rồi truyền địa chỉ đó cho ứng dụng 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 những cách khác nhau 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 cần phải làm gì nhiều hoặc phải 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.

Hầu hết các lệnh gọi WebRTC đều tạo kết nối thành công bằng STUN – 86% theo Webrtcstats.com, mặc dù con số này có thể ít hơn đối với các lệnh gọi giữa các đồng nghiệp 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 để nhận địa chỉ IP:cổng công khai

Xoay

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

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

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

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

Sơ đồ này cho thấy tính năng TURN trong thực tế. Pure STUN không thành công, do đó, mỗi nhóm ứng dụng ngang hàng đều phải sử dụng máy chủ TURN.

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

Để thử nghiệm, Google chạy máy chủ STUN công khai stun.l.google.com:19302, do appr.tc sử dụng. Đối với dịch vụ STUN/TAG chính thức, hãy sử dụng rfc5766-Turn-server. Mã nguồn cho các máy chủ STUN và TURN có trên GitHub, tại đó 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ề việc cài đặt máy chủ. Ngoài ra, 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à máy chủ lưu trữ, có sẵn dưới dạng mã nguồn và cũng có thể dùng cho AWS. Dưới đây là hướng dẫn về cách thiết lập chế độ nghỉ trên Compute Engine.

  1. Mở tường lửa khi cần thiết cho tcp=443, udp/tcp=3478.
  2. Tạo bốn thực thể, mỗi thực thể cho một 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 phần còn lại từ creytiv.com/restund.html rồi 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à phần còn lại.
  9. Điều chỉnh restund.conf cho phù hợp với nhu cầu của bạn (thay thế địa chỉ IP và đảm bảo địa chỉ 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 chế độ nghỉ:
    1. Đặt LD_LIBRARY_PATH.
    2. Sao chép restund.conf vào /etc/restund.conf.
    3. Đặt restund.conf để sử dụng đúng 10. Địa chỉ IP.
  12. Chạy chế độ nghỉ
  13. Kiểm thử bằng ứng dụng dự phòng từ máy từ xa: ./client IP:port

Không chỉ hỗ trợ một với một: WebRTC đa bên

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

Thật dễ dàng hình dung những trường hợp sử dụng phát trực tuyến nội dung đa phương tiện không chỉ là cuộc gọi trực tiếp đơ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 cộng có một diễn giả với hàng trăm hoặc hàng triệu người xem.

Ứng dụng WebRTC có thể 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, chẳng hạn như talky.io áp dụng, và hoạt động rất hiệu quả đối với một số ít các ứng dụng ngang hàng. Ngoài ra, việ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-way nhỏ
Cấu trúc liên kết lưới đầy đủ: Mọi người đều kết nối với mọi người

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 thiết bị khác trong cấu hình dấu sao. Bạn cũng có thể chạy đ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. Nhờ vậy, các cấu trúc sẽ linh hoạt hơn vì nó cho phép một ứng dụng web xử lý việc định tuyến lệnh gọi bằng cách chọn kết nối ngang hàng khác. Để xem cách hoạt động này của ứng dụng, hãy xem phần Chuyển tiếp kết nối ngang hàng cho mẫu WebRTCMẫu WebRTC Nhiều kết nối ngang hàng.

Thiết bị điều khiển đa điểm

Một lựa chọn phù hợp hơn cho số lượng lớn đ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 hội nghị truyền hình; xử lý quá trình chuyển mã, chuyển tiếp luồng có chọn lọc và kết hợp hoặc ghi âm thanh với video. Đối với lệnh 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 gói phần cứng MCU hoàn chỉnh hoặc tự chế tạo.

Mặt sau của Cisco MCU5300
Mặt sau của Cisco MCU

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

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

Bản chất chuẩn của WebRTC giúp bạn có thể thiết lập hoạt động giao tiếp giữa ứng dụng WebRTC chạy trong trình duyệt và thiết bị hoặc nền tảng chạy trên một nền tảng liên lạc 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à 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, một khi giao tiếp đã được thiết lập, lưu lượng SRTP (video và âm thanh) có thể lưu chuyển trực tiếp 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 cho tất cả các điện thoại tương tự "cũ thuần tuý". Đối với các cuộc gọi giữa điện thoại và ứng dụng web WebRTC, lưu lượng truy cập phải đi qua cổng PSTN. Tương tự như vậy, các ứng dụng web WebRTC cần có một máy chủ SAML 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 tức thì. Jingle được Google phát triển dưới dạng một phần mở rộng cho giao thức SAML để hỗ trợ tính năng thoại và video cho các dịch vụ nhắn tin. Các hoạt động triển khai WebRTC hiện tại dựa trên thư viện libjingle C++. Đây là một cách triển khai của Jingle được phát triển ban đầu cho Talk.

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

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

Các nhà phát triển sipML5 cũng đã tạo cổng webrtc2sip. Tethr và Tropo đã minh hoạ một khung hỗ trợ truyền thông về thảm hoạ "trong một chiếc cặp" bằng cách dùng tế bào 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à liên lạc qua điện thoại không có nhà mạng!

Tìm hiểu thêm

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

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

Bài thuyết trình SFHTML5 của Chris Wilson - Giới thiệu về ứng dụng WebRTC

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

WebRTC và tín hiệu: Những điều đã giúp chúng tôi nắm được hai năm

Hướng dẫn thực hành về xây dựng ứng dụng WebRTC củaBen Strong cung cấp nhiều thông tin về cấu trúc liên kết và cơ sở hạ tầng WebRTC.

Chương WebRTC trong Ilya Grigorik Kết nối mạng cho trình duyệt hiệu suất cao đi sâu vào kiến trúc WebRTC, các trường hợp sử dụng và hiệu suất.