Ubuntu 24.04 安装开源WebRTC信令服务器
Ubuntu 24.04 安装开源WebRTC信令服务器
前言
本指南提供了在Ubuntu 24.04环境下安装和配置三种流行的开源WebRTC信令服务器的详细步骤:
- Janus - 功能丰富的通用WebRTC服务器
- MediaSoup - 高性能的WebRTC选择性转发单元(SFU)
- Simple-Peer-Server - 轻量级WebSocket信令服务器
1. 系统准备
更新系统包
sudo apt update
sudo apt upgrade -y
安装通用依赖
sudo apt install -y build-essential git curl wget gnupg2 dirmngr
2. 安装Node.js(用于MediaSoup和Simple-Peer-Server)
# 添加Node.js源
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -# 安装Node.js
sudo apt install -y nodejs# 验证安装
node -v
npm -v
3. 安装Janus Gateway
安装依赖
sudo apt install -y libmicrohttpd-dev libjansson-dev \libssl-dev libsrtp-dev libsofia-sip-ua-dev libglib2.0-dev \libopus-dev libogg-dev libcurl4-openssl-dev liblua5.3-dev \libconfig-dev pkg-config gengetopt libtool automake libnice-dev
下载并编译Janus
# 克隆代码
cd ~
git clone https://github.com/meetecho/janus-gateway.git
cd janus-gatewaygit checkout v1.0.7 # 使用稳定版本# 编译安装
./autogen.sh
./configure --prefix=/opt/janus
make -j$(nproc)
sudo make install
sudo make configs
配置Janus
# 编辑配置文件
sudo nano /opt/janus/etc/janus/janus.jcfg# 在nat_1_1_mapping字段添加服务器公网IP(如果是云服务器)
# nat_1_1_mapping = "your_server_public_ip"# 启用HTTPS(可选但推荐)
sudo nano /opt/janus/etc/janus/janus.transport.http.jcfg
# 将secure = false 改为 secure = true
# 配置cert_pem和key_pem指向SSL证书文件
创建系统服务
sudo nano /etc/systemd/system/janus.service
添加以下内容:
[Unit]
Description=Janus WebRTC Server
After=network.target[Service]
ExecStart=/opt/janus/bin/janus
Restart=on-failure
RestartSec=5
User=root[Install]
WantedBy=multi-user.target
启用并启动服务:
sudo systemctl daemon-reload
sudo systemctl enable janus
sudo systemctl start janus
4. 安装MediaSoup
创建项目目录
mkdir -p ~/mediasoup-server
cd ~/mediasoup-server
初始化项目
npm init -y
安装MediaSoup及依赖
npm install mediasoup mediasoup-client express socket.io
创建基本服务器文件
创建server.js文件:
nano server.js
添加以下内容:
const express = require('express');
const https = require('https');
const fs = require('fs');
const { Server } = require('socket.io');
const mediasoup = require('mediasoup');const app = express();// 静态文件服务
app.use(express.static('public'));// HTTPS服务器配置
const options = {key: fs.readFileSync('/path/to/your/key.pem'),cert: fs.readFileSync('/path/to/your/cert.pem')
};const httpsServer = https.createServer(options, app);
const io = new Server(httpsServer);// 房间管理
const rooms = new Map();// MediaSoup配置
const mediaCodecs = [{kind: 'audio',mimeType: 'audio/opus',clockRate: 48000,channels: 2},{kind: 'video',mimeType: 'video/VP8',clockRate: 90000}
];io.on('connection', async (socket) => {console.log('新客户端连接:', socket.id);// 房间创建或加入socket.on('createRoom', async (callback) => {try {const router = await mediasoup.createRouter({ mediaCodecs });const roomId = Math.random().toString(36).substring(2, 15);rooms.set(roomId, {router,peers: new Map()});callback({ roomId });} catch (error) {console.error('创建房间失败:', error);callback({ error: error.message });}});socket.on('joinRoom', async ({ roomId }, callback) => {try {const room = rooms.get(roomId);if (!room) {return callback({ error: '房间不存在' });}socket.join(roomId);callback({ success: true });} catch (error) {console.error('加入房间失败:', error);callback({ error: error.message });}});// WebRTC信令处理socket.on('getRtpCapabilities', ({ roomId }, callback) => {const room = rooms.get(roomId);if (!room) return callback({ error: '房间不存在' });callback({ rtpCapabilities: room.router.rtpCapabilities });});socket.on('createWebRtcTransport', async ({ roomId }, callback) => {try {const room = rooms.get(roomId);if (!room) return callback({ error: '房间不存在' });const transport = await room.router.createWebRtcTransport({listenIps: [{ip: '0.0.0.0',announcedIp: process.env.ANNOUNCED_IP || '127.0.0.1'}],enableUdp: true,enableTcp: true,preferUdp: true});callback({id: transport.id,iceParameters: transport.iceParameters,iceCandidates: transport.iceCandidates,dtlsParameters: transport.dtlsParameters});room.peers.set(socket.id, { transport });} catch (error) {console.error('创建WebRTC传输失败:', error);callback({ error: error.message });}});// 断开连接socket.on('disconnect', () => {console.log('客户端断开连接:', socket.id);// 清理资源});
});// 启动服务器
const PORT = process.env.PORT || 3000;
httpsServer.listen(PORT, () => {console.log(`MediaSoup服务器运行在 https://localhost:${PORT}`);
});
创建启动脚本
nano start.sh
添加以下内容:
#!/bin/bash# 设置环境变量
export ANNOUNCED_IP=127.0.0.1 # 替换为你的服务器IP# 启动服务器
node server.js
设置执行权限:
chmod +x start.sh
5. 安装Simple-Peer-Server(轻量级信令服务器)
创建项目目录
mkdir -p ~/simple-peer-server
cd ~/simple-peer-server
初始化项目
npm init -y
安装依赖
npm install ws uuid
创建服务器文件
nano server.js
添加以下内容:
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');
const https = require('https');
const fs = require('fs');// 房间管理
const rooms = new Map();// HTTPS配置
const options = {key: fs.readFileSync('/path/to/your/key.pem'),cert: fs.readFileSync('/path/to/your/cert.pem')
};// 创建HTTPS服务器
const server = https.createServer(options);
const wss = new WebSocket.Server({ server });// WebSocket连接处理
wss.on('connection', (ws, req) => {// 为每个连接生成唯一IDconst userId = uuidv4();console.log(`新用户连接: ${userId}`);// 存储用户信息ws.userId = userId;ws.roomId = null;// 消息处理ws.on('message', (message) => {try {const data = JSON.parse(message);switch (data.type) {case 'join_room':handleJoinRoom(ws, data.roomId, data.userName);break;case 'offer':case 'answer':case 'ice_candidate':// 转发WebRTC信令forwardSignaling(ws, data);break;case 'leave_room':handleLeaveRoom(ws);break;default:console.log(`未知消息类型: ${data.type}`);}} catch (error) {console.error('消息处理错误:', error);}});// 断开连接处理ws.on('close', () => {handleDisconnect(ws);});// 错误处理ws.on('error', (error) => {console.error(`WebSocket错误 (${userId}):`, error);});
});// 处理加入房间
function handleJoinRoom(ws, roomId, userName) {// 离开当前房间(如果在其他房间中)if (ws.roomId) {handleLeaveRoom(ws);}// 加入新房间if (!rooms.has(roomId)) {rooms.set(roomId, new Map());}const room = rooms.get(roomId);room.set(ws.userId, {ws,userName: userName || `用户${ws.userId.substring(0, 8)}`});ws.roomId = roomId;// 通知房间内其他用户const userList = [];room.forEach((user, id) => {if (id !== ws.userId) {userList.push({ id, userName: user.userName });// 通知其他用户新用户加入user.ws.send(JSON.stringify({type: 'user_joined',user_id: ws.userId,nickname: userName || `用户${ws.userId.substring(0, 8)}`}));}});// 发送房间内用户列表给新用户ws.send(JSON.stringify({type: 'room_joined',room_id: roomId,users: userList,user_id: ws.userId}));console.log(`${userName || `用户${ws.userId.substring(0, 8)}`} 加入房间 ${roomId}`);
}// 处理离开房间
function handleLeaveRoom(ws) {if (!ws.roomId) return;const room = rooms.get(ws.roomId);if (!room) return;const user = room.get(ws.userId);room.delete(ws.userId);// 如果房间为空,删除房间if (room.size === 0) {rooms.delete(ws.roomId);} else {// 通知其他用户room.forEach((u) => {u.ws.send(JSON.stringify({type: 'user_left',user_id: ws.userId,nickname: user ? user.userName : `用户${ws.userId.substring(0, 8)}`}));});}console.log(`${user ? user.userName : `用户${ws.userId.substring(0, 8)}`} 离开房间 ${ws.roomId}`);ws.roomId = null;
}// 处理断开连接
function handleDisconnect(ws) {console.log(`用户断开连接: ${ws.userId}`);handleLeaveRoom(ws);
}// 转发WebRTC信令
function forwardSignaling(ws, data) {if (!ws.roomId) {ws.send(JSON.stringify({type: 'error',message: '必须先加入房间'}));return;}const room = rooms.get(ws.roomId);if (!room) return;// 添加发送者信息data.sender_id = ws.userId;data.from_user_id = ws.userId;data.sender_name = room.get(ws.userId)?.userName || `用户${ws.userId.substring(0, 8)}`;data.from_user_name = data.sender_name;// 支持两种目标用户ID字段名const targetUserId = data.target_user_id || data.recipient_id;if (!targetUserId) {ws.send(JSON.stringify({type: 'error',message: '缺少目标用户ID'}));return;}const targetUser = room.get(targetUserId);if (!targetUser) {ws.send(JSON.stringify({type: 'error',message: '目标用户不在房间中'}));return;}// 转发消息给目标用户targetUser.ws.send(JSON.stringify(data));
}// 启动服务器
const PORT = process.env.PORT || 8443;
server.listen(PORT, () => {console.log(`信令服务器运行在 https://0.0.0.0:${PORT}`);
});
创建启动脚本
nano start.sh
添加以下内容:
#!/bin/bash# 启动服务器
node server.js
设置执行权限:
chmod +x start.sh
6. 与当前项目集成
修改前端连接配置
修改/home/lhz/meeting/static/js/app.js中的WebSocket连接URL:
// 替换initWebSocket函数中的连接URL
function initWebSocket() {// 使用Simple-Peer-Serverconst wsUrl = 'wss://your-server-ip:8443';// 或使用Janus// const wsUrl = 'wss://your-server-ip:8188';state.socket = new WebSocket(wsUrl);// 其余代码保持不变...
}
配置ICE服务器
确保ICE_SERVERS配置在server.py和前端代码中一致:
# 在server.py中
ICE_SERVERS = [RTCIceServer(urls='stun:stun.l.google.com:19302'),RTCIceServer(urls='stun:stun1.l.google.com:19302'),# 添加自己的TURN服务器配置RTCIceServer(urls='turn:your-turn-server.com:443?transport=tcp',username='your-username',credential='your-credential')
]
7. 生成SSL证书
对于HTTPS/WSS连接,需要SSL证书:
# 生成自签名证书(开发环境)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes# 将证书复制到需要的位置
mkdir -p ~/ssl
cp key.pem cert.pem ~/ssl/
8. 防火墙配置
# 开放必要的端口
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3478/udp # STUN/TURN端口
sudo ufw allow 49152:65535/udp # WebRTC媒体端口范围
sudo ufw reload
9. 监控和日志
Janus日志
# 查看Janus日志
sudo journalctl -u janus -f
MediaSoup/Simple-Peer-Server日志
可以使用pm2来管理Node.js进程并查看日志:
# 安装pm2
sudo npm install -g pm2# 启动服务并设置开机自启
cd ~/simple-peer-server
pm2 start server.js --name signaling-server
pm2 startup
pm2 save# 查看日志
pm2 logs signaling-server
10. 性能优化建议
-
调整服务器资源限制:
sudo nano /etc/security/limits.conf # 添加以下行 * soft nofile 65535 * hard nofile 65535 -
优化内核参数:
sudo nano /etc/sysctl.conf # 添加以下行 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 net.ipv4.tcp_no_metrics_save = 1 net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_sack = 1 net.core.netdev_max_backlog = 5000应用配置:
sudo sysctl -p
11. 推荐方案总结
-
轻量级部署:选择 Simple-Peer-Server
- 优点:简单易配置,资源占用少,与当前项目架构相似
- 适用:小型会议系统,开发环境
-
中等规模部署:选择 Janus
- 优点:功能全面,文档完善,社区活跃
- 适用:中小规模生产环境,需要多种WebRTC功能
-
大规模高并发:选择 MediaSoup
- 优点:高性能,可扩展性好,支持大规模并发
- 适用:大规模生产环境,需要处理大量并发连接
根据您的实际需求和服务器资源选择合适的方案。
