WebRTC项目架构详解

目录

[🏗️ WebRTC项目架构详解](#🏗️ WebRTC项目架构详解)

[📁 项目结构总览](#📁 项目结构总览)

服务器启动命令

启动开发服务器

直接启动Node.js

检查node_modules

[🌐 网络测试命令](#🌐 网络测试命令)

测试HTTP服务

测试通话页面

[🔧 进程管理命令](#🔧 进程管理命令)

查看Node.js进程

等待命令

[🛠️ 权限管理命令](#🛠️ 权限管理命令)

修复npm权限

[📊 系统监控命令](#📊 系统监控命令)

检查网络连接

检查进程状态

完整工作流程命令序列

项目初始化流程:

问题排查流程:

[📋 常用命令组合](#📋 常用命令组合)

快速启动:

完整重启:

状态检查:

[⚠️ 常见问题解决](#⚠️ 常见问题解决)

[1. npm安装卡住](#1. npm安装卡住)

[2. 权限问题](#2. 权限问题)

[3. 端口占用](#3. 端口占用)

关闭服务器的方法总结

[✅ 方法1:Ctrl+C(最简单)](#✅ 方法1:Ctrl+C(最简单))

[✅ 方法2:查找并杀死进程](#✅ 方法2:查找并杀死进程)

[✅ 方法3:使用pkill命令](#✅ 方法3:使用pkill命令)

[✅ 方法4: 使用lsof查找端口](#✅ 方法4: 使用lsof查找端口)

[1. 服务器端模块](#1. 服务器端模块)

[📄 `server.js` - HTTP服务器](#📄 server.js - HTTP服务器)

核心功能:

[📄 `signaling.js` - WebSocket信令服务器](#📄 signaling.js - WebSocket信令服务器)

核心功能:

[📄 `user-manager.js` - 用户管理](#📄 user-manager.js - 用户管理)

核心功能:

[2. 客户端模块](#2. 客户端模块)

[📄 `auth.js` - 认证逻辑](#📄 auth.js - 认证逻辑)

核心功能:

[📄 `webrtc-client.js` - WebRTC核心](#📄 webrtc-client.js - WebRTC核心)

核心功能:

[🚀 完整流程解析](#🚀 完整流程解析)

[1. 服务器启动流程](#1. 服务器启动流程)

[2. 客户端连接流程](#2. 客户端连接流程)

[3. WebRTC连接建立流程](#3. WebRTC连接建立流程)

[🎯 关键技术点](#🎯 关键技术点)

[📡 WebRTC项目协议设计详解](#📡 WebRTC项目协议设计详解)

[🏗️ 协议架构层次](#🏗️ 协议架构层次)

[1. 传输层协议](#1. 传输层协议)

[2. 应用层协议](#2. 应用层协议)

[3. 媒体传输协议](#3. 媒体传输协议)

[📋 协议消息规范](#📋 协议消息规范)

[🔐 认证协议](#🔐 认证协议)

[客户端 → 服务器](#客户端 → 服务器)

[服务器 → 客户端](#服务器 → 客户端)

[👥 用户管理协议](#👥 用户管理协议)

用户列表广播

[📞 通话控制协议](#📞 通话控制协议)

发起通话

通话响应

[🔄 WebRTC信令协议](#🔄 WebRTC信令协议)

Offer消息

Answer消息

ICE候选

挂断消息

错误消息

[🔄 完整通信流程](#🔄 完整通信流程)

[1. 连接建立阶段](#1. 连接建立阶段)

[2. 通话建立阶段](#2. 通话建立阶段)

[🛡️ 协议安全设计](#🛡️ 协议安全设计)

[1. 连接安全](#1. 连接安全)

[2. 消息验证](#2. 消息验证)

[3. 状态管理](#3. 状态管理)

[📊 协议性能优化](#📊 协议性能优化)

[1. 消息压缩](#1. 消息压缩)

[2. 连接管理](#2. 连接管理)

[3. 状态同步](#3. 状态同步)

[🎯 协议特点总结](#🎯 协议特点总结)

[✅ 优势](#✅ 优势)

[🔧 技术特点](#🔧 技术特点)

[🔄 协议消息类型总结](#🔄 协议消息类型总结)


**本文摘要:**本文详细解析了一个基于WebRTC的视频通话系统架构与协议设计。系统采用分层结构,服务器端包含HTTP服务器、WebSocket信令服务和用户管理模块;客户端实现认证和WebRTC核心功能。关键协议采用JSON格式消息,涵盖登录认证、用户管理、通话控制等全流程,支持WebRTC信令交换(Offer/Answer/ICE候选)。通过WebSocket实现实时通信后建立P2P音视频连接,具有状态管理完善、错误处理健全等特点,并提供了完整的命令行操作指南和问题解决方案。该系统实现了信令中转与P2P传输的高效结合,确保实时通信质量与系统稳定性。

相关文章,对本文章项目一些概念和细节的补充:WebRTC学习中各项概念笔记

🏗️ WebRTC项目架构详解

📁 项目结构总览

bash 复制代码
webrtcDemo/
├── server/                 # 服务器端
│   ├── server.js          # HTTP服务器 + 静态文件服务
│   ├── signaling.js       # WebSocket信令服务器
│   ├── user-manager.js    # 用户管理模块
│   └── package.json        # 依赖配置
└── client/                # 客户端
    ├── index.html         # 主页
    ├── login.html         # 登录页
    ├── call.html          # 通话页
    ├── css/style.css      # 样式文件
    └── js/
        ├── auth.js        # 认证逻辑
        ├── webrtc-client.js # WebRTC核心
        └── ui.js          # UI交互

服务器启动命令

启动开发服务器
bash 复制代码
npm start

作用: 启动Node.js服务器

说明: 执行package.json中定义的start脚本

输出示例:

> server@1.0.0 start

> node server.js

服务器运行在 http://localhost:3000

直接启动Node.js
bash 复制代码
node server.js

作用: 直接运行服务器文件

说明: 等同于npm start,但更直接

检查node_modules
bash 复制代码
ls -la node_modules

作用: 检查依赖包是否安装成功

说明: 如果node_modules不存在,说明依赖未安装

🌐 网络测试命令

测试HTTP服务

bash 复制代码
curl -s http://localhost:3000 | head -10

作用: 测试服务器是否响应

参数说明:

  • -s: 静默模式
  • head -10: 只显示前10行

输出示例:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebRTC 视频通话</title>

测试通话页面

bash 复制代码
curl -s http://localhost:3000/call | head -10

作用: 测试通话页面路由

说明: 验证服务器路由配置

🔧 进程管理命令

查看Node.js进程

bash 复制代码
ps aux | grep node

作用: 查看正在运行的Node.js进程

输出示例:

bash 复制代码
pupu       32063   0.0  0.3 411112512  54640   ??  S    12:52PM   0:00.11 node server.js

说明: 确认服务器进程正在运行

等待命令

bash 复制代码
sleep 3

作用: 等待3秒

说明: 给服务器启动时间,然后测试连接

🛠️ 权限管理命令

修复npm权限

bash 复制代码
sudo chown -R 501:20 "/Users/pupu/.npm"

作用: 修复npm缓存权限问题

说明: 解决EACCES权限错误

注意: 需要管理员权限

📊 系统监控命令

检查网络连接

bash 复制代码
netstat -an | grep :443

作用: 检查HTTPS端口使用情况

说明: 验证网络连接状态

检查进程状态

bash 复制代码
ps ajx | grep -v grep|grep npm

作用: 查看npm相关进程

说明: 检查npm安装是否卡住

完整工作流程命令序列

项目初始化流程:

1. 进入服务器目录

bash 复制代码
cd /Users/zhouxinrui/Desktop/code/webrtcDemo/server

2. 清理npm缓存

bash 复制代码
npm cache clean --force

3. 安装依赖

bash 复制代码
npm install --no-optional

4. 启动服务器

bash 复制代码
npm start

问题排查流程:

1. 检查目录结构

bash 复制代码
ls -la

2. 检查依赖安装

bash 复制代码
ls -la node_modules

3. 测试服务器

bash 复制代码
curl -s http://localhost:3000

4. 检查进程

bash 复制代码
ps aux | grep node

📋 常用命令组合

快速启动:

bash 复制代码
cd server && npm start

完整重启:

bash 复制代码
cd server && npm cache clean --force && npm install && npm start

状态检查:

bash 复制代码
ps aux | grep node && curl -s http://localhost:3000 | head -5

⚠️ 常见问题解决

1. npm安装卡住

解决方案1: 清理缓存

bash 复制代码
npm cache clean --force

解决方案2: 使用国内镜像

bash 复制代码
npm config set registry https://registry.npmmirror.com

解决方案3: 跳过可选依赖

bash 复制代码
npm install --no-optional

2. 权限问题

修复npm权限

bash 复制代码
sudo chown -R 501:20 "/Users/zhouxinrui/.npm"

3. 端口占用

bash 复制代码
# 检查端口使用

lsof -i :3000

# 杀死进程

kill -9 <PID>

这些命令涵盖了WebRTC项目从安装到运行的完整生命周期,每个命令都有其特定的作用和适用场景!

关闭服务器的方法总结

✅ 方法1:Ctrl+C(最简单)

如果服务器在终端前台运行:

bash 复制代码
Ctrl + C

✅ 方法2:查找并杀死进程

bash 复制代码
# 查找服务器进程

ps aux | grep "node server.js"

# 杀死进程(替换PID为实际进程ID)

kill <PID>

# 或者强制杀死

kill -9 <PID>

✅ 方法3:使用pkill命令

bash 复制代码
# 杀死所有node server.js进程

pkill -f "node server.js"

# 或者更精确的匹配

pkill -f "server.js"

✅ 方法4: 使用lsof查找端口

bash 复制代码
# 查找占用3000端口的进程

lsof -i :3000

# 杀死占用端口的进程

kill -9 <PID>

🔧 各模块详细解析

1. 服务器端模块

📄 `server.js` - HTTP服务器

javascript 复制代码
/*----------------------------模块引入部分------------------------------*/
// 引入Express.js框架(Express是Node.js最流行的应用框架、用于快速搭建HTTP服务器和处理路由)。
const express = require('express');
// 引入 Node.js 内置的 HTTP 模块、用于创建HTTP服务器。
const http = require('http');
// 引入Node.js内置的路径模块、提供处理文件和目录路径的工具函数,如path.join();
const path = require('path');

//引入自定义信令服务器模块、从当前signaling.js文件导入、这个模块包含WebRTC信令服务器的相关逻辑。
const SignalingServer = require('./signaling');

/*----------------------------模块引入部分------------------------------*/


/*-----------------------初始化服务器------------------------------*/
// 创建Express应用实例、这是整个Web应用的核心对象,用于配置中间件、路由等。
const app = express(); 
// 使用Express应用创建HTTP服务器
const server = http.createServer(app);
/*-----------------------初始化服务器------------------------------*/


/*-----------------------中间件配置部分---------------------------*/
// 设置静态文件目录(客户端文件)
// express.static():Express 的内置中间件,用于提供静态文件(HTML、CSS、JS、图片等)

// path.join(__dirname, '../client'):构建静态文件目录的绝对路径

// __dirname:当前文件所在目录

// '../client':上一级目录中的 client 文件夹

// 这意味着 ../client 目录下的所有文件都可以通过 URL 直接访问
app.use(express.static(path.join(__dirname, '../client')));
/*-----------------------中间件配置部分---------------------------*/

/*-----------------------路由配置部分---------------------------*/
// 提供客户端页面
// 作用:定义根路径 '/' 的路由处理
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '../client/login.html'));
});
// 说明:

// 当用户访问网站根路径时(如 http://localhost:3000/)

// 服务器会发送 ../client/login.html 文件给客户端

// res.sendFile():发送整个文件内容,而不是渲染模板

// 定义 '/call' 路径的路由处理
app.get('/call', (req, res) => {
  res.sendFile(path.join(__dirname, '../client/call.html'));
});
// 说明:

// 当用户访问 /call 路径时(如 http://localhost:3000/call)

// 服务器发送 ../client/call.html 文件

// 这很可能是视频通话的主页面
/*-----------------------路由配置部分---------------------------*/

/*-----------------------启动信令服务器---------------------------*/
// 启动信令服务器
new SignalingServer(server);
/*
实例化 SignalingServer 类,并将 HTTP 服务器实例传递给它

这样信令服务器就可以在同一个端口上处理 WebSocket 连接

信令服务器负责处理 WebRTC 的 Offer/Answer 交换和 ICE 候选信息传递
 */
/*-----------------------启动信令服务器---------------------------*/

// 启动 HTTP 服务器
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`);
});
/*-----------------------启动信令服务器---------------------------*/

核心功能:

  • 🌐 提供HTTP服务(端口3000)
  • 📁 静态文件服务(客户端文件)
  • 🔗 路由管理(首页、登录页、通话页)

📄 `signaling.js` - WebSocket信令服务器

javascript 复制代码
// 导入必要的模块:
const WebSocket = require('ws');
const { v4: uuidv4 } = require('uuid');
const UserManager = require('./user-manager');

class SignalingServer {
  constructor(server) {
    // 基于传入的HTTP服务器创建WebSocket服务器
    this.wss = new WebSocket.Server({ server });
    // 创建用户管理器实例,用于管理在线用户和通话状态
    this.userManager = new UserManager();
    // 设置WebSocket事件监听:
    this.setupWebSocket();
  }

  setupWebSocket() {
    this.wss.on('connection', (ws) => {
      console.log('新的WebSocket连接');
      
      ws.on('message', (data) => {  // 处理接收到的WebSocket消息
        try {
          const message = JSON.parse(data);  // 解析JSON格式的消息
          this.handleMessage(ws, message);  // 调用消息处理器
        } catch (error) {
          console.error('消息解析错误:', error);
          this.sendError(ws, '消息格式错误');  // 发送格式错误响应
        }
      });

      ws.on('close', () => {  // 处理WebSocket连接关闭
        console.log('WebSocket连接关闭');
        const user = this.userManager.removeUser(ws);  // 从用户管理器中移除用户
        if (user) {
          this.broadcastUserList();  // 广播更新后的用户列表
        }
      });

      ws.on('error', (error) => {  // 处理WebSocket错误
        console.error('WebSocket错误:', error);
      });
    });
  }

  /**
   * 处理WebSocket接收到的消息
   * 
   * @param {WebSocket} ws - 客户端WebSocket连接对象
   * @param {Object} message - 接收到的消息对象
   * @param {string} message.type - 消息类型
   * @param {Object} message.data - 消息数据
   * 
   * @description
   * 根据不同的消息类型调用对应的处理函数:
   * - login: 处理登录请求
   * - call_request: 处理呼叫请求
   * - call_response: 处理呼叫响应
   * - offer: 处理WebRTC offer
   * - answer: 处理WebRTC answer
   * - ice_candidate: 处理ICE候选
   * - hangup: 处理挂断请求
   * - 其他: 返回未知类型错误
   */
  handleMessage(ws, message) {
    const { type, data } = message;

    switch (type) {
      case 'login':
        this.handleLogin(ws, data);
        break;
      case 'call_request':
        this.handleCallRequest(ws, data);
        break;
      case 'call_response':
        this.handleCallResponse(ws, data);
        break;
      case 'offer':
        this.handleOffer(ws, data);
        break;
      case 'answer':
        this.handleAnswer(ws, data);
        break;
      case 'ice_candidate':
        this.handleIceCandidate(ws, data);
        break;
      case 'hangup':
        this.handleHangup(ws, data);
        break;
      default:
        console.log('未知消息类型:', type);
        this.sendError(ws, '未知消息类型');
    }
  }

  // 处理用户登录
  handleLogin(ws, data) {
    const { userName } = data;
    
    // 验证用户名不为空
    if (!userName) {
      this.sendError(ws, '用户名不能为空');
      return;
    }

    // 生成唯一用户ID并添加用户
    const userId = uuidv4();
    const user = this.userManager.addUser(userId, userName, ws);

    // 发送登录成功响应
    this.sendTo(ws, {
      type: 'login_success',
      data: {
        userId,
        userName,
        onlineUsers: this.userManager.getOnlineUsers()
      }
    });

    // 广播更新用户列表
    this.broadcastUserList();
  }

  /**
   * 处理呼叫请求
   * 
   * @param {WebSocket} ws - 发起呼叫的用户WebSocket连接
   * @param {Object} data - 呼叫请求数据
   * @param {string} data.toUserId - 被呼叫用户的ID
   * 
   * @description
   * 验证呼叫请求的有效性,包括:
   * - 检查主叫用户是否已登录
   * - 检查被叫用户是否在线
   * - 检查被叫用户是否正在通话中
   * 
   * 验证通过后:
   * - 设置双方用户的通话状态为通话中
   * - 向被叫用户发送呼叫请求通知
   * - 广播更新用户列表
   */
  handleCallRequest(ws, data) {
    const fromUser = this.userManager.getUserBySocket(ws);
    const { toUserId } = data;

    if (!fromUser) {
      this.sendError(ws, '请先登录');
      return;
    }

    const toUser = this.userManager.getUser(toUserId);
    if (!toUser || !toUser.online) {
      this.sendError(ws, '用户不在线');
      return;
    }

    if (toUser.inCall) {
      this.sendError(ws, '用户正在通话中');
      return;
    }

    // 设置用户通话状态
    this.userManager.setUserInCall(fromUser.id, true);
    this.userManager.setUserInCall(toUserId, true);

    // 发送呼叫请求给被叫方
    this.sendTo(toUser.ws, {
      type: 'call_request',
      data: {
        fromUserId: fromUser.id,
        fromUserName: fromUser.name
      }
    });

    // 广播更新用户列表
    this.broadcastUserList();
  }

  // 处理呼叫响应
  handleCallResponse(ws, data) {
    const user = this.userManager.getUserBySocket(ws);
    const { toUserId, accepted } = data;

    if (!user) {
      this.sendError(ws, '请先登录');
      return;
    }

    const toUser = this.userManager.getUser(toUserId);
    if (!toUser || !toUser.online) {
      this.sendError(ws, '用户不在线');
      return;
    }

    // 发送呼叫响应给主叫方
    this.sendTo(toUser.ws, {
      type: 'call_response',
      data: {
        fromUserId: user.id,
        fromUserName: user.name,
        accepted
      }
    });

    if (!accepted) {
      // 如果拒绝通话,重置通话状态
      this.userManager.setUserInCall(user.id, false);
      this.userManager.setUserInCall(toUserId, false);
      this.broadcastUserList();
    }
  }

  // 处理 WebRTC Offer
  handleOffer(ws, data) {
    const fromUser = this.userManager.getUserBySocket(ws);
    const { toUserId, offer } = data;

    if (!fromUser) {
      this.sendError(ws, '请先登录');
      return;
    }

    const toUser = this.userManager.getUser(toUserId);
    if (!toUser || !toUser.online) {
      this.sendError(ws, '用户不在线');
      return;
    }

    // 转发 Offer 给被叫方
    this.sendTo(toUser.ws, {
      type: 'offer',
      data: {
        fromUserId: fromUser.id,
        offer
      }
    });
  }

  // 处理 WebRTC Answer
  handleAnswer(ws, data) {
    const fromUser = this.userManager.getUserBySocket(ws);
    const { toUserId, answer } = data;

    if (!fromUser) {
      this.sendError(ws, '请先登录');
      return;
    }

    const toUser = this.userManager.getUser(toUserId);
    if (!toUser || !toUser.online) {
      this.sendError(ws, '用户不在线');
      return;
    }

    // 转发 Answer 给主叫方
    this.sendTo(toUser.ws, {
      type: 'answer',
      data: {
        fromUserId: fromUser.id,
        answer
      }
    });
  }

  // 处理 ICE 候选
  handleIceCandidate(ws, data) {
    const fromUser = this.userManager.getUserBySocket(ws);
    const { toUserId, candidate } = data;

    if (!fromUser) {
      this.sendError(ws, '请先登录');
      return;
    }

    const toUser = this.userManager.getUser(toUserId);
    if (!toUser || !toUser.online) {
      this.sendError(ws, '用户不在线');
      return;
    }

    // 转发 ICE 候选
    this.sendTo(toUser.ws, {
      type: 'ice_candidate',
      data: {
        fromUserId: fromUser.id,
        candidate
      }
    });
  }

  // 处理挂断
  handleHangup(ws, data) {
    const user = this.userManager.getUserBySocket(ws);
    const { toUserId } = data;

    if (!user) {
      this.sendError(ws, '请先登录');
      return;
    }

    // 重置通话状态
    this.userManager.setUserInCall(user.id, false);

    // 如果指定了对方,也重置对方状态并通知
    if (toUserId) {
      this.userManager.setUserInCall(toUserId, false);
      const toUser = this.userManager.getUser(toUserId);
      if (toUser && toUser.online) {
        this.sendTo(toUser.ws, {
          type: 'hangup',
          data: {
            fromUserId: user.id
          }
        });
      }
    }

    // 广播更新用户列表
    this.broadcastUserList();
  }

  // 发送消息给指定 WebSocket
  sendTo(ws, message) {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify(message));
    }
  }

  // 发送错误消息
  sendError(ws, errorMessage) {
    this.sendTo(ws, {
      type: 'error',
      data: { message: errorMessage }
    });
  }

  // 广播用户列表给所有客户端
  broadcastUserList() {
    const userList = this.userManager.getOnlineUsers();
    const message = {
      type: 'user_list',
      data: { users: userList }
    };

    this.wss.clients.forEach(client => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(JSON.stringify(message));
      }
    });
  }
}

module.exports = SignalingServer;

核心功能:

  • 🔌 WebSocket连接管理

  • 📨 消息路由和转发

  • 👥 用户状态管理

  • 🔄 WebRTC信令转发

📄 `user-manager.js` - 用户管理

cpp 复制代码
class UserManager {
    constructor() {
      this.users = new Map(); // userId -> {id, name, ws, online, inCall}
      this.userSockets = new Map(); // socket -> user
    }
  
    // 添加用户
    addUser(userId, userName, ws) {
      const user = {
        id: userId,
        name: userName,
        ws: ws,
        online: true,
        inCall: false
      };
      
      this.users.set(userId, user);
      this.userSockets.set(ws, user);
      
      console.log(`用户 ${userName} (${userId}) 上线`);
      return user;
    }
  
    // 移除用户
    removeUser(ws) {
      const user = this.userSockets.get(ws);
      if (user) {
        user.online = false;
        this.users.delete(user.id);
        this.userSockets.delete(ws);
        console.log(`用户 ${user.name} 下线`);
        return user;
      }
      return null;
    }
  
    // 获取用户
    getUser(userId) {
      return this.users.get(userId);
    }
  
  // 获取所有在线用户(包括通话中的用户)
  getOnlineUsers() {
    const onlineUsers = [];
    for (const [id, user] of this.users) {
      if (user.online) {
        onlineUsers.push({
          id: user.id,
          name: user.name,
          inCall: user.inCall,
          status: user.inCall ? '通话中' : '在线'
        });
      }
    }
    return onlineUsers;
  }
  
    // 设置用户通话状态
    setUserInCall(userId, inCall) {
      const user = this.users.get(userId);
      if (user) {
        user.inCall = inCall;
      }
    }
  
    // 通过WebSocket获取用户
    getUserBySocket(ws) {
      return this.userSockets.get(ws);
    }
  }
  
  module.exports = UserManager;

核心功能:

  • 👤 用户注册和认证
  • 📊 在线用户管理(包括通话中用户)
  • 🔄 用户状态跟踪
  • 📡 用户列表广播

2. 客户端模块

📄 `auth.js` - 认证逻辑

javascript 复制代码
// 登录逻辑
document.getElementById('loginForm').addEventListener('submit', function(e) {
    e.preventDefault(); // 阻止表单默认提交
    
    const userName = document.getElementById('userName').value.trim();
    const errorMessage = document.getElementById('errorMessage');
    
    if (!userName) {
        showError('请输入用户名');
        return;
    }
    
    // 存储用户名并跳转到通话页面
    localStorage.setItem('userName', userName);
    window.location.href = '/call';
});

function showError(message) {
    const errorElement = document.getElementById('errorMessage');
    errorElement.textContent = message;
    errorElement.style.display = 'block';
}

// 检查是否已登录
window.addEventListener('DOMContentLoaded', () => {
    const savedUserName = localStorage.getItem('userName');
    if (savedUserName && window.location.pathname === '/') {
        window.location.href = '/call';
    }
});

核心功能:

  • 🔐 用户登录验证
  • 💾 本地存储管理
  • 🔄 自动登录检查

📄 `webrtc-client.js` - WebRTC核心

javascript 复制代码
class WebRTCClient {
    constructor() {
        this.ws = null;
        this.userId = null;
        this.userName = null;
        this.peerConnection = null;
        this.localStream = null;
        this.remoteStream = null;
        this.currentCall = null;
        
        // WebRTC 配置
        this.rtcConfig = {
            iceServers: [
                { urls: 'stun:stun.l.google.com:19302' }, //提供服务器地址,帮助NAT获取公网IP
                { urls: 'stun:stun1.l.google.com:19302' }
            ]
        };
        
        this.init();
    }
    
    init() {
        this.setupEventListeners();
        this.connectWebSocket();
        this.setupMedia();
    }
    
    /**
     * 设置所有UI事件监听器
     * 
     * 该方法负责为WebRTC客户端的所有用户界面元素绑定事件处理函数,
     * 包括登出按钮、通话控制按钮(视频切换、音频切换、挂断)以及来电处理按钮。
     * 
     * @memberof WebRTCClient
     * @returns {void}
     */
    setupEventListeners() {
        // 登出按钮
        document.getElementById('logoutBtn').addEventListener('click', () => {
            this.logout();
        });
        
        // 通话控制按钮
        document.getElementById('toggleVideo').addEventListener('click', () => {
            this.toggleVideo();
        });
        
        document.getElementById('toggleAudio').addEventListener('click', () => {
            this.toggleAudio();
        });
        
        document.getElementById('hangupBtn').addEventListener('click', () => {
            this.hangup();
        });
        
        // 来电处理
        document.getElementById('acceptCall').addEventListener('click', () => {
            this.acceptCall();
        });
        
        document.getElementById('rejectCall').addEventListener('click', () => {
            this.rejectCall();
        });
    }
    
    // 连接 WebSocket
    connectWebSocket() {
        // 根据当前协议确定 WebSocket 协议(HTTP 用 ws://,HTTPS 用 wss://)
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        const wsUrl = `${protocol}//${window.location.host}`;
        
        // 创建 WebSocket 连接
        this.ws = new WebSocket(wsUrl);
        
        // 连接建立成功回调
        this.ws.onopen = () => {
            console.log('WebSocket 连接已建立');
            this.login(); // 连接成功后立即登录
        };
        
        // 接收服务器消息回调
        this.ws.onmessage = (event) => {
            this.handleMessage(JSON.parse(event.data)); // 解析并处理消息
        };
        
        // 连接关闭回调
        this.ws.onclose = () => {
            console.log('WebSocket 连接已关闭');
            // 尝试重新连接(3秒后重连)
            setTimeout(() => this.connectWebSocket(), 3000);
        };
        
        // 连接错误回调
        this.ws.onerror = (error) => {
            console.error('WebSocket 错误:', error);
        };
    }
    
    // 处理服务器消息
    handleMessage(message) {
        const { type, data } = message;
        
        switch (type) {
            case 'login_success':
                this.handleLoginSuccess(data);
                break;
            case 'user_list':
                this.updateUserList(data.users);
                break;
            case 'call_request':
                this.handleIncomingCall(data);
                break;
            case 'call_response':
                this.handleCallResponse(data);
                break;
            case 'offer':
                this.handleOffer(data);
                break;
            case 'answer':
                this.handleAnswer(data);
                break;
            case 'ice_candidate':
                this.handleIceCandidate(data);
                break;
            case 'hangup':
                this.handleRemoteHangup(data);
                break;
            case 'error':
                this.showError(data.message);
                break;
        }
    }
    
    /**
     * 发送消息到服务器
     * 
     * @param {Object} message - 要发送的消息对象
     * @param {string} message.type - 消息类型
     * @param {Object} message.data - 消息数据
     */
    sendMessage(message) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(message));
        }
    }
    
    // 用户登录
    login() {
        const userName = localStorage.getItem('userName');
        if (!userName) {
            window.location.href = '/';
            return;
        }
        
        this.userName = userName;
        document.getElementById('userNameDisplay').textContent = userName;
        
        this.sendMessage({
            type: 'login',
            data: { userName }
        });
    }
    
    // 处理登录成功
    handleLoginSuccess(data) {
        this.userId = data.userId;
        this.updateUserList(data.onlineUsers);
    }
    
    // 更新通话状态显示
    updateCallStatus() {
        if (this.currentCall) {
            const statusElement = document.getElementById('callStatus');
            const remoteUserInfo = document.getElementById('remoteUserInfo');
            const remoteVideoTitle = document.getElementById('remoteVideoTitle');
            
            if (this.currentCall.initiated) {
                // 发起方显示"正在呼叫..."
                if (statusElement) {
                    statusElement.textContent = '正在呼叫...';
                }
                if (remoteVideoTitle) {
                    remoteVideoTitle.textContent = '等待对方接听...';
                }
            } else {
                // 接听方显示"与 [对方姓名] 通话中"
                if (statusElement) {
                    statusElement.textContent = `与 ${this.currentCall.fromUserName} 通话中`;
                }
                if (remoteVideoTitle) {
                    remoteVideoTitle.textContent = `${this.currentCall.fromUserName} 的视频`;
                }
                // 显示对方信息
                this.showRemoteUserInfo(this.currentCall.fromUserName);
            }
        }
    }
    
    // 显示对方用户信息
    showRemoteUserInfo(userName) {
        const remoteUserInfo = document.getElementById('remoteUserInfo');
        const userNameDisplay = document.querySelector('.user-name-display');
        
        if (remoteUserInfo && userNameDisplay) {
            userNameDisplay.textContent = userName;
            remoteUserInfo.style.display = 'flex';
        }
    }
    
    // 隐藏对方用户信息
    hideRemoteUserInfo() {
        const remoteUserInfo = document.getElementById('remoteUserInfo');
        if (remoteUserInfo) {
            remoteUserInfo.style.display = 'none';
        }
    }
    
    // 更新发起方的通话状态
    updateCallStatusForInitiator(remoteUserName) {
        const statusElement = document.getElementById('callStatus');
        const remoteVideoTitle = document.getElementById('remoteVideoTitle');
        
        if (statusElement) {
            statusElement.textContent = `与 ${remoteUserName} 通话中`;
        }
        
        if (remoteVideoTitle) {
            remoteVideoTitle.textContent = `${remoteUserName} 的视频`;
        }
    }
    
    // 更新用户列表
    updateUserList(users) {
        const userListElement = document.getElementById('userList');
        userListElement.innerHTML = '';
        
        users.forEach(user => {
            if (user.id !== this.userId) {
                const userElement = document.createElement('div');
                userElement.className = 'user-item';
                
                // 根据用户状态显示不同的按钮和样式
                let buttonHtml = '';
                let statusClass = '';
                
                if (user.inCall) {
                    buttonHtml = '<span class="status-badge in-call">通话中</span>';
                    statusClass = 'in-call';
                } else {
                    buttonHtml = '<button class="call-btn" data-user-id="' + user.id + '">呼叫</button>';
                    statusClass = 'available';
                }
                
                userElement.className = `user-item ${statusClass}`;
                userElement.innerHTML = `
                    <span class="user-name">${user.name}</span>
                    <span class="user-status">${user.status}</span>
                    ${buttonHtml}
                `;
                userListElement.appendChild(userElement);
                
                // 只有非通话中的用户才能被呼叫
                if (!user.inCall) {
                    userElement.querySelector('.call-btn').addEventListener('click', () => {
                        this.startCall(user.id);
                    });
                }
            }
        });
    }
    
    // 开始呼叫
    async startCall(toUserId) {
        if (this.currentCall) {
            this.showError('您正在通话中');
            return;
        }
        
        this.currentCall = {
            toUserId,
            initiated: true
        };
        
        // 只发送呼叫请求,不立即创建WebRTC连接
        this.sendMessage({
            type: 'call_request',
            data: { toUserId }
        });
        
        this.updateUIForCall(true);
    }
    
    // 处理来电
    handleIncomingCall(data) {
        this.currentCall = {
            fromUserId: data.fromUserId,
            fromUserName: data.fromUserName,
            initiated: false
        };
        
        // 显示来电对话框
        document.getElementById('callerName').textContent = data.fromUserName;
        document.getElementById('incomingCallModal').style.display = 'block';
    }
    
    // 接听来电
    async acceptCall() {
        document.getElementById('incomingCallModal').style.display = 'none';
        
        // 发送呼叫接受响应
        this.sendMessage({
            type: 'call_response',
            data: {
                toUserId: this.currentCall.fromUserId,
                accepted: true
            }
        });
        
        this.updateUIForCall(true);
        // 更新通话状态显示
        this.updateCallStatus();
    }
    
    // 拒绝来电
    rejectCall() {
        document.getElementById('incomingCallModal').style.display = 'none';
        
        this.sendMessage({
            type: 'call_response',
            data: {
                toUserId: this.currentCall.fromUserId,
                accepted: false
            }
        });
        
        this.currentCall = null;
    }
    
    // 处理呼叫响应
    async handleCallResponse(data) {
        if (!data.accepted) {
            this.showError('对方拒绝了您的呼叫');
            this.hangup();
            return;
        }
        
        // 对方接受呼叫后,开始建立WebRTC连接
        try {
            // 创建 PeerConnection
            this.createPeerConnection();
            
            // 添加本地流
            if (this.localStream) {
                this.localStream.getTracks().forEach(track => {
                    this.peerConnection.addTrack(track, this.localStream);
                });
            }
            
            // 创建并发送 Offer
            const offer = await this.peerConnection.createOffer();
            await this.peerConnection.setLocalDescription(offer);
            
            this.sendMessage({
                type: 'offer',
                data: {
                    toUserId: this.currentCall.toUserId,
                    offer
                }
            });
            
            // 更新发起方的通话状态 - 显示与对方通话中
            this.updateCallStatusForInitiator(data.fromUserName || '对方');
            
            // 显示对方信息(发起方)
            this.showRemoteUserInfo(data.fromUserName || '对方');
            
        } catch (error) {
            console.error('建立WebRTC连接失败:', error);
            this.showError('连接失败');
            this.hangup();
        }
    }
    
    // 创建 PeerConnection
    createPeerConnection() {
        // 创建新的RTCPeerConnection实例
        this.peerConnection = new RTCPeerConnection(this.rtcConfig);
        
        // 处理远程流
        this.peerConnection.ontrack = (event) => {
            const remoteVideo = document.getElementById('remoteVideo');
            if (event.streams && event.streams[0]) {
                remoteVideo.srcObject = event.streams[0];  // 设置远程视频源
                this.remoteStream = event.streams[0];      // 保存远程流引用
            }
        };
        
        // 处理 ICE 候选
        this.peerConnection.onicecandidate = (event) => {
            if (event.candidate && this.currentCall) {
                // 确定消息接收方ID
                const toUserId = this.currentCall.initiated ? 
                    this.currentCall.toUserId : this.currentCall.fromUserId;
                
                // 发送ICE候选信息给对方
                this.sendMessage({
                    type: 'ice_candidate',
                    data: {
                        toUserId,
                        candidate: event.candidate
                    }
                });
            }
        };
        
        // 处理连接状态变化
        this.peerConnection.onconnectionstatechange = () => {
            console.log('连接状态:', this.peerConnection.connectionState);
            
            if (this.peerConnection.connectionState === 'connected') {
                console.log('WebRTC 连接已建立');
            } else if (this.peerConnection.connectionState === 'disconnected' ||
                      this.peerConnection.connectionState === 'failed') {
                console.log('WebRTC 连接断开');
                this.hangup();  // 连接断开时挂断通话
            }
        };
    }
    
    // 处理 Offer
    async handleOffer(data) {
        // 只有在接听状态下才处理Offer
        if (!this.currentCall || this.currentCall.initiated) {
            console.log('忽略Offer:未在接听状态');
            return;
        }
        
        if (!this.peerConnection) {
            this.createPeerConnection();
            
            // 添加本地流
            if (this.localStream) {
                this.localStream.getTracks().forEach(track => {
                    this.peerConnection.addTrack(track, this.localStream);
                });
            }
        }
        
        try {
            await this.peerConnection.setRemoteDescription(data.offer);
            const answer = await this.peerConnection.createAnswer();
            await this.peerConnection.setLocalDescription(answer);
            
            this.sendMessage({
                type: 'answer',
                data: {
                    toUserId: data.fromUserId,
                    answer
                }
            });
            
        } catch (error) {
            console.error('处理 Offer 失败:', error);
            this.hangup();
        }
    }
    
    // 处理 Answer
    async handleAnswer(data) {
        try {
            await this.peerConnection.setRemoteDescription(data.answer);
        } catch (error) {
            console.error('处理 Answer 失败:', error);
            this.hangup();
        }
    }
    
    // 处理 ICE 候选
    async handleIceCandidate(data) {
        try {
            await this.peerConnection.addIceCandidate(data.candidate);
        } catch (error) {
            console.error('添加 ICE 候选失败:', error);
        }
    }
    
    // 处理远程挂断
    handleRemoteHangup() {
        this.showError('对方已挂断');
        this.hangup();
    }
    
    // 挂断通话
    hangup() {
        if (this.currentCall) {
            const toUserId = this.currentCall.initiated ? 
                this.currentCall.toUserId : this.currentCall.fromUserId;
            
            this.sendMessage({
                type: 'hangup',
                data: { toUserId }
            });
        }
        
        this.cleanupCall();
    }
    
    // 清理通话资源
    cleanupCall() {
        if (this.peerConnection) {
            this.peerConnection.close();
            this.peerConnection = null;
        }
        
        // 清除远程视频
        const remoteVideo = document.getElementById('remoteVideo');
        remoteVideo.srcObject = null;
        this.remoteStream = null;
        
        // 隐藏对方信息
        this.hideRemoteUserInfo();
        
        // 重置远程视频标题
        const remoteVideoTitle = document.getElementById('remoteVideoTitle');
        if (remoteVideoTitle) {
            remoteVideoTitle.textContent = '远程视频';
        }
        
        // 重置通话状态
        const callStatus = document.getElementById('callStatus');
        if (callStatus) {
            callStatus.textContent = '等待通话...';
        }
        
        this.currentCall = null;
        this.updateUIForCall(false);
    }
    
    // 设置媒体流
    async setupMedia() {
        try {
            // 获取用户摄像头和麦克风权限
            // 获取媒体流:使用 navigator.mediaDevices.getUserMedia() API 请求访问用户的摄像头和视频设备
            this.localStream = await navigator.mediaDevices.getUserMedia({
                video: true, //启用摄像头
                audio: true //启用麦克风
            });
            
            // 将本地视频流显示在页面上
            const localVideo = document.getElementById('localVideo');
            localVideo.srcObject = this.localStream;
            
        } catch (error) {
            console.error('获取媒体设备失败:', error);
            this.showError('无法访问摄像头或麦克风');
        }
    }
    
    // 切换视频
    toggleVideo() {
        if (this.localStream) {
            const videoTracks = this.localStream.getVideoTracks();
            if (videoTracks.length > 0) {
                const enabled = !videoTracks[0].enabled;  // 切换视频轨道启用状态
                videoTracks[0].enabled = enabled;
                
                const button = document.getElementById('toggleVideo');
                button.textContent = enabled ? '关闭视频' : '开启视频';  // 更新按钮文本
                button.classList.toggle('muted', !enabled);  // 切换静音样式
            }
        }
    }
    
    // 切换音频
    toggleAudio() {
        if (this.localStream) {
            const audioTracks = this.localStream.getAudioTracks();
            if (audioTracks.length > 0) {
                const enabled = !audioTracks[0].enabled;
                audioTracks[0].enabled = enabled;
                
                const button = document.getElementById('toggleAudio');
                button.textContent = enabled ? '关闭音频' : '开启音频';
                button.classList.toggle('muted', !enabled);
            }
        }
    }
    
    // 更新通话界面
    updateUIForCall(inCall) {
        const hangupBtn = document.getElementById('hangupBtn');
        const userList = document.getElementById('userList');
        
        hangupBtn.disabled = !inCall;
        userList.style.opacity = inCall ? '0.5' : '1';
        
        // 禁用用户列表中的呼叫按钮
        const callButtons = userList.querySelectorAll('.call-btn');
        callButtons.forEach(btn => {
            btn.disabled = inCall;
        });
    }
    
    // 显示错误信息
    showError(message) {
        // 在实际应用中,可以使用更友好的方式显示错误
        alert(message);
    }
    
    // 用户登出
    logout() {
        if (this.currentCall) {
            this.hangup();
        }
        
        localStorage.removeItem('userName');
        
        if (this.ws) {
            this.ws.close();
        }
        
        window.location.href = '/';
    }
}

// 初始化 WebRTC 客户端
let webrtcClient;

document.addEventListener('DOMContentLoaded', () => {
    if (window.location.pathname === '/call') {
        webrtcClient = new WebRTCClient();
    }
});

核心功能:

  • 🔌 WebSocket通信
  • 🎥 媒体流管理
  • 🔄 WebRTC连接建立(修复后)
  • 📡 信令处理
  • 👤 对方用户信息显示
  • 📊 通话状态管理

🚀 完整流程解析

1. 服务器启动流程

2. 客户端连接流程

3. WebRTC连接建立流程

🔄 详细实现步骤

步骤1:服务器启动

javascript 复制代码
graph TD
    A[启动 server.js] --> B[创建 Express 应用]
    B --> C[创建 HTTP 服务器]
    C --> D[设置静态文件服务]
    D --> E[配置路由]
    E --> F[启动信令服务器]
    F --> G[监听端口 3000]
    G --> H[服务器运行中]

步骤2:客户端连接

javascript 复制代码
graph TD
    A[用户访问 localhost:3000] --> B[加载 login.html]
    B --> C[用户输入用户名]
    C --> D[存储到 localStorage]
    D --> E[跳转到 /call]
    E --> F[加载 call.html]
    F --> G[初始化 WebRTCClient]
    G --> H[建立 WebSocket 连接]
    H --> I[发送登录消息]
    I --> J[服务器验证并返回用户列表]

步骤3:用户认证

javascript 复制代码
graph TD
    A[用户A点击呼叫用户B] --> B[发送呼叫请求]
    B --> C[用户B收到呼叫请求]
    C --> D[用户B选择接听/拒绝]
    D --> E{用户B是否接听?}
    E -->|拒绝| F[显示拒绝消息]
    E -->|接听| G[发送接听响应]
    G --> H[用户A收到接听响应]
    H --> I[用户A创建PeerConnection]
    I --> J[用户A创建Offer]
    J --> K[用户B处理Offer]
    K --> L[用户B创建Answer]
    L --> M[交换ICE候选]
    M --> N[建立P2P连接]
    N --> O[开始音视频传输]
    O --> P[显示对方用户信息]
    P --> Q[更新通话状态显示]

🎯 关键技术点

  1. 信令服务器的作用
  • 🔄 转发WebRTC信令消息
  • 👥 管理用户状态(包括通话中用户)
  • 📡 广播用户列表
  • 🔐 处理用户认证
javascript 复制代码
peerConnection.onicecandidate = (event) => {
  if(event.candidate) {
    // 发送候选至对方
    signalingServer.send({type: 'candidate', candidate: event.candidate});
  }
};
 
  1. 实时通信机制(最终版)
  • 🔌 WebSocket:实时双向通信
  • 📡 信令转发:服务器中转信令
  • 🎥 媒体传输:客户端直连
  • 🔄 状态同步:用户列表实时更新
  • 🛡️ 状态保护:通过状态控制连接建立时机
  • 📊 状态显示:通话状态和对方信息实时显示

📡 WebRTC项目协议设计详解

基于代码分析,这个项目采用了分层协议架构,让我为您详细解析:

🏗️ 协议架构层次

1. 传输层协议

HTTP/HTTPS (端口3000) + WebSocket (实时通信)

2. 应用层协议

JSON消息格式 + 自定义信令协议

3. 媒体传输协议

WebRTC (P2P音视频传输)

📋 协议消息规范

🔐 认证协议

客户端 → 服务器
javascript 复制代码
{
  "type": "login",
  "data": {
    "userName": "pupu"
  }
}
服务器 → 客户端
javascript 复制代码
{
  "type": "login_success",
  "data": {
    "userId": "e7a47af4-6919-4e42-8446-2a3a4b02cecc",
    "userName": "pupu",
    "onlineUsers": [
      {
        "id": "user1", 
        "name": "user1",
        "inCall": false,
        "status": "在线"
      },
      {
        "id": "user2", 
        "name": "user2",
        "inCall": true,
        "status": "通话中"
      }
    ]
  }
}

👥 用户管理协议

用户列表广播
javascript 复制代码
{
  "type": "user_list",
  "data": {
    "users": [
      {
        "id": "user1", 
        "name": "user1",
        "inCall": false,
        "status": "在线"
      },
      {
        "id": "user2", 
        "name": "user2",
        "inCall": true,
        "status": "通话中"
      }
    ]
  }
}

📞 通话控制协议

发起通话
javascript 复制代码
{
  "type": "call_request",
  "data": {
    "toUserId": "target-user-id"
  }
}
通话响应
javascript 复制代码
{
  "type": "call_response",
  "data": {
    "toUserId": "caller-user-id",
    "fromUserId": "responder-user-id",
    "fromUserName": "responder-name",
    "accepted": true
  }
}

🔄 WebRTC信令协议

Offer消息
javascript 复制代码
{
  "type": "offer",
  "data": {
    "toUserId": "target-user-id",
    "offer": {
      "type": "offer",
      "sdp": "v=0\r\no=- 1234567890 1234567890 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=msid-semantic: WMS\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:abc123\r\na=ice-pwd:def456\r\na=ice-options:trickle\r\na=fingerprint:sha-256 AA:BB:CC:DD:EE:FF\r\na=setup:actpass\r\na=mid:0\r\na=sendrecv\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=ssrc:1234567890 cname:user@host\r\na=ssrc:1234567890 msid:stream0 video0\r\na=ssrc:1234567890 mslabel:stream0\r\na=ssrc:1234567890 label:video0"
    }
  }
}
Answer消息
javascript 复制代码
{
  "type": "answer",
  "data": {
    "toUserId": "caller-user-id",
    "answer": {
      "type": "answer",
      "sdp": "v=0\r\no=- 1234567890 1234567890 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=msid-semantic: WMS\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:xyz789\r\na=ice-pwd:uvw012\r\na=ice-options:trickle\r\na=fingerprint:sha-256 FF:EE:DD:CC:BB:AA\r\na=setup:active\r\na=mid:0\r\na=sendrecv\r\na=rtcp-mux\r\na=rtcp-rsize\r\na=rtpmap:96 VP8/90000\r\na=rtcp-fb:96 nack\r\na=rtcp-fb:96 nack pli\r\na=rtcp-fb:96 ccm fir\r\na=rtcp-fb:96 goog-remb\r\na=rtcp-fb:96 transport-cc\r\na=ssrc:0987654321 cname:user@host\r\na=ssrc:0987654321 msid:stream0 video0\r\na=ssrc:0987654321 mslabel:stream0\r\na=ssrc:0987654321 label:video0"
    }
  }
}
ICE候选
javascript 复制代码
{
  "type": "ice_candidate",
  "data": {
    "toUserId": "target-user-id",
    "candidate": {
      "candidate": "candidate:1 1 UDP 2113667326 192.168.1.100 54400 typ host",
      "sdpMLineIndex": 0,
      "sdpMid": "0"
    }
  }
}
挂断消息
javascript 复制代码
{
  "type": "hangup",
  "data": {
    "toUserId": "target-user-id"
  }
}
错误消息
javascript 复制代码
{
  "type": "error",
  "data": {
    "message": "用户不在线"
  }
}

🔄 完整通信流程

1. 连接建立阶段

2. 通话建立阶段

🛡️ 协议安全设计

1. 连接安全

  • WebSocket over HTTP/HTTPS
  • 支持WSS加密连接
  • 自动重连机制

2. 消息验证

javascript 复制代码
sequenceDiagram
    participant C as 客户端
    participant S as 服务器
    
    C->>S: WebSocket连接
    S-->>C: 连接确认
    C->>S: {"type":"login","data":{"userName":"pupu"}}
    S->>S: 生成userId, 注册用户
    S-->>C: {"type":"login_success","data":{...}}
    S->>S: 广播用户列表更新

3. 状态管理·

javascript 复制代码
sequenceDiagram
    participant A as 用户A
    participant S as 服务器
    participant B as 用户B
    
    A->>S: {"type":"call_request","data":{"toUserId":"B"}}
    S->>B: {"type":"call_request","data":{"fromUserId":"A","fromUserName":"A"}}
    B->>S: {"type":"call_response","data":{"toUserId":"A","accepted":true}}
    S->>A: {"type":"call_response","data":{"fromUserName":"B","accepted":true}}
    
    A->>A: 创建PeerConnection
    A->>A: 创建Offer
    A->>S: {"type":"offer","data":{"toUserId":"B","offer":...}}
    S->>B: {"type":"offer","data":{"fromUserId":"A","offer":...}}
    
    B->>B: 创建Answer
    B->>S: {"type":"answer","data":{"toUserId":"A","answer":...}}
    S->>A: {"type":"answer","data":{"fromUserId":"B","answer":...}}
    
    A->>S: {"type":"ice_candidate","data":{"toUserId":"B","candidate":...}}
    S->>B: {"type":"ice_candidate","data":{"fromUserId":"A","candidate":...}}
    B->>S: {"type":"ice_candidate","data":{"toUserId":"A","candidate":...}}
    S->>A: {"type":"ice_candidate","data":{"fromUserId":"B","candidate":...}}

📊 协议性能优化

1. 消息压缩

  • JSON格式轻量级
  • 只传输必要信息
  • 批量用户列表更新

2. 连接管理

javascript 复制代码
// 服务器端消息验证
handleMessage(ws, message) {
  const { type, data } = message;
  
  // 验证消息格式
  if (!type || !data) {
    this.sendError(ws, '消息格式错误');
    return;
  }
  
  // 验证用户状态
  const user = this.userManager.getUserBySocket(ws);
  if (!user && type !== 'login') {
    this.sendError(ws, '请先登录');
    return;
  }
}

3. 状态同步

javascript 复制代码
// 用户状态跟踪
const user = {
  id: userId,
  name: userName,
  ws: ws,
  online: true,
  inCall: false  // 通话状态控制
};

🎯 协议特点总结

✅ 优势

  1. 简单高效 - JSON消息格式易解析
  2. 实时性强 - WebSocket双向通信
  3. 扩展性好 - 模块化消息类型
  4. 容错性强 - 自动重连和错误处理
  5. 状态完整 - 完整的用户状态管理
  6. 信息丰富 - 包含用户姓名和状态

🔧 技术特点

  1. 分层设计 - 传输层、应用层、媒体层分离
  2. 状态管理 - 服务器维护用户状态
  3. 信令转发 - 服务器作为信令中转站
  4. P2P传输 - 最终建立点对点连接
  5. 时机控制 - 只有在双方同意后才建立连接
  6. 用户信息 - 完整的用户信息传递

🔄 协议消息类型总结

消息类型 方向 用途 关键字段
login C→S 用户登录 userName
login_success S→C 登录成功 userId, onlineUsers
user_list S→C 用户列表更新 users[]
call_request C→S 发起通话 toUserId
call_response C→S 通话响应 toUserId, accepted, fromUserName
offer C→S WebRTC Offer toUserId, offer
answer C→S WebRTC Answer toUserId, answer
ice_candidate C→S ICE候选 toUserId, candidate
hangup C→S 挂断通话 toUserId
error S→C 错误消息 message

这个协议设计实现了信令服务器 + WebRTC的经典架构,既保证了实时性,又确保了系统的可扩展性和稳定性!

相关推荐
Yupureki13 小时前
从零开始的C++学习生活 20:数据结构与STL复习课(4.4w字全解析)
c语言·数据结构·c++·学习·visual studio·1024程序员节
Predestination王瀞潞16 小时前
Java EE开发技术(第五章:JSP技术)
1024程序员节
AORO202517 小时前
三防平板三防是指哪三防?适合应用在什么场景?
服务器·网络·智能手机·电脑·1024程序员节
极客数模17 小时前
【浅析赛题,一等奖水平】思路模型数据相关资料!2025 年“大湾区杯”粤港澳金融数学建模竞赛B 题 稳定币的综合评价与发展分析~
大数据·算法·数学建模·金融·数据挖掘·图论·1024程序员节
再睡一夏就好19 小时前
【C++闯关笔记】使用红黑树简单模拟实现map与set
java·c语言·数据结构·c++·笔记·语法·1024程序员节
TDengine (老段)19 小时前
益和热力性能优化实践:从 SQL Server 到 TDengine 时序数据库,写入快 20 秒、查询提速 5 倍
大数据·数据库·物联网·性能优化·时序数据库·tdengine·1024程序员节
B站计算机毕业设计之家21 小时前
python图像识别系统 AI多功能图像识别检测系统(11种识别功能)银行卡、植物、动物、通用票据、营业执照、身份证、车牌号、驾驶证、行驶证、车型、Logo✅
大数据·开发语言·人工智能·python·图像识别·1024程序员节·识别
艾莉丝努力练剑21 小时前
【Linux基础开发工具 (一)】详解Linux软件生态与包管理器:从yum / apt原理到镜像源实战
linux·运维·服务器·ubuntu·centos·1024程序员节
杰克尼1 天前
单词11/1
1024程序员节