系列文章目录
webrtc学习记录一【媒体录制MediaRecorder】
webrtc学习记录三【创建基于RTCPeerConnection本机内的1v1音视频互通】
目录
前言
记录webrtc学习过程中的要点,以便温故知新。本章主要基于socket.io创建一个信令服务器,并且实现一个简单的聊天室功能。
同时,由于学习视频中的socket.io的版本比较低,和最新版的有区别,所以学习过程中也踩了一些坑,用新版本实现了功能并且处理了一些问题,如跨域等。
提示:以下是本篇文章正文内容,下面案例可供参考
一、nodejs信令服务器
首先使用nodejs和express实现一个简单的服务器,由于这里我使用的是https,所以额外配置了一些证书相关的代码,但是并不影响功能。
1.1、基本服务器代码
'use strict'
const https = require('https')
const fs = require('fs')
const express = require('express')
const serveIndex = require('serve-index')
const app = express()
// 浏览指定目录,加上这个之后就可以浏览静态资源目录。
app.use(serveIndex('./public'))
// 静态资源发布到指定目录
app.use(express.static('./public'))
const options = {
key: fs.readFileSync('./cert/tongyichen.com.key'),
cert: fs.readFileSync('./cert/tongyichen.com.pem'),
}
const https_server = https.createServer(options, app, (req, res) => {
console.log('req', req)
})
https_server.listen(443, '0.0.0.0', (_) => {
console.log(`https服务器创建成功!启动时间:${new Date().toLocaleString()}`)
console.log('访问地址:')
console.log('https://106.55.160.183:443')
console.log('https://tongyichen.com')
})
1.2、绑定socket.io,创建信令服务器。
首先引入socket.io,
const socketIo = require('socket.io')
然后将socket.io和https_server服务器绑定。
此处需要注意的是,设置cors:true,即可允许跨域。
学习视频中的代码,由于服务器代码和客户端代码是同一地址,所以不存在跨域。但是实际生产开发中,前后端分离,服务器与客户端分离的跨域情况很常见。
查阅了相关的资料和文档,里面介绍的什么设置origin,更换端口,都无效。
最后是输出了io信息,发现io信息中有一个属性cors默认是false,于是便想到修改cors为true,结果正好解决了跨域问题。
虽然解决代码很简单,但是解决过程很艰辛。
const io = socketIo(https_server, {
cors: true // 中文文档没更新,看代码找到的设置cors为true即可解决跨域问题,nice
})
socket.io绑定到服务器之后,就可以开始处理建立连接后的逻辑了。
这里监听connection连接事件,然后在建立连接后,继续监听join,leave,message事件。
需要说明的一点是,不知道是否因为版本原因,使用视频中的io.sockets.adapter.rooms[roomId]无法找到,输出打印信息后发现,4.1.2版本的socket.io中的io.sockets.adapter.rooms为一个map格式的数据,并且返回的是一个set格式的数据。所以使用原方法自然获取到的是undefined,按set数据的size属性即可获取到当前房间的人数。
代码内容也不难理解,join成功后,就触发一个joined事件,同时抛出相关数据给客户端。leave成功后,就抛出left事件给客户端。监听到message事件时,就给房间内所有人都发送消息。
这里倒是可以参看socket.io文档中的速查表,来查看发送消息的方法:
引自文档:
io.on('connect', onConnect);
function onConnect(socket){
// 发送给当前客户端
socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
// 发送给所有客户端,除了发送者
socket.broadcast.emit('broadcast', 'hello friends!');
// 发送给同在 'game' 房间的所有客户端,除了发送者
socket.to('game').emit('nice game', "let's play a game");
// 发送给同在 'game1' 或 'game2' 房间的所有客户端,除了发送者
socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
// 发送给同在 'game' 房间的所有客户端,包括发送者
io.in('game').emit('big-announcement', 'the game will start soon');
// 发送给同在 'myNamespace' 命名空间下的所有客户端,包括发送者
io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
// 发送给指定 socketid 的客户端(私密消息)
socket.to(<socketid>).emit('hey', 'I just met you');
// 包含回执的消息
socket.emit('question', 'do you think so?', function (answer) {});
// 不压缩,直接发送
socket.compress(false).emit('uncompressed', "that's rough");
// 如果客户端还不能接收消息,那么消息可能丢失
socket.volatile.emit('maybe', 'do you really need it?');
// 发送给当前 node 实例下的所有客户端(在使用多个 node 实例的情况下)
io.local.emit('hi', 'my lovely babies');
};
1.3、完整服务器代码
最后,完整的nodejs信令服务器端代码如下:
'use strict'
// const http = require('http')
const https = require('https')
const fs = require('fs')
const express = require('express')
const serveIndex = require('serve-index')
const socketIo = require('socket.io')
const app = express()
// 浏览指定目录,加上这个之后就可以浏览静态资源目录。
app.use(serveIndex('./public'))
// 静态资源发布到指定目录
app.use(express.static('./public'))
const options = {
key: fs.readFileSync('./cert/tongyichen.com.key'),
cert: fs.readFileSync('./cert/tongyichen.com.pem'),
}
const https_server = https.createServer(options, app, (req, res) => {
console.log('req', req)
})
// bind socket.io with https_server, options 参考https://www.w3cschool.cn/socket/socket-odxe2egl.html,此文档并不齐全。
const io = socketIo(https_server, {
cors: true // 中文文档没更新,看代码找到的设置cors为true即可解决跨域问题,nice
})
//io.sockets 站点, socket,当前客户端。
io.sockets.on('connection', (socket) => {
// socket代表每一个客户端
socket.on('join', (roomId) => {
console.log('join success, roomId is ', roomId)
socket.join(roomId)
// const myRoom = io.sockets.adapter.rooms[roomId]
const userCount = io.sockets.adapter.rooms.get(roomId).size
console.log(`the number of user is : ${userCount}`)
// socket.emit('joined', roomId, socket.id) // 给该客户端单独返回消息。
// socket.to(roomId).emit('joined', roomId, socket.id) // 给房间内,除了自己以外的所有人返回消息。
io.in(roomId).emit('joined', {
// 给房间内的所有人都发送消息。
userCount,
roomId,
id: socket.id,
})
// socket.broadcast.emit('joined', roomId, socket.id) // 给出了自己,全部站点的所有人发送消息。broadcast 广播
})
socket.on('leave', (roomId) => {
console.log('join success, roomId is ', roomId)
socket.leave(roomId)
// const myRoom = io.sockets.adapter.rooms[roomId]
const userCount = io.sockets.adapter.rooms.get(roomId).size
console.log(`the number of user is : ${userCount}`)
// socket.emit('joined', roomId, socket.id) // 给该客户端单独返回消息。
// socket.to(roomId).emit('joined', roomId, socket.id) // 给房间内,除了自己以外的所有人返回消息。
io.in(roomId).emit('left', {
// 给房间内的所有人都发送消息。
userCount,
roomId,
id: socket.id,
}) // 给房间内的所有人都发送消息。
// socket.broadcast.emit('left', roomId, socket.id) // 给出了自己,全部站点的所有人发送消息。broadcast 广播
})
socket.on('message', (roomId, data) => {
console.log('收到消息', data)
// socket.to(roomId).emit('message', {
io.in(roomId).emit('message', {
// 给房间内的所有人都发送消息。
roomId,
id: socket.id,
data,
})
})
})
https_server.listen(443, '0.0.0.0', (_) => {
console.log(`https服务器创建成功!启动时间:${new Date().toLocaleString()}`)
console.log('访问地址:')
console.log('https://106.55.160.183:443')
console.log('https://tongyichen.com')
})
二、客户端代码实现
先搭建doom框架,这里我选用的vue简单开发了一下界面。
简单的需求逻辑为:
用户a输入房间号码111,加入该房间。
用户b输入房间号码111,加入该房间
然后用户a发送的消息,a和b均能收到。用户b发送的消息,a和b也能收到。
梳理一下开发流程,应该是:
1、连接socket.io服务器,
2、监听服务器返回的joined事件(因为在上面服务器代码中有监听join事件,然后触发一个joined事件)
3、连接成功建立后,即可通过触发message事件到服务器端,而服务器端的socket监听到了message事件后,同样触发一个message事件到客户端,客户端监听到服务器端返回的message事件和数据后,呈现数据即可。
语言描述比较绕,用流程图画一下就显得很清楚:
关键代码
sendMessage() {
let data = `${this.userName}:${this.inputData}`
console.log('发送的消息为', data)
this.socket.emit('message', this.roomId, data)
this.inputData = ''
},
joinRoom() {
// 建立链接 connect中为链接的socket.io服务器的地址
// this.socket = io.connect('https://192.168.1.153')
this.socket = io.connect('https://tongyichen.com')
this.socket.on('joined', ({userCount, roomId, id}) => {
this.joinStatus = true
})
this.socket.on('left', ({userCount, roomId, id}) => {
this.joinStatus = false
})
this.socket.on('message', ({userCount, roomId, id, data}) => {
this.chatData = this.chatData + data + '\n'
})
console.log('roomId', this.roomId)
this.socket.emit('join', this.roomId)
},
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<div id="app">
<p style="text-align: center;">聊天室</p>
<div class="line">
<div class="label">用户名:</div>
<input v-model="userName" placeholder="请输入用户名">
</div>
<div class="line">
<div class="label">房间号:</div>
<input v-model="roomId" placeholder="请输入房间号">
<button @click="joinRoom" :disabled="joinStatus">加入房间</button>
<button @click="leaveRoom" :disabled="!joinStatus">离开房间</button>
</div>
<div>
<div>聊天框:</div>
<textarea style="width:500px;height:200px" v-html="chatData"></textarea>
</div>
<hr>
<div>
<div>输入框:</div>
<textarea style="width:500px;height:70px" v-model="inputData"></textarea>
<div> <button @click="sendMessage">发送消息</button> </div>
</div>
</div>
</body>
<script src="./js/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
joinStatus: false,
message: 'Hello Vue!',
userName: '',
roomId: '',
chatData: '',
inputData: '',
socket: null
},
methods: {
sendMessage() {
let data = `${this.userName}:${this.inputData}`
console.log('发送的消息为', data)
this.socket.emit('message', this.roomId, data)
this.inputData = ''
},
joinRoom() {
// 建立链接 connect中为链接的socket.io服务器的地址
// this.socket = io.connect('https://192.168.1.153')
this.socket = io.connect('https://tongyichen.com')
this.socket.on('joined', ({userCount, roomId, id}) => {
this.joinStatus = true
})
this.socket.on('left', ({userCount, roomId, id}) => {
this.joinStatus = false
})
this.socket.on('message', ({userCount, roomId, id, data}) => {
this.chatData = this.chatData + data + '\n'
})
console.log('roomId', this.roomId)
this.socket.emit('join', this.roomId)
},
leaveRoom() {
}
},
created() {
console.log('io', io)
}
})
</script>
</html>