网站首页 > 开源技术 正文
建议先看:WEBRtc 实现视频会议-今日头条 (toutiao.com)
https://www.toutiao.com/article/7164302481869210152/
我们已经在之前讲解了WebRTC的基本原理和被投放端和投放发起端的交互流程,本节来进行服务端的搭建。我将以node.js作为后端。
您可能需要预先了解以下知识:socket.io实现的websocket,nodejs服务器express框架,WebRTC的交互流程,TURN/STUN服务器。
服务端主要有两部分组成:信令服务器和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);
});
- io.of(‘NAMESPACE’):指定了命名空间。允许你在同一个http服务运行很多个websocket。比如,如果NAMESPACE=‘/rtc’,那么你在尝试连接的时候应该访问 http://your.domain/rtc 来建立连接。
- io.on(‘EVENT_NAME’):事件绑定。 常见的保留事件有:connection(连接建立成功触发,携带一个本次链接的socket对象作为参数。可以保存起来。),disconnect(链接断开时触发,可以做一些内存清理)
- socket.on(‘YOUR_EVENT’):自定义事件绑定。和io.on不同。可以理解为io用来创建和记录socket链接,而socket.on则负责处理单个链接内部事件。事件名称可以自定义。当客户端链接socket成功后,客户端也会拿到一个socket对象。比如客户端调用socket.emit(‘foo’,args1,args2,…);服务端就可以通过socket.on(‘foo’,(args1,args2)=>{ //your code });进行参数获取。同理,服务器也可以用emit触发客户端定义好的事件。emit在获取时间名’foo’后,跟随的的参数会依次传送给socket.on的回调函数。
- socket.emit(‘EVENT_NAME’,args1,args2,…) 用于触发事件,见上。
- socket.join(ROOM_NAME);传入房间名,可以让socket加入ROOM,当一个ROOM有很多个socket的时候,可以很方便的对所有连接的socket进行广播。
- io.to(ROOM_NAME).emit(‘EVENTNAME’,…args); 向所有该房间的socket发送事件,包括自己。
- socket.broadcast.emit(‘EVENTNAME’,…args);向和该socket处于同一命名空间的所有socket发送事件,不包括自己。
- socket.broadcast.to(ROOM).emit(‘EVENTNAME’,…args); 向同一命名空间指定的房间广播信息,不包括自己。
更多关于socket.io的信息,见官方文档。
为什么我建立socket时会请求socket.io?DIO=xxxx,返回404?
keyboard_arrow_down
信令服务器搭建
下面的代码讲解我将会从三端代码(发起投屏端,信令服务器,被投屏端)入手,一步一步的实现信令服务,因为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,按照相同的方法返回。
})
交换offer的方法如上。但根据WebRTC规范,还需要实现ICE信息的交换(ICECandidateExchange),也可以采取类似的思路,不过注意emit ICE交换事件时的时机,建议放在onicecandidate事件中,从event回调中拿到ice候选者进行转送。
// 发起投屏端
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);
}
}
});
warning 必须实现ICE候选者交换
必须实现ICE候选者交换,否则可能会出现P2P无法建立、无法投屏或只能内网投屏的问题。 若您在log中发现ICE候选人收集事件没有被触发,经检查你的Offer生成和交换过程。我遇到的原因是因为没有在createOffer之前加载stream(可能导致系统认为没有数据要传输?)检查这一点最简单的方法是console.log打印你的Offer,你的offer应该是特别长的一串(起码20行以上)
实现完这些以后,若你的TURN/STUN服务器设置正确(在new RTCPeerConnection(config)时传入的config无误),投屏应该会马上开始!Congratulations!
TURN/STUN服务器搭建
但是,我们还有别的事情要做:搭建自己的TURN/STUN服务器。
这部分非常复杂。好在著名的Google开源了一款方案:Coturn
GitHub
coturn/coturn
coturn TURN server project
★ 8.4k
关于Coturn的部署网上教程一大堆,大家可以看下面这篇
获取页面信息时出现问题
尝试直接访问 https://www.cnblogs.com/yjmyzz/p/how-to-install-coturn-on-ubuntu.html
补充几点:
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表中查找密钥。
那么,我们如何生成链接服务器所需的username和password呢?
官方的规则很简单,用户名是 “过期时间:用户名”的字符串拼接,其中用户名可以是任何值,不用事先注册,如果你要做系统,可以使用你原有系统的username。而密码则是用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
warning 关于端口
TURN/STUN服务器默认端口3478,但事实上中继的时候可能会使用大量高位端口。建议在系统防火墙和云服务商安全组把TCP和UDP的出流量全部放开。
至此,服务端搭建完毕,可以快乐的投屏了!
猜你喜欢
- 2024-10-17 WebRTC 云服务器搭建 AppRTC 环境
- 2024-10-17 WebRTC原理与web端实战开发(webrtc技术详解)
- 2024-10-17 NAT穿透服务安装记录(内网nat穿透)
- 2024-10-17 Mac搭建WebRTC服务器(mac搭建网站)
- 2024-10-17 互动直播之WebRTC服务器Kurento实战
- 2024-07-09 WebRTC 实战: P2P 音视频通话解决方案
- 2024-07-09 旧手机别卖掉换脸盆了,自制服务器了解一下
- 2024-07-09 如何搭建WebRTC信令服务器(webrtc建立连接)
- 2024-07-09 FFmpeg/WebRTC/RTMP 音视频流媒体高级开发知识点总结
- 2024-07-09 Ubuntu 下 Janus Server 搭建笔记
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- jdk (81)
- putty (66)
- rufus (78)
- 内网穿透 (89)
- okhttp (70)
- powertoys (74)
- windowsterminal (81)
- netcat (65)
- ghostscript (65)
- veracrypt (65)
- asp.netcore (70)
- wrk (67)
- aspose.words (80)
- itk (80)
- ajaxfileupload.js (66)
- sqlhelper (67)
- express.js (67)
- phpmailer (67)
- xjar (70)
- redisclient (78)
- wakeonlan (66)
- tinygo (85)
- startbbs (72)
- webftp (82)
- vsvim (79)
本文暂时没有评论,来添加一个吧(●'◡'●)