1 信令服务器的作用
WebRTC需要一个房间服务器将多端聚集到一起管理,以及信令服务器进行信令数据交换(如媒体描述信息SDP的交换,连接地址的交换等),但在WebRTC的规范中没有对这部分内容进行规定,所以需要由用户自己处理。
2 信令服务器的选型
Nodejs 的最大优点即是可以使用 JS 语言开发服务器程序,这样客户端和服务端就可以使用同一种语言开发。另一方面是JS语言比较简单。而且Nodejs服务端的功能库也比较多,可以用npm命令快速安装。
3 信令服务器环境搭建
3.1 搭建nodejs服务器
可以参考之前的博客Nodejs服务器搭建
3.2 安装socket.io
npm install socket.io
如果在CentOS上遇到报错
ERR! Error: CERT_UNTRUSTED
npm ERR! at SecurePair.<anonymous> (tls.js:1430:32)
npm ERR! at SecurePair.emit (events.js:92:17)
npm ERR! at SecurePair.maybeInitFinished (tls.js:1029:10)
npm ERR! at CleartextStream.read [as _read] (tls.js:521:13)
npm ERR! at CleartextStream.Readable.read (_stream_readable.js:341:10)
npm ERR! at EncryptedStream.write [as _write] (tls.js:418:25)
npm ERR! at doWrite (_stream_writable.js:226:10)
npm ERR! at writeOrBuffer (_stream_writable.js:216:5)
npm ERR! at EncryptedStream.Writable.write (_stream_writable.js:183:11)
npm ERR! at write (_stream_readable.js:602:24)
npm ERR! at flow (_stream_readable.js:611:7)
npm ERR! at Socket.pipeOnReadable (_stream_readable.js:643:5)
npm ERR! If you need help, you may report this log at:
npm ERR! <http://github.com/isaacs/npm/issues>
npm ERR! or email it to:
npm ERR! <npm-@googlegroups.com>
npm ERR! System Linux 2.6.32-696.16.1.el6.x86_64
npm ERR! command "node" "/usr/bin/npm" "install" "socket.io"
npm ERR! cwd /root/yeliang
npm ERR! node -v v0.10.48
npm ERR! npm -v 1.3.6
npm http GET https://registry.npmjs.org/socket.io-adapter
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR! /root/yeliang/npm-debug.log
npm ERR! not ok code 0
则尝试如下命令
npm config set strict-ssl false
3.3 在js中引入socket.io
var socketIo = require('socket.io');
4 信令服务器发送消息
4.1 给本次连接发消息
socket.emit()
示例:
socket.emit('joined', room, socket.id);
4.2 给某个房间内所有人发消息
io.in(room).emit()
这里的io实际上是监听https_server返回的对象
var io = socketIo.listen(https_server);
示例:
io.in(room).emit('joined', room, socket.id)
4.3 除本连接外,给某个房间内所有人发送消息
socket.to(room).emit()
示例:
socket.to(room).emit('otherjoin', room);//除自己之外
4.4 除本连接外,给所有人发送消息
socket.broadcast.emit()
示例:
socket.to(room).emit('joined', room, socket.id);
5 客户端使用socket.io处理消息
5.1 发送action命令
client发送:
socket.emit('action');
server接收:
socket.on('action', function(){...});
5.2 发送了action命令,并且有data数据
client发送:
socket.emit('action', data);
server接收:
socket.on('action', function(data){...});
示例:
client向server发送的action为leave
socket.emit('leave', room);
server端接收leave消息
socket.on('leave', (room)=> {
logger.info("leave",",room = ", room,",socket.id = ", socket.id);
var myRoom = https_socket.sockets.adapter.rooms[room];
var users = Object.keys(myRoom.sockets).length;
socket.leave(room);
socket.to(room).emit('bye', room, socket.id)
socket.emit('leaved', room, socket.id);
});
5.3 发送action命令,并且有两个数据<
client发送:
socket.emit(action, arg1, arg2);
server接收:
socket.on('action', function(arg1, arg2){...});
示例:
client向server发送action为message
socket.emit('message', room, data);
server接收messag消息
socket.on('message', (room, data)=>{
https_socket.to(room).emit('message', room, socket.id, data);
});
5.4 发送action命令,在emit方法中包含回调函数
client发送:
socket.emit('action', data, function(arg1, arg2){...});
server接收:
socket.on('action',function(data, fn));
6 聊天室示例
1 完整功能展示
2 客户端完整代码
2.1 html部分
<html>
<head>
<title>Chat Room</title>
<link rel="stylesheet" type="text/css" href="./css/main.css"></link>
</head>
<body>
<table align="center">
<tr>
<td>
<label>UserName:</label>
<input type=text id="username"></input>
</td>
</tr>
<tr>
<td>
<label>room:</label>
<input type=text id="room"></input>
<button id="connect">Connect</button>
<button id="leave" disabled>Leave</button>
</td>
</tr>
<tr>
<td>
<label>Content:</label><br>
<textarea disabled style="line-height: 1.5;" id="output" rows="10" cols="100"></textarea>
</td>
</tr>
<tr>
<td>
<label>Input:</label><br>
<textarea disabled id="input" rows="3" cols="100"></textarea>
</td>
</tr>
<tr>
<td>
<button id="send">Send</button>
</td>
</tr>
</table>
<script src = "./client.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
</body>
</html>
2.2 js部分
'use strict'
var userName = document.querySelector('input#username');
var inputRoom = document.querySelector('input#room');
var btnConnect = document.querySelector('button#connect');
var btnLeave = document.querySelector('button#leave');
var outputArea = document.querySelector('textarea#output');
var inputArea = document.querySelector('textarea#input');
var btnSend = document.querySelector('button#send');
var socket;
var room;
btnConnect.onclick = () =>{
//connect
socket = io.connect();
//receive message
socket.on('joined', (room,id) =>{
console.log('joined',"room = ", room, "id = ",id);
btnConnect.disabled = true;
btnLeave.disabled = false;
inputArea.disabled = false;
btnSend.disabled = false;
});
socket.on('leaved', (room, id)=>{
console.log('leaved',"room = ", room, "id = ",id);
btnConnect.disabled = false;
btnLeave.disabled = true;
inputArea.disabled = true;
btnSend.disabled = true;
socket.disconnect();
});
socket.on('message',(room, id, data) =>{
console.log('message',"room = ", room, "id = ",id, "data = " + data);
outputArea.scrollTop = outputArea.scrollHeight;//窗口总是显示最后的内容
outputArea.value = outputArea.value + data + '\r';
});
socket.on('disconnect', (socket)=>{
console.log('disconnect');
btnConnect.disabled = false;
btnLeave.disabled = true;
inputArea.disabled = true;
btnSend.disabled = true;
});
room = inputRoom.value;
socket.emit('join', room);
}
btnSend.onclick = ()=>{
var data = inputArea.value;
data = userName.value + ':' + data;
socket.emit('message', room, data);
inputArea.value = '';
}
btnLeave.onclick = ()=>{
room = inputRoom.value;
socket.emit('leave', room);
}
inputArea.onkeypress = (event)=>{
if(event.keyCode == 13){
var data = inputArea.value;
data = userName.value + ":" + data;
socket.emit('message', room, data);
inputArea.value = '';
event.preventDefault();//阻止默认行文
}
}
当点击Connect连接服务器时,日志输出为
joined room = 111 id = BMxGJ2ViQlARUtAGAAAB
在另外一个html页面的出入框中输入文字“嘎哈呢啊盖伦”,然后点击发送时日志输出:
message room = 111 id = t6WAh4Cq5dikz_2CAAAA data = 德邦:嘎哈呢啊盖伦
点击leave按钮,日志输出:
leaved room = 111 id = BMxGJ2ViQlARUtAGAAAB
disconnect
3 服务端完整代码
'use strict'
var https = require('https');
var fs = require('fs');
var express = require('express');
var serveIndex = require('serve-index');
//socket.io
var socketIo = require('socket.io');
//
var log4js = require('log4js');
log4js.configure({
appenders: {
file: {
type: 'file',
filename: 'app.log',
layout: {
type: 'pattern',
pattern: '%r %p - %m',
}
}
},
categories: {
default: {
appenders: ['file'],
level: 'debug'
}
}
});
var logger = log4js.getLogger();
var app = express();
app.use(serveIndex('./public'));
app.use(express.static('./public'));
var options = {
key : fs.readFileSync('./cert/test.key'),
cert: fs.readFileSync('./cert/test.pem')
}
//https server
var https_server = https.createServer(options, app);
//bind socket.io with https_server
var https_socket = socketIo.listen(https_server);
//connection
https_socket.sockets.on('connection', (socket)=>{
logger.log("connection");
socket.on('message', (room, data)=>{
https_socket.to(room).emit('message', room, socket.id, data)//房间内所有人,除自己外
});
//该函数应该加锁
socket.on('join', (room)=> {
logger.log("join",",room = ", room,",socket.id = ", socket.id);
socket.join(room);
var myRoom = https_socket.sockets.adapter.rooms[room];
var users = Object.keys(myRoom.sockets).length;
logger.log('the number of user in room is: ' + users);
//在这里可以控制进入房间的人数,现在一个房间最多 2个人
//为了便于客户端控制,如果是多人的话,应该将目前房间里
//人的个数当做数据下发下去。
if(users < 3) {
socket.emit('joined', room, socket.id);
if (users > 1) {
socket.to(room).emit('otherjoin', room);//除自己之外
}
}else {
socket.leave(room);
socket.emit('full', room, socket.id);
}
//socket.to(room).emit('joined', room, socket.id);//除自己之外
//io.in(room).emit('joined', room, socket.id)//房间内所有人
//socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点
});
socket.on('leave', (room)=> {
logger.log("leave",",room = ", room,",socket.id = ", socket.id);
var myRoom = https_socket.sockets.adapter.rooms[room];
var users = Object.keys(myRoom.sockets).length;
//users - 1;
logger.log('the number of user in room is: ' + (users-1));
socket.leave(room);
socket.to(room).emit('bye', room, socket.id)//房间内所有人,除自己外
socket.emit('leaved', room, socket.id);
//socket.to(room).emit('joined', room, socket.id);//除自己之外
//io.in(room).emit('joined', room, socket.id)//房间内所有人
//socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点
});
});
https_server.listen(443, '0.0.0.0');
3.1 以下是各个事件的输出日志
连接:
[2019-10-17T14:34:30.815] [INFO] default - connection
[2019-10-17T14:34:30.819] [INFO] default - join ,room = 111 ,socket.id = DZWaJaib0mzKy5ouAAAB
收到消息:
[2019-10-17T14:34:38.766] [INFO] default - receive message ,room = 111 ,data = 德邦:嘎哈呢啊盖伦
离开房间:
[2019-10-17T14:36:51.772] [INFO] default - leave ,room = 111 ,socket.id = kczB-Ecky7J2t9UCAAAC