你将学到

通过本节,你将学到:

  • 使用npm安装package.json指定的工程依赖
  • 运行Node.js server 并使用node-static来服务静态文件
  • 使用Socket.IO在Node.js上构建消息服务
  • 用这些来创建“rooms”并交换消息

本节的所有内容位于 step-04 文件夹

概述

为了建立并维护WebRTC通话,WebRTC客户端(peers)需要交换元数据(metadata):

  • candidate(network)信息
  • Offeranswer 消息,提供媒体的相关信息,如分辨率和编解码器

换句话讲,在端到端(peer-to-peer)进行音频、视频或数据流传输之前,必须交换metadata,这个过程称为信令

之前的章节,发送端和接收端的RTCPeerConnection位于同一个页面,所以“信令”简化为在对象之间传递metadata。

在真实使用场景,发送端和接收端的RTCPeerConnection运行于不同设备的web页面,你需要一种方式让他们交换metadata。

为此,使用信令服务器:一个可以在WebRTC客户端(peer)之间传递消息的服务器。实际的消息是纯文本的:字符串化的JavaScript对象。

安装Node.js

为了运行本节和后面的例子(文件夹step-04到step-06),你需要使用Node.js运行一个本地服务器。

你可以从本 链接 下载并安装Node.js,或者通过 包管理

一旦安装成功,你可以导入下一步所需的依赖项(运行 npm install),以及运行本地服务器来执行codelab(运行 node index.js)。

关于app

WebRTC使用用户端JavaScript API,但对于现实世界使用场景需要一个信令(消息)服务,STUN和TURN服务器。你可从 这里 了解更多。

本节有将构建一个简单的Node.js信令服务器,使用Node.js Socket.IO模块和JavaScript消息库。有Node.js和Socket.IO相关经验很有用,但不是必须;这些消息组件都非常简单。

选择正确的信令服务器

codelab使用 Socket.IO 作为信令服务器

Socket.IO的设计使得构建一个交换消息的服务变得简单,而Socket.IO由于其内置的“rooms”概念而适合学习WebRTC信令。

然而,对于生产服务来说,有更好的选择。请参阅 如何为下一个WebRTC项目选择信令协议

本示例中,index.js实现服务器(Node.js程序)部分,index.html实现运行于服务之上的客户端部分。

本节的Node.js程序有两个任务。

首先,它充当消息中继(relay):

1
2
3
4
socket.on('message', function (message) {
  log('Got message: ', message);
  socket.broadcast.emit('message', message);
});

其次,它管理WebRTC视频通话的房间(rooms):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
if (numClients === 0) {
  socket.join(room);
  socket.emit('created', room, socket.id);
} else if (numClients === 1) {
  socket.join(room);
  socket.emit('joined', room, socket.id);
  io.sockets.in(room).emit('ready');
} else { // max two clients
  socket.emit('full', room);
}

我们的WebRTC程序比较简单,将允许最多2个peers共享共享房间。

HTML & JavaScript

更新 index.html 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>

<head>

  <title>Realtime communication with WebRTC</title>

  <link rel="stylesheet" href="css/main.css" />

</head>

<body>

  <h1>Realtime communication with WebRTC</h1>

  <script src="/socket.io/socket.io.js"></script>
  <script src="js/main.js"></script>

</body>

</html>

完成这一步你会发现页面上没有任何东西:所有的日志输出到了浏览器console。

替换 js/main.js 为如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
'use strict';

var isInitiator;

window.room = prompt("Enter room name:");

var socket = io.connect();

if (room !== "") {
  console.log('Message from client: Asking to join room ' + room);
  socket.emit('create or join', room);
}

socket.on('created', function(room, clientId) {
  isInitiator = true;
});

socket.on('full', function(room) {
  console.log('Message from client: Room ' + room + ' is full :^(');
});

socket.on('ipaddr', function(ipaddr) {
  console.log('Message from client: Server IP address is ' + ipaddr);
});

socket.on('joined', function(room, clientId) {
  isInitiator = false;
});

socket.on('log', function(array) {
  console.log.apply(console, array);
});

基于Node.js构建Socket.IO

你可能看到我们在HTML里使用的Socket.IO:

1
<script src="/socket.io/socket.io.js"></script>

在工作目录创建 package.json 文件,输入如下内容:

1
2
3
4
5
6
7
8
9
{
  "name": "webrtc-codelab",
  "version": "0.0.1",
  "description": "WebRTC codelab",
  "dependencies": {
    "node-static": "^0.7.10",
    "socket.io": "^1.2.0"
  }
}

这是一个应用程序清单,告诉 Node Package Manager (npm) 要安装哪些项目依赖项。

在命令行终端,你的工作目录下运行如下命令来安装依赖项(例如 /socket.io/socket.io.js):

1
npm install

你将看到类似如下显示的安装日志: node-package-installation.jpeg

在工作目录新建 index.js 文件(注意不是在js目录),并添加如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
'use strict';

var os = require('os');
var nodeStatic = require('node-static');
var http = require('http');
var socketIO = require('socket.io');

var fileServer = new(nodeStatic.Server)();
var app = http.createServer(function(req, res) {
  fileServer.serve(req, res);
}).listen(8080);

var io = socketIO.listen(app);
io.sockets.on('connection', function(socket) {

  // convenience function to log server messages on the client
  function log() {
    var array = ['Message from server:'];
    array.push.apply(array, arguments);
    socket.emit('log', array);
  }

  socket.on('message', function(message) {
    log('Client said: ', message);
    // for a real app, would be room-only (not broadcast)
    socket.broadcast.emit('message', message);
  });

  socket.on('create or join', function(room) {
    log('Received request to create or join room ' + room);

    var clientsInRoom = io.sockets.adapter.rooms[room];
    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;

    log('Room ' + room + ' now has ' + numClients + ' client(s)');

    if (numClients === 0) {
      socket.join(room);
      log('Client ID ' + socket.id + ' created room ' + room);
      socket.emit('created', room, socket.id);

    } else if (numClients === 1) {
      log('Client ID ' + socket.id + ' joined room ' + room);
      io.sockets.in(room).emit('join', room);
      socket.join(room);
      socket.emit('joined', room, socket.id);
      io.sockets.in(room).emit('ready');
    } else { // max two clients
      socket.emit('full', room);
    }
  });

  socket.on('ipaddr', function() {
    var ifaces = os.networkInterfaces();
    for (var dev in ifaces) {
      ifaces[dev].forEach(function(details) {
        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
          socket.emit('ipaddr', details.address);
        }
      });
    }
  });
});

在命令行终端,工作目录下,运行如下命令:

1
node index.js

浏览器打开 localhost:8080

每次打开该URL,将弹出提示框输入 room name. 输入相同的房间号加入同一个房间,比如输入“foo”.

新建浏览器tab页,打开 localhost:8080 并输入相同的房间号。

再次新建tab页打开 localhost:8080,输入相同的房间号。

查看每个tab页的浏览器终端,你将看到JavaScript的日志。

加分项(Bonus points)

  1. 可能有哪些替代的消息传递机制?使用“纯”WebSocket可能会遇到哪些问题?
  2. 扩展此应用程序可能涉及哪些问题?你能开发一种方法来测试成千上万的同时房间请求吗?
  3. 此应用程序使用JavaScript提示获取文件室名称。找出一种从URL获取房间名称的方法。例如,localhost:8080/foo将给出房间名foo。

了解更多

下一步

如何使用信令使两个用户之间建立peer连接。