创作人 Leo
编辑时间 Wed Nov 23,2022 at 15:03
webrtc web 实时通讯,可以进行点对点的实时通讯,包括实时数据传输(文字聊天),实时视频(在线视频会议),实时语音(多人语音聊天)
涉及知识:
涉及软件:
stun 和 turn 协议用来保证两个端能够建立连接
再其中一端无法进行打洞的情况下,turn 服务也会负责数据转发,生产环境建议根据用户量选择带宽和负载均衡策略
首先安装 Ubuntu 18.04
安装 coturn
$ sudo apt-get -y update
$ sudo apt-get -y install coturn
安装后的指令介绍
turnserver 启动 STUN/TURN server instance
turnadmin 界面管理后台
turnutils_peer UDP-only echo server 检测连接使用
turnutils_stunclient 呼叫 STUN server 并获取回应
turnutils_uclient 模拟多人连线 TURN server 并获取回应
首先,服务器需要开放以下端口
3478: UDP+TCP TURN Server 接收 request 的 port
5394: UDP+TCP TURN Server 接收 TLS request 的 port
49152-65536: UDP+TCP 實際連線的 Socket Port range
然后就可以执行命令启动了
sudo turnserver -v -o –lt-cred-mech –user hello:world –realm {turnserver 域名} –external-ip {外网ip}/{需要绑定到的本机网卡ip,内网ip}
例:
sudo turnserver -v -o –lt-cred-mech –user hello:world –realm x.xx.xxx –external-ip 30.1.2.3⁄172.1.2.3
用户名 hello
密码 world
-o 代表后台启动
安装后还需要配置 DNS
将 turnserver 域名绑定到该服务器
A 记录: x.xx.xxx 30.1.2.3
以上都做完,可以通过这个网站测试连通性 测试地址
测试结果能显示 IP 即表式成功了
peerjs 提供了一个云 server,也可以自己搭建私有的信令服务
$ npm install peer -g
$ mkdir peer_js
$ cd peer_js/
$ nohup peerjs --port 10002 --path /peerjs > peerjs-0214.log &
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name peerjs.xxxxxxxx.xxx;
access_log /data/logs/nginx/peerjs.xxxxxxxx.xxx;
ssl_certificate /data/local/nginx/conf/sslkey/xxxxxxxx.xxx.pem;
ssl_certificate_key /data/local/nginx/conf/sslkey/xxxxxxxx.xxx.key;
location / {
proxy_pass http://127.0.0.1:10002 ;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
这个服务比较简单,主要使用websocket
消息示例:
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>ws test</title>
</head>
<body>
<div id="connect">
<button id="do-connect">连接</button>
</div>
<div id="join-room">
<input id="user-flag" placeholder="随便输入个用户名">
<input id="room-id" placeholder="输入一个房间ID">
<button id="do-join">加入房间</button>
</div>
<div id="user-list">
</div>
<div id="camerabox">
<audio id="local_video" autoplay muted controls></audio>
<!-- other connections -->
</div>
<script src="jquery.js" type="text/javascript"></script>
<script src="ConnWs.js" type="text/javascript"></script>
<script src="https://unpkg.com/peerjs@1.3.1/dist/peerjs.min.js"></script>
<script>
var server_host = window.location.hostname;
var wsObj = null;
var myWebcamStream = null;
var mediaConstraints = { audio: true };
var roomUserList = []; //{"username"=>mediaConnection}
var myUserFlag = "";
var myPeer = null;
function composeUserFlag(v) {
return v;
}
function callStreamFn(uflag, stream) {
// `stream` is the MediaStream of the remote peer.
// Here you'd add it to an HTML video/canvas element.
let audioItem = document.createElement("audio");
audioItem.autoplay = true;
audioItem.controls = true;
audioItem.id = "audio-" + uflag
document.getElementById("camerabox").appendChild(audioItem);
audioItem.srcObject = stream;
}
function NewLocalPeer(uflag) {
return new Promise((resole, reject) => {
myPeer = new Peer(composeUserFlag(uflag), {
host: 'peerjs.xxxxxxxx.xxx',
path: '/peerjs',
config: {
'iceServers': [
{ urls: 'stun:turn.xxxxxxxx.xxx:3478' },
{
urls: "turn:turn.xxxxxxxx.xxx:3478", // A TURN server
username: "hello",
credential: "world"
}
]
}
});
myPeer.on('open', function (id) {
console.log('My peer ID is: ' + id);
resole(true); // waiting until peer open
});
myPeer.on('call', function (mediaConnection) {
mediaConnection.on('stream', function (stream) {
// `stream` is the MediaStream of the remote peer.
// Here you'd add it to an HTML video/canvas element.
console.log(`RECV call.peer ${mediaConnection.peer}`);
callStreamFn(mediaConnection.peer, stream);
});
// Answer the call, providing our mediaStream
mediaConnection.answer(myWebcamStream);
});
});
}
$(document).ready(function () {
// 加入房间
$("#do-join").click(async function () {
if (wsObj == null) {
alert("websocket didn't init");
return;
}
myUserFlag = $("#user-flag").val();
let roomId = $("#room-id").val();
// 开启本地语音
try {
myWebcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
} catch (err) {
console.error(err);
return;
}
document.getElementById("local_video").srcObject = myWebcamStream;
// 连接到 peer
await NewLocalPeer(myUserFlag);
console.log("local peer connected");
let msg = {
user_flag: myUserFlag,
room_id: roomId
};
wsObj.sendMSG("VC_join_room", msg);
});
function respError(data) {
console.error(`remote resp error msg: code(${data.code}) ${data.msg}`);
}
// 更新房间用户列表
function respRoomUsers(data) {
let ihtml = "用户列表:<br>";
for (let i in data.userlist) {
let userFlag = data.userlist[i];
ihtml += userFlag + "<br>"
if (userFlag != myUserFlag) {
// 连接
console.log(`will add user to room list: ${userFlag}`)
roomUserList[userFlag] = myPeer.call(composeUserFlag(userFlag), myWebcamStream); // roomUserList[userFlag] = mediaConnection
roomUserList[userFlag].on('stream', function (stream) {
// `stream` is the MediaStream of the remote peer.
// Here you'd add it to an HTML video/canvas element.
callStreamFn(userFlag, stream);
});
}
}
$('#user-list').html(ihtml);
}
/**
* 路由收到的服务器响应数据
* @param msgid 双字节无符号整数,消息id,0-65535
* @param content 消息正文
*/
function dealAction(msgid, content) {
console.log("deal action data ", msgid, content);
switch (msgid) {
case 'error':
respError(content);
break;
case 'VC_room_userlist':
respRoomUsers(content);
break;
default:
console.log("unsupported action:", msgid);
break;
}
}
function updateUUID(uuid) {
$("#ws-host-space").html(`已经连接到:${wsObj.serverHost}`);
}
$("#do-connect").click(function (e) {
wsHost = `wss://${server_host}` // wss://roomser.xxxxxxxx.xxx/connect
console.log("will connect to ", wsHost);
wsObj = new ConnWs(wsHost, dealAction, updateUUID);
});
});
</script>
</body>
</html>
设计思路:
其他信息: