Ubuntu 24.04 安装开源WebRTC信令服务器
前言
本指南提供了在Ubuntu 24.04环境下安装和配置三种流行的开源WebRTC信令服务器的详细步骤:
- Janus - 功能丰富的通用WebRTC服务器
- MediaSoup - 高性能的WebRTC选择性转发单元(SFU)
- Simple-Peer-Server - 轻量级WebSocket信令服务器
1. 系统准备
更新系统包
bash
sudo apt update
sudo apt upgrade -y
安装通用依赖
bash
sudo apt install -y build-essential git curl wget gnupg2 dirmngr
2. 安装Node.js(用于MediaSoup和Simple-Peer-Server)
bash
# 添加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
安装依赖
bash
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
bash
# 克隆代码
cd ~
git clone https://github.com/meetecho/janus-gateway.git
cd janus-gateway
git checkout v1.0.7 # 使用稳定版本
# 编译安装
./autogen.sh
./configure --prefix=/opt/janus
make -j$(nproc)
sudo make install
sudo make configs
配置Janus
bash
# 编辑配置文件
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证书文件
创建系统服务
bash
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
启用并启动服务:
bash
sudo systemctl daemon-reload
sudo systemctl enable janus
sudo systemctl start janus
4. 安装MediaSoup
创建项目目录
bash
mkdir -p ~/mediasoup-server
cd ~/mediasoup-server
初始化项目
bash
npm init -y
安装MediaSoup及依赖
bash
npm install mediasoup mediasoup-client express socket.io
创建基本服务器文件
创建server.js文件:
bash
nano server.js
添加以下内容:
javascript
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}`);
});
创建启动脚本
bash
nano start.sh
添加以下内容:
bash
#!/bin/bash
# 设置环境变量
export ANNOUNCED_IP=127.0.0.1 # 替换为你的服务器IP
# 启动服务器
node server.js
设置执行权限:
bash
chmod +x start.sh
5. 安装Simple-Peer-Server(轻量级信令服务器)
创建项目目录
bash
mkdir -p ~/simple-peer-server
cd ~/simple-peer-server
初始化项目
bash
npm init -y
安装依赖
bash
npm install ws uuid
创建服务器文件
bash
nano server.js
添加以下内容:
javascript
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) => {
// 为每个连接生成唯一ID
const 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}`);
});
创建启动脚本
bash
nano start.sh
添加以下内容:
bash
#!/bin/bash
# 启动服务器
node server.js
设置执行权限:
bash
chmod +x start.sh
6. 与当前项目集成
修改前端连接配置
修改/home/lhz/meeting/static/js/app.js中的WebSocket连接URL:
javascript
// 替换initWebSocket函数中的连接URL
function initWebSocket() {
// 使用Simple-Peer-Server
const wsUrl = 'wss://your-server-ip:8443';
// 或使用Janus
// const wsUrl = 'wss://your-server-ip:8188';
state.socket = new WebSocket(wsUrl);
// 其余代码保持不变...
}
配置ICE服务器
确保ICE_SERVERS配置在server.py和前端代码中一致:
python
# 在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证书:
bash
# 生成自签名证书(开发环境)
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. 防火墙配置
bash
# 开放必要的端口
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日志
bash
# 查看Janus日志
sudo journalctl -u janus -f
MediaSoup/Simple-Peer-Server日志
可以使用pm2来管理Node.js进程并查看日志:
bash
# 安装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. 性能优化建议
-
调整服务器资源限制:
bashsudo nano /etc/security/limits.conf # 添加以下行 * soft nofile 65535 * hard nofile 65535 -
优化内核参数:
bashsudo 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应用配置:
bashsudo sysctl -p
11. 推荐方案总结
-
轻量级部署 :选择 Simple-Peer-Server
- 优点:简单易配置,资源占用少,与当前项目架构相似
- 适用:小型会议系统,开发环境
-
中等规模部署 :选择 Janus
- 优点:功能全面,文档完善,社区活跃
- 适用:中小规模生产环境,需要多种WebRTC功能
-
大规模高并发 :选择 MediaSoup
- 优点:高性能,可扩展性好,支持大规模并发
- 适用:大规模生产环境,需要处理大量并发连接
根据您的实际需求和服务器资源选择合适的方案。