web后端开发-web 前端 后端 开发
推荐先阅读:WEBRtc实现视频会议-今日头条(头条网)
https://www.toutiao.com/article/7164302481869210152/
我们已经解释了WebRTC的基本原理,以及被投递端和投递发起者的交互过程。 本节将构建服务器。 我将使用 node.js 作为后端。
你可能需要提前了解以下知识:socket.io实现的websocket、nodejs server express框架、WebRTC交互流程、TURN/STUN server。
服务器主要由两部分组成:信令服务器和TURN/STUN服务器。 当然,它们可以运行在同一台主机上。
关于 WebSocket 和 Socket.IO 的信令服务器聊天
简单来说:websocket是一种让服务端主动向客户端推送消息的技术。
在常见的项目中,往往是前端发起请求,后端响应的模型。 如果后端需要主动推送信息,往往会用到轮询。 大多数这种大规模轮询都是对性能的无意义浪费。 websocket 标准允许客户端与服务器建立长连接。 连接成功后,可以进行全双工通信,即服务端可以主动向目标客户端推送信息。
为什么我们在 WebRTC 中需要 WebSocket?
keyboard_arrow_down
Socket.IO 是一个 node.js 库。 用于在后端建立一个WebSocket服务。 您可以像这样将其加载到支持 Express 的服务器上:
// 若你需要在同一端口监听socket和express请求,可以用这种写法
// 引入node包
const express = require('express');
const socketio = require('socket.io');
// 创建express和http后端
const app = express();
const http = require('http');
// 绑定express的http服务
const server = http.createServer(app);
// 把socket.io绑定到http服务器上
const io = socketio(server, { cors: true });
server.listen(80);
// 当然,若你不在意还要用express搭建其他的后端逻辑,只需要socket而已,上面的都不需要,可以用
const io = require('socket.io')(80);
// 直接搞定
以后可以使用io来处理WebSocket事件。 常用命令如:
io.of('NAMESPACE').on('connection', (socket) => {
socket.on('YOUR_EVENT',(arg1,arg2,...)=>{
// YOUR_CODE_HERE
});
socket.join(ROOM_NAME);
io.to('abc').emit('EVENT',args);
socket.broadcast.emit('EVENT',args);
socket.broadcast.to('ROOM').emit('EVENT',...args);
});
有关 socket.io 的更多信息,请参阅官方文档。
为什么我创建socket时请求socket.io?DIO=xxxx,返回404?
keyboard_arrow_down
信令服务器建设
在下面的代码讲解中web后端开发,我将从三端代码(发起投射端、信令服务器、投射端)开始,一步步实现信令服务,因为websocket更像是几个人之间的“对话” ,请注意每段代码属于三个端点中的哪一端?
// 被投屏端,向信令服务器注册,等待连接。
import io from "socket.io-client"
let socket = io.connect('YOUR_SOCKET_SERVER');
socket.on('connect',function()=>{ // 保留事件,连接成功触发
socket.emit('TV_INIT'); // 触发TV_INIT事件,可以在参数传token之类的校验。
});
// 信令服务器端
// 外面的io.on就在这写一次,以后信令服务器的socket监听直接都扔io.on里面就行
io.on('connection',(socket)=>{
socket.on('TV_INIT',function(){
// 你的代码,生成投屏码projCode
socket.join(projCode); // 加入房间
socket.emit('TV_INIT_OK',projCode); // 返回投屏码
})
});
// 被投屏端
socket.on('TV_INIT_OK',(code)=>{
// 拿到投屏码code,显示出来。
})
接下来是WebRTC的服务流程
// 发起投屏端
// 拿到输入的投屏码,加入房间
socket.emit('JOIN_ROOM',projCode);// 也可以在这时候做用户的身份校验
// 信令服务器
socket.on('JOIN_ROOM',projcode=>{
// 保存socket和它的projcode,可以用一个对象
socket.join(projcode); // 加入房间
})
// 发起投屏端
// 生成PeerConnection的Offer,发送给信令服务器转发
socket.emit('CLIENT_OFFER_TO_SERVER',offer);
// 信令服务器
socket.on('CLIENT_OFFER_TO_SERVER',offer=>{
// 向被投屏端转发offer,利用broadcast直接在房间里广播。ROOM是之前加入房间的时候保存的
socket.broadcast.to(ROOM).emit('CLIENT_OFFER_TO_TV',offer);
});
// 被投屏端
socket.on('CLIENT_OFFER_TO_TV',offer=>{
// 被投屏端拿到了offer,创建Answer,按照相同的方法返回。
})
兑换优惠的方法如上。 但是根据WebRTC规范,还需要实现ICE信息交换(ICECandidateExchange)。 也可以采用类似的思路,但是要注意emit ICE交换事件的时机。 建议放在onicecandidate事件中,从事件回调中获取ice candidate。 转移。
// 发起投屏端
peerConnection.onicecandidate = function (event) {
console.log(event);
if (event.candidate) {
socket.emit("RTC_Candidate_Exchange", {
iceCandidate: event.candidate,
});
}
};
// 投屏端
socket.on("RTC_Candidate_Exchange", async (message) => {
if (message.iceCandidate) {
try {
await peerConnection.addIceCandidate(message.iceCandidate);
} catch (e) {
console.error("Error adding received ice candidate", e);
}
}
});
警告必须实施 ICE 候选交换
ICE 候选人的交换必须实施,否则可能会出现无法建立 P2P、无法进行屏幕投射或只能进行内网投射等问题。 如果您发现日志中没有触发ICE候选采集事件,请检查您的Offer生成和兑换流程。 我遇到的原因是因为在createOffer之前没有加载流(可能导致系统认为没有数据传输?)最简单的检查方法是在console.log中打印你的Offer,你的offer应该是一个特别的长字符串(至少 20 行或更多)
实施这些后,如果您的 TURN/STUN 服务器设置正确(当 new RTCPeerConnection(config) 正确时传入的配置),屏幕投射应该立即开始! 恭喜!
TURN/STUN 服务器设置
然而,我们还有其他事情要做:设置我们自己的 TURN/STUN 服务器。
这部分非常复杂。 幸运的是,大名鼎鼎的谷歌开源了一个解决方案:Coturn
GitHub
转轮/转轮
coturn TURN 服务器项目
★ 8.4k
网上有很多关于Coturn部署的教程web后端开发,可以看下面这篇文章
获取页面信息时出现问题
尝试直接访问
补充几点:
coturn数据库路径:/usr/local/var/db/turndb
Coturn配置文件路径:/usr/local/etc/turnserver.conf
这里补充一下认证相关的东西,感觉相关的资料挺少的。 如何使用coturn服务器认证和配置tls访问?
打开coturn的配置文件,添加:
external-ip=公网ip
user=你的user
realm=你添加的realm
lt-cred-mech
cert=证书位置
pkey=证书位置
use-auth-secret
static-auth-secret=自己随便设
其中,static-auth-secret是指自己指定静态密钥,而不是从coturn数据库中的turn-secret表中查找密钥。
那么,我们如何生成链接服务器需要的用户名和密码呢?
官方规则非常简单。 用户名是“过期时间:用户名”的字符串拼接,其中用户名可以是任意值,不需要事先注册。 如果要创建系统,可以使用原始系统的用户名。 密码是用sha1加密的base64格式的密码。 如果开启use-static-secret,coturn会直接使用用户名和你设置的密钥进行加密,然后与你的密码进行比对。 也就是说,如果密钥被泄露,密码就等于没有。 如果不设置,coturn会使用turn-secret数据表中的secret来尝试一一比较,应该是为了提高安全性。
用户认证后可以返回turn的登录信息,如果用js可以这样写
// 安全提示:你应该在后端运行这些代码而不是在客户端!!!
const crypto=require('crypto');
function getKey(username) {
// 如果你在配置文件开启了use-static-secret,则直接填写你在那里写的密钥
let request_key = '你的密钥';
// 过期时间戳,超过时间戳密钥无效。
let time = (Date.now() + 365 * 1 * 1000 * 60 * 60 * 24).toString();
let uname = time + ':' + username;
let hmac = crypto.createHmac("sha1", request_key);
let result = hmac.update(uname).digest("Base64");
return {
username: uname,
credential: result
}
}
// 生成的config再通过socket回传给客户端,客户端用此作为config创建PeerConnection
let key=getKey(用户名);
// 发送配置
socket.emit("CONFIG_FEEDBACK", {
config: {
iceServers: [
{
urls: "turn:你的turn服务器:3478?transport=udp",
username: key.username,
credential: key.credential,
},
{
urls: "turn:你的turn服务器:3478?transport=tcp",
username: key.username,
credential: key.credential,
},
{ urls: "stun:你的stun服务器:3478" },
],
}
});
如何测试我的 TURN/STUN 服务器是否成功?
keyboard_arrow_down
关于端口的警告
TURN/STUN服务器的默认端口是3478,但实际上中继时可能会用到大量的高阶端口。 建议在系统防火墙和云服务提供商安全组放行所有TCP和UDP的出站流量。
至此,服务器就搭建好了,可以愉快的投屏啦!