完整的 WebRTC 呼叫流程

图5-1、5-2 和 5-3 提供了与完整的 WebRTC 呼叫流程图,该流程涉及一个信道发起方,一个信道连接器以及在信道建立时在它们之间中继消息的信令服务器。

图5-1

图5-1 WebRTC 调用流程:序列图,第 1 部分

图5-2

图5-2 WebRTC 调用流程:序列图,第 2 部分

图5-3

图5-3 WebRTC 调用流程:序列图,第 3 部分

序列图通过以下步骤变化:

  1. Initiator 连接到服务器,并使其创建信令通道。
  2. Initiator(在获得用户同意后)可以访问用户的媒体。
  3. Joiner 连接到服务器并加入频道。
  4. 当 Joiner 还可以访问本地用户的媒体时,将通过服务器将一条消息发送给 Initiator ,从而触发协商过程:
    • 发起方创建 PeerConnection ,向其添加本地流,创建 SDP offer,然后通过信令服务器将其发送到 Joiner。
    • 收到 SDP offer 后,Joiner 会通过创建 PeerConnection 对象,向其添加本地流并构建 SDP answer 以(通过服务器)发送回远程方来镜像发起方的行为。
  5. 在协商过程中,双方利用信令服务器交换网络可达性信息(以 ICE 协议候选地址的形式)。
  6. 当 Initiator 收到 Joiner 对其自己的offer 返回的 answer 时,协商过程结束:
    • 双方通过利用各自的 PeerConnection 对象切换到对等通信,PeerConnection 对象还配备了可用于直接交换文本消息的数据通道(data channel)。

在以下各节中,我们将通过详细分析每个步骤来逐步完成这些步骤。 在进行此操作之前,让我们介绍作为本章的运行示例而设计的简单 Web 应用程序。 HTML 代码在 例5-1 中。

例5-1 简单的 WebRTC 应用程序

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Very simple WebRTC application with a Node.js signaling server</title>
  5. </head>
  6. <body>
  7. <div id='mainDiv'>
  8. <table border="1" width="100%">
  9. <tr>
  10. <th>
  11. Local video
  12. </th>
  13. <th>
  14. Remote video
  15. </th>
  16. </tr>
  17. <tr>
  18. <td>
  19. <video id="localVideo" autoplay></video>
  20. </td>
  21. <td>
  22. <video id="remoteVideo" autoplay></video>
  23. </td>
  24. </tr>
  25. <tr>
  26. <td align="center">
  27. <textarea rows="4" cols="60"id="dataChannelSend" disabledplaceholder="This will be enabled once the data channel is up..."></textarea>
  28. </td>
  29. <td align="center">
  30. <textarea rows="4" cols="60"id="dataChannelReceive" disabled></textarea>
  31. </td>
  32. </tr>
  33. <tr>
  34. <td align="center">
  35. <button id="sendButton" disabled>Send</button>
  36. </td>
  37. <td>
  38. </td>
  39. </tr>
  40. </table>
  41. </div>
  42. <script src='/socket.io/socket.io.js'></script>
  43. <script src='js/lib/adapter.js'></script>
  44. <script src='js/completeNodeClientWithDataChannel.js'></script>
  45. </body>
  46. </html>

本地视频以及本地数据通道信息显示在页面的左侧,而远程视频和数据则显示在窗口的右侧。 该页面涉及三个脚本文件,第一个是已经介绍的 socket.io 库(请参阅第66页的 “ socket.io JavaScript 库” )。 至于第二个文件(adapter.js),它是一个方便的 JavaScript 填充程序库,可通过适当抽象浏览器前缀以及其他浏览器差异和供应商当前解释规格的方式更改,来帮助程序员。 最后,completeNodeClientWithDataChannel.js 包含实际的客户端代码,并在 例5-2 中完整介绍了该示例,以使读者受益。 在本章的其余部分中,我们将深入研究该文件的详细信息。

例5-2 completeNodeClientWithDataChannel.js

<<< @/js/completeNodeClientWithDataChannel.js

根据 第4章 中包含的信息,读者在理解信令服务器的行为时应该不会遇到任何问题,信令服务器已作为 Node.js 应用程序编写,其代码复制如下:

  1. var static = require('node-static');
  2. var http = require('http');
  3. // Create a node-static server instance
  4. var file = new(static.Server)();
  5. // We use the http module’s createServer function and
  6. // rely on our instance of node-static to serve the files
  7. var app = http.createServer(function (req, res) {
  8. file.serve(req, res);
  9. }).listen(8181);
  10. // Use socket.io JavaScript library for real-time web applications
  11. var io = require('socket.io').listen(app);
  12. // Let's start managing connections...
  13. io.sockets.on('connection', function (socket) {
  14. // Handle 'message' messages
  15. socket.on('message', function (message) {
  16. log('S --> got message: ', message);
  17. // channel-only broadcast...
  18. socket.broadcast.to(message.channel).emit('message', message);
  19. });
  20. // Handle 'create or join' messages
  21. socket.on('create or join', function (room) {
  22. var numClients = io.sockets.clients(room).length;
  23. log('S --> Room ' + room + ' has ' + numClients + ' client(s)');
  24. log('S --> Request to create or join room', room);
  25. // First client joining...
  26. if (numClients == 0) {
  27. socket.join(room);
  28. socket.emit('created', room);
  29. } else if (numClients == 1) {
  30. // Second client joining...
  31. io.sockets.in(room).emit('join', room);
  32. socket.join(room);
  33. socket.emit('joined', room);
  34. } else {
  35. // max two clients
  36. socket.emit('full', room);
  37. }
  38. });
  39. function log() {
  40. var array = [">>> "];
  41. for (var i = 0; i < arguments.length; i++) {
  42. array.push(arguments[i]);
  43. }
  44. socket.emit('log', array);
  45. }
  46. });

基本上,服务器负责两种通道管理操作(在收到发起方的请求后创建,在第二个对等方到达时加入)和消息中继(在会话建立时)。 正如已经预期的那样,在成功实例化共享信令通道的两个浏览器之间的对等会话之后,它立即完成任务。

现在,让我们开始完整的 WebRTC 示例演练。