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

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

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

相关推荐
HarmonLTS28 分钟前
Python Socket网络通信详解
服务器·python·网络安全
sun00770032 分钟前
androd和qnx判断实网卡还是虚网卡
运维·服务器·网络
郝学胜-神的一滴39 分钟前
Python数据封装与私有属性:保护你的数据安全
linux·服务器·开发语言·python·程序人生
口嗨农民工1 小时前
live555 sample基本解读
运维·服务器
175063319451 小时前
EtherCAT ubuntu wireshark
网络·ubuntu·wireshark
小宇的天下1 小时前
Synopsys Technology File and Routing Rules Reference Manual (1)
java·服务器·前端
lph0092 小时前
mqtt broker (mosquitto)创建服务器、订阅与发布
运维·服务器
酒醉的胡铁2 小时前
uniapp运行到鸿蒙证书配置
服务器·uni-app·harmonyos
weixin_462446232 小时前
ubuntu真机安装tljh jupyterhub支持跨域iframe
linux·运维·ubuntu
AI科技星2 小时前
光子的几何起源与量子本质:一个源于时空本底运动的统一模型
服务器·人工智能·线性代数·算法·机器学习