Ubuntu 24.04 安装开源WebRTC信令服务器

Ubuntu 24.04 安装开源WebRTC信令服务器

前言

本指南提供了在Ubuntu 24.04环境下安装和配置三种流行的开源WebRTC信令服务器的详细步骤:

  1. Janus - 功能丰富的通用WebRTC服务器
  2. MediaSoup - 高性能的WebRTC选择性转发单元(SFU)
  3. 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. 性能优化建议

  1. 调整服务器资源限制

    bash 复制代码
    sudo nano /etc/security/limits.conf
    # 添加以下行
    * soft nofile 65535
    * hard nofile 65535
  2. 优化内核参数

    bash 复制代码
    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

    应用配置:

    bash 复制代码
    sudo sysctl -p

11. 推荐方案总结

  1. 轻量级部署 :选择 Simple-Peer-Server

    • 优点:简单易配置,资源占用少,与当前项目架构相似
    • 适用:小型会议系统,开发环境
  2. 中等规模部署 :选择 Janus

    • 优点:功能全面,文档完善,社区活跃
    • 适用:中小规模生产环境,需要多种WebRTC功能
  3. 大规模高并发 :选择 MediaSoup

    • 优点:高性能,可扩展性好,支持大规模并发
    • 适用:大规模生产环境,需要处理大量并发连接

根据您的实际需求和服务器资源选择合适的方案。

相关推荐
这个人需要休息2 小时前
TCP/IP 协议栈
服务器·网络·网络协议·tcp/ip
kkce2 小时前
快快测(KKCE)TCping 检测全面升级:IPv6 深度覆盖 + 多维度可视化,重构网络性能监测新体验
服务器·网络·重构
浪漫血液&2 小时前
进程调度的基本过程
服务器·进程调度
YJlio2 小时前
VMMap 学习笔记(8.2):启动 VMMap、选择目标进程、权限要求与首次快照流程
服务器·笔记·学习
浪漫血液&3 小时前
Linux基础指令(简易版)
linux·服务器
云计算老刘3 小时前
1. Cockpit 管理服务器;2. Linux 软件包管理
linux·运维·服务器·云原生·云计算
一匹电信狗4 小时前
【C++11】Lambda表达式+新的类功能
服务器·c++·算法·leetcode·小程序·stl·visual studio
小苏兮4 小时前
【把Linux“聊”明白】进程的概念与状态
linux·运维·服务器·学习
wsad05324 小时前
Ubuntu 24.04 更换国内软件源(以阿里云为例)
linux·ubuntu·阿里云