音视频流媒体开发-目录
iOS知识点-目录
Android-目录
Flutter-目录
数据结构与算法-目录
uni-pp-目录
5. Nodejs实战
对于我们WebRTC项目而言,nodejs主要是实现信令服务器的功能,客户端和服务器端的交互我们选择websocket作为通信协议,所以该章节的实战以websocket的使用为主。
web客户端的websocket和nodejs服务器端的websocket有一定的差别,所以我们分开两部分进行讲解。
5.1 web客户端 websocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocketAPI 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
WebSocket 属性
以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:
WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
WebSocket 方法
以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
5.2 Nodejs服务器 websocket
简单的说 Node.js 就是运行在服务端的 JavaScript。
服务器端使用websocket需要安装nodejswebsocket
cd 工程目录
# 此刻我们需要执行命令:
sudo npm init
#创建package.json文件,系统会提示相关配置,也可以使用命令:
sudo npm init ‐y
sudo npm install nodejs‐websocket
我们只要关注:
(1)如何创建websocket服务器,通过createServer和listen接口;
(2)如何判断有新的连接进来,createServer的回调函数判断;
(3)如何判断关闭事件,通过on("close", callback) 事件的回调函数;
(4)如何判断接收到数据,通过on("text", callkback)事件的回调函数;
(5)如何判断接收异常,通过on("error", callkback)事件的回调函数;
(6)如何主动发送数据,调用sendText
参考代码
var ws = require("nodejs‐websocket")
// Scream server example: "hi" ‐> "HI!!!"
var server = ws.createServer(function (conn) {
console.log("New connection")
conn.on("text", function (str) { // 收到数据的响应
console.log("Received "+str)
conn.sendText(str.toUpperCase()+"!!!") // 发送
})
conn.on("close", function (code, reason) { // 关闭时的响应
console.log("Connection closed")
})
conn.on("error", function (err) { // 出错
console.log("error:" + err);
});
}).listen(8001)
5.3 websocket聊天室实战
效果展示+框架分析
效果展示
客服端
框架分析
消息类型分为三种:
- enter:新人进入 (蓝色字体显示)
- message:普通聊天信息 (黑色字体显示)
- leave:有人离开 (红色字体显示)
服务器在收到某个客户端的消息(message+enter+leave),然后将其广播给所有的客户端(包括发送者)。
客户端代码
目录和文件名:05/5.3/client.html
<html>
<body>
<h1>Websocket简易聊天</h1>
<div id="app">
<input id="sendMsg" type="text" />
<button id="submitBtn">发送</button>
</div>
</body>
<script type="text/javascript">
//在页面显示聊天内容
function showMessage(str, type) {
var div = document.createElement("div");
div.innerHTML = str;
if (type == "enter") {
div.style.color = "blue";
} else if (type == "leave") {
div.style.color = "red";
}
document.body.appendChild(div);
}
//新建一个websocket
var websocket = new WebSocket("[ws://192.168.221.132:8010");](ws://192.168.221.132:8010/)
//打开websocket连接
websocket.onopen = function () {
console.log("已经连上服务器‐‐‐‐");
document.getElementById("submitBtn").onclick = function () {
var txt = document.getElementById("sendMsg").value;
if (txt) {
//向服务器发送数据
websocket.send(txt);
}
};
};
//关闭连接
websocket.onclose = function () {
console.log("websocket close");
};
//接收服务器返回的数据
websocket.onmessage = function (e) {
var mes = JSON.parse(e.data); // json格式
showMessage(mes.data, mes.type);
};
</script>
</html>
服务器端代码
目录和文件名:05/5.3/server.js
var ws = require("nodejs‐websocket")
var port = 8010;
var user = 0;
// 创建一个连接
var server = ws.createServer(function (conn) {
console.log("创建一个新的连接‐‐‐‐‐‐‐‐");
user++;
conn.nickname="user" + user;
conn.fd="user" + user;
var mes = {};
mes.type = "enter";
mes.data = conn.nickname + " 进来啦"
broadcast(JSON.stringify(mes)); // 广播
//向客户端推送消息
conn.on("text", function (str) {
console.log("回复 "+str)
mes.type = "message";
mes.data = conn.nickname + " 说: " + str;
broadcast(JSON.stringify(mes));
});
//监听关闭连接操作
conn.on("close", function (code, reason) {
console.log("关闭连接");
mes.type = "leave";
mes.data = conn.nickname+" 离开了"
broadcast(JSON.stringify(mes));
});
//错误处理
conn.on("error", function (err) {
console.log("监听到错误");
console.log(err);
});
}).listen(port);
function broadcast(str){
server.connections.forEach(function(connection){
connection.sendText(str);
})
}
5.4 Map实战
因为信令服务器使用map管理房间,所以我们先做个小练习。
主要涉及put/get/remove/size等操作。
目录和文件名:05/5.4/map.js
/** ‐‐‐‐‐ ZeroRTCMap ‐‐‐‐‐ */
var ZeroRTCMap = function () {
this._entrys = new Array();
// 插入
this.put = function (key, value) {
if (key == null || key == undefined) {
return;
}
var index = this._getIndex(key);
if (index == ‐1) {
var entry = new Object();
entry.key = key;
entry.value = value;
this._entrys[this._entrys.length] = entry;
} else {
this._entrys[index].value = value;
}
};
// 根据key获取value
this.get = function (key) {
var index = this._getIndex(key);
return (index != ‐1) ? this._entrys[index].value : null;
};
// 移除key‐value
this.remove = function (key) {
var index = this._getIndex(key);
if (index != ‐1) {
this._entrys.splice(index, 1);
}
};
// 清空map
this.clear = function () {
this._entrys.length = 0;
};
// 判断是否包含key
this.contains = function (key) {
var index = this._getIndex(key);
return (index != ‐1) ? true : false;
};
// map内key‐value的数量
this.size = function () {
return this._entrys.length;
};
// 获取所有的key
this.getEntrys = function () {
return this._entrys;
};
// 内部函数
this._getIndex = function (key) {
if (key == null || key == undefined) {
return ‐1;
}
var _length = this._entrys.length;
for (var i = 0; i < _length; i++) {
var entry = this._entrys[i];
if (entry == null || entry == undefined) {
continue;
}
if (entry.key === key) {// equal
return i;
}
}
return ‐1;
};
}
function Client(uid, conn, roomId) {
this.uid = uid; // 用户所属的id
this.conn = conn; // uid对应的websocket连接
this.roomId = roomId;
console.log('uid:' + uid +', conn:' + conn + ', roomId: ' + roomId);
}
var roomMap = new ZeroRTCMap();
// Math.random() 返回介于 0(包含) ~ 1(不包含) 之间的一个随机数:
// toString(36)代表36进制,其他一些也可以,比如toString(2)、toString(8),代表输出为二进制和八进制。最高支持几进制
// substr(2) 舍去0/1位置的字符
console.log('\n\n‐‐‐‐‐‐‐‐‐‐Math.random() ‐‐‐‐‐‐‐‐‐‐');
var randmo = Math.random();
console.log('Math.random() = ' + randmo);
console.log('Math.random().toString(10) = ' + randmo.toString(10));
console.log('Math.random().toString(36) = ' + randmo.toString(36));
console.log('Math.random().toString(36).substr(0) = ' + randmo.toString(36).substr(0));
console.log('Math.random().toString(36).substr(1) = ' + randmo.toString(36).substr(1));
console.log('Math.random().toString(36).substr(2) = ' + randmo.toString(36).substr(2));
console.log('\n\n‐‐‐‐‐‐‐‐‐‐create client ‐‐‐‐‐‐‐‐‐‐');
var roomId = 100;
var uid1 = Math.random().toString(36).substr(2);
var conn1 = 100;
var client1 = new Client(uid1, conn1, roomId);
var uid2 = Math.random().toString(36).substr(2);
var conn2 = 101;
var client2 = new Client(uid2, conn2, roomId);
// 插入put
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐put‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
console.log('roomMap put client1');
roomMap.put(uid1, client1);
console.log('roomMap put client2');
roomMap.put(uid2, client2);
console.log('roomMap size:' + roomMap.size());
// 获取get
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐get‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
var client = null;
var uid = uid1;
client = roomMap.get(uid);
if(client != null) {
console.log('get client‐>' + 'uid:' + client.uid +', conn:' + client.conn + ', roomId: '+ client.roomId);
} else {
console.log("can't find the client of " + uid);
}
uid = '123345';
client = roomMap.get(uid);
if(client != null) {
console.log('get client‐>' + 'uid:' + client.uid +', conn:' + client.conn + ', roomId: '+ client.roomId);
} else {
console.log("can't find the client of " + uid);
}
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐traverse‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
// 遍历map
var clients = roomMap.getEntrys();
for (var i in clients) {
let uid = clients[i].key;
let client = roomMap.get(uid);
console.log('get client‐>' + 'uid:' + client.uid +', conn:' + client.conn + ', roomId: '+ client.roomId);
}
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐remove‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
console.log('roomMap remove uid1');
roomMap.remove(uid1);
console.log('roomMap size:' + roomMap.size());
console.log('\n\n‐‐‐‐‐‐‐‐‐‐‐‐‐‐clear‐‐‐‐‐‐‐‐‐‐‐‐‐‐');
console.log('roomMap clear all');
roomMap.clear();
console.log('roomMap size:' + roomMap.size());