使用 WebSocket 实现手机控制端与电脑展示端的实时通信,支持断线重连、状态同步和双向数据交互。(最优方案)

系统采用前端技术栈,通过 Node.js 搭建 WebSocket 服务端,控制端 (手机) 和展示端 (电脑) 均为网页应用。

1. 服务端实现 (WebSocket 服务器)

首先创建 WebSocket 服务,用于转发控制端和展示端之间的消息:

javascript 复制代码
// server.js
const WebSocket = require('ws');
const http = require('http');
const express = require('express');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

// 存储连接的客户端:控制端和展示端
let controller = null;
let display = null;

// 静态文件服务
app.use(express.static('public'));

// WebSocket连接处理
wss.on('connection', (ws) => {
  console.log('新客户端连接');

  // 客户端身份验证
  ws.on('message', (data) => {
    try {
      const message = JSON.parse(data);
      
      // 身份注册
      if (message.type === 'register') {
        if (message.role === 'controller') {
          controller = ws;
          console.log('控制端已连接');
          sendStatusUpdate(); // 更新连接状态
        } else if (message.role === 'display') {
          display = ws;
          console.log('展示端已连接');
          sendStatusUpdate(); // 更新连接状态
        }
      } 
      // 转发控制命令
      else if (message.type === 'control' && ws === controller && display) {
        console.log('转发控制命令:', message);
        display.send(JSON.stringify({
          type: 'control',
          action: message.action,
          data: message.data,
          timestamp: new Date().getTime()
        }));
      } 
      // 转发状态更新
      else if (message.type === 'status' && ws === display && controller) {
        console.log('转发状态更新:', message);
        controller.send(JSON.stringify({
          type: 'status',
          status: message.status,
          data: message.data,
          timestamp: new Date().getTime()
        }));
      }
    } catch (error) {
      console.error('消息处理错误:', error);
    }
  });

  // 客户端断开连接处理
  ws.on('close', () => {
    if (ws === controller) {
      console.log('控制端已断开');
      controller = null;
    } else if (ws === display) {
      console.log('展示端已断开');
      display = null;
    }
    sendStatusUpdate(); // 更新连接状态
  });

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

// 发送连接状态更新给所有客户端
function sendStatusUpdate() {
  const status = {
    controllerConnected: !!controller,
    displayConnected: !!display,
    bothConnected: !!controller && !!display,
    timestamp: new Date().getTime()
  };
  
  if (controller) {
    controller.send(JSON.stringify({ type: 'connectionStatus', ...status }));
  }
  if (display) {
    display.send(JSON.stringify({ type: 'connectionStatus', ...status }));
  }
}

// 启动服务器
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
  console.log(`服务器运行在 http://localhost:${PORT}`);
  console.log(`控制端: http://localhost:${PORT}/controller.html`);
  console.log(`展示端: http://localhost:${PORT}/display.html`);
});
2. 公共工具类 (WebSocket 连接管理)

创建一个公共的 WebSocket 管理工具,处理连接、重连和消息发送:

javascript 复制代码
// public/js/websocket-manager.js
class WebSocketManager {
  constructor(role, reconnectInterval = 3000) {
    this.role = role; // 'controller' 或 'display'
    this.reconnectInterval = reconnectInterval;
    this.ws = null;
    this.connected = false;
    this.callbacks = {
      connectionStatus: [],
      control: [],
      status: []
    };
    this.connect();
  }

  // 建立连接
  connect() {
    // 关闭现有连接
    if (this.ws) {
      this.ws.close();
    }

    // 连接WebSocket服务器
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const url = `${protocol}//${window.location.host}`;
    this.ws = new WebSocket(url);

    // 连接成功
    this.ws.onopen = () => {
      console.log('WebSocket连接成功');
      this.connected = true;
      // 注册客户端角色
      this.send({ type: 'register', role: this.role });
    };

    // 接收消息
    this.ws.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);
        this.handleMessage(message);
      } catch (error) {
        console.error('解析消息错误:', error);
      }
    };

    // 连接关闭
    this.ws.onclose = () => {
      console.log('WebSocket连接关闭,尝试重连...');
      this.connected = false;
      // 自动重连
      setTimeout(() => this.connect(), this.reconnectInterval);
    };

    // 连接错误
    this.ws.onerror = (error) => {
      console.error('WebSocket错误:', error);
    };
  }

  // 处理接收到的消息
  handleMessage(message) {
    switch (message.type) {
      case 'connectionStatus':
        this.callbacks.connectionStatus.forEach(callback => callback(message));
        break;
      case 'control':
        this.callbacks.control.forEach(callback => callback(message));
        break;
      case 'status':
        this.callbacks.status.forEach(callback => callback(message));
        break;
    }
  }

  // 发送消息
  send(message) {
    if (this.connected && this.ws) {
      try {
        this.ws.send(JSON.stringify(message));
        return true;
      } catch (error) {
        console.error('发送消息失败:', error);
        return false;
      }
    }
    console.warn('无法发送消息,连接未建立');
    return false;
  }

  // 注册事件回调
  on(eventType, callback) {
    if (this.callbacks[eventType]) {
      this.callbacks[eventType].push(callback);
    } else {
      console.warn(`未知的事件类型: ${eventType}`);
    }
  }

  // 关闭连接
  close() {
    if (this.ws) {
      this.ws.close();
      this.ws = null;
      this.connected = false;
    }
  }
}
3. 控制端实现 (手机端)

创建控制端界面,用于发送控制命令到展示端:

html 复制代码
<!-- public/controller.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>控制端</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
      max-width: 500px;
      margin: 0 auto;
      padding: 20px;
    }
    .status {
      padding: 10px;
      margin-bottom: 20px;
      border-radius: 5px;
      text-align: center;
    }
    .disconnected {
      background-color: #ffcccc;
      color: #cc0000;
    }
    .connected {
      background-color: #ccffcc;
      color: #006600;
    }
    .controls {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
      margin-bottom: 20px;
    }
    button {
      padding: 15px;
      font-size: 16px;
      cursor: pointer;
      border: none;
      border-radius: 5px;
      background-color: #4CAF50;
      color: white;
    }
    button:hover {
      background-color: #45a049;
    }
    .data-section {
      margin-top: 20px;
    }
    .item {
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 5px;
      margin-bottom: 10px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    input, textarea {
      width: 100%;
      padding: 10px;
      margin-bottom: 10px;
      border: 1px solid #ddd;
      border-radius: 5px;
    }
    .response {
      margin-top: 10px;
      padding: 10px;
      background-color: #f0f0f0;
      border-radius: 5px;
    }
  </style>
</head>
<body>
  <h1>设备控制端</h1>
  
  <!-- 连接状态 -->
  <div class="status disconnected" id="connectionStatus">
    未连接
  </div>

  <!-- 控制按钮 -->
  <div class="controls">
    <button id="btnStart">启动</button>
    <button id="btnStop">停止</button>
    <button id="btnReset">重置</button>
    <button id="btnUpdate">更新配置</button>
  </div>

  <!-- 数据管理 -->
  <div class="data-section">
    <h3>数据管理</h3>
    <input type="text" id="dataInput" placeholder="输入数据...">
    <button id="btnAdd">添加数据</button>
    <button id="btnQuery">查询数据</button>
    
    <div id="dataList">
      <!-- 数据项将在这里动态生成 -->
    </div>
  </div>

  <!-- 响应区域 -->
  <div class="response-section">
    <h3>展示端响应</h3>
    <div id="response" class="response">
      等待响应...
    </div>
  </div>

  <script src="js/websocket-manager.js"></script>
  <script>
    // 初始化WebSocket管理器,角色为控制端
    const wsManager = new WebSocketManager('controller');
    
    // DOM元素
    const connectionStatusEl = document.getElementById('connectionStatus');
    const responseEl = document.getElementById('response');
    const dataListEl = document.getElementById('dataList');
    const dataInputEl = document.getElementById('dataInput');
    
    // 监听连接状态更新
    wsManager.on('connectionStatus', (status) => {
      console.log('连接状态更新:', status);
      if (status.bothConnected) {
        connectionStatusEl.textContent = '已连接到展示端';
        connectionStatusEl.className = 'status connected';
      } else if (status.controllerConnected) {
        connectionStatusEl.textContent = '等待展示端连接...';
        connectionStatusEl.className = 'status disconnected';
      } else {
        connectionStatusEl.textContent = '未连接';
        connectionStatusEl.className = 'status disconnected';
      }
    });
    
    // 监听展示端返回的状态
    wsManager.on('status', (message) => {
      console.log('收到展示端状态:', message);
      responseEl.textContent = `[${new Date(message.timestamp).toLocaleTimeString()}] ${JSON.stringify(message.data)}`;
      
      // 如果是数据列表更新,刷新本地列表
      if (message.action === 'query' || message.action === 'add' || message.action === 'delete') {
        updateDataList(message.data);
      }
    });
    
    // 更新数据列表
    function updateDataList(items) {
      dataListEl.innerHTML = '';
      if (!items || items.length === 0) {
        dataListEl.innerHTML = '<div class="item">无数据</div>';
        return;
      }
      
      items.forEach((item, index) => {
        const itemEl = document.createElement('div');
        itemEl.className = 'item';
        itemEl.innerHTML = `
          <span>${item}</span>
          <button class="delete-btn" data-index="${index}">删除</button>
        `;
        dataListEl.appendChild(itemEl);
      });
      
      // 绑定删除按钮事件
      document.querySelectorAll('.delete-btn').forEach(btn => {
        btn.addEventListener('click', (e) => {
          const index = parseInt(e.target.dataset.index);
          sendControlCommand('delete', { index });
        });
      });
    }
    
    // 发送控制命令
    function sendControlCommand(action, data = {}) {
      if (wsManager.connected) {
        wsManager.send({
          type: 'control',
          action,
          data
        });
        responseEl.textContent = `已发送 ${action} 命令,等待响应...`;
      } else {
        responseEl.textContent = '未连接到服务器,无法发送命令';
      }
    }
    
    // 绑定按钮事件
    document.getElementById('btnStart').addEventListener('click', () => {
      sendControlCommand('start');
    });
    
    document.getElementById('btnStop').addEventListener('click', () => {
      sendControlCommand('stop');
    });
    
    document.getElementById('btnReset').addEventListener('click', () => {
      sendControlCommand('reset');
    });
    
    document.getElementById('btnUpdate').addEventListener('click', () => {
      sendControlCommand('update', { config: '新配置参数' });
    });
    
    document.getElementById('btnAdd').addEventListener('click', () => {
      const value = dataInputEl.value.trim();
      if (value) {
        sendControlCommand('add', { value });
        dataInputEl.value = '';
      }
    });
    
    document.getElementById('btnQuery').addEventListener('click', () => {
      sendControlCommand('query');
    });
  </script>
</body>
</html>
4. 展示端实现 (电脑端)

创建展示端界面,接收控制命令并返回处理结果:

html 复制代码
<!-- public/display.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>展示端</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
    }
    .status {
      padding: 15px;
      margin-bottom: 20px;
      border-radius: 5px;
      font-size: 18px;
    }
    .disconnected {
      background-color: #ffcccc;
      color: #cc0000;
    }
    .connected {
      background-color: #ccffcc;
      color: #006600;
    }
    .control-panel {
      border: 1px solid #ddd;
      padding: 20px;
      border-radius: 5px;
      margin-bottom: 20px;
    }
    .data-display {
      border: 1px solid #ddd;
      padding: 20px;
      border-radius: 5px;
    }
    .event-log {
      margin-top: 20px;
      padding: 10px;
      background-color: #f9f9f9;
      border-radius: 5px;
      height: 200px;
      overflow-y: auto;
    }
    .log-entry {
      margin-bottom: 5px;
      padding: 5px;
      border-bottom: 1px solid #eee;
    }
    .item {
      padding: 10px;
      border: 1px solid #eee;
      border-radius: 5px;
      margin-bottom: 10px;
    }
  </style>
</head>
<body>
  <h1>展示端</h1>
  
  <!-- 连接状态 -->
  <div class="status disconnected" id="connectionStatus">
    未连接到控制端
  </div>
  
  <!-- 当前状态 -->
  <div class="control-panel">
    <h2>当前状态: <span id="currentState">未启动</span></h2>
    <div id="actionResult">等待控制命令...</div>
  </div>
  
  <!-- 数据展示 -->
  <div class="data-display">
    <h3>数据列表</h3>
    <div id="dataList">
      <!-- 数据项将在这里动态生成 -->
    </div>
  </div>
  
  <!-- 事件日志 -->
  <div class="event-log">
    <h3>事件日志</h3>
    <div id="eventLog"></div>
  </div>

  <script src="js/websocket-manager.js"></script>
  <script>
    // 初始化WebSocket管理器,角色为展示端
    const wsManager = new WebSocketManager('display');
    
    // 模拟数据存储
    let dataStore = [];
    let currentState = '未启动';
    
    // DOM元素
    const connectionStatusEl = document.getElementById('connectionStatus');
    const currentStateEl = document.getElementById('currentState');
    const actionResultEl = document.getElementById('actionResult');
    const dataListEl = document.getElementById('dataList');
    const eventLogEl = document.getElementById('eventLog');
    
    // 监听连接状态更新
    wsManager.on('connectionStatus', (status) => {
      console.log('连接状态更新:', status);
      if (status.bothConnected) {
        connectionStatusEl.textContent = '已连接到控制端';
        connectionStatusEl.className = 'status connected';
        logEvent('已连接到控制端');
      } else if (status.displayConnected) {
        connectionStatusEl.textContent = '等待控制端连接...';
        connectionStatusEl.className = 'status disconnected';
        logEvent('等待控制端连接...');
      } else {
        connectionStatusEl.textContent = '未连接';
        connectionStatusEl.className = 'status disconnected';
        logEvent('连接已断开');
      }
    });
    
    // 监听控制命令
    wsManager.on('control', (message) => {
      console.log('收到控制命令:', message);
      logEvent(`收到命令: ${message.action}`);
      handleControlCommand(message.action, message.data);
    });
    
    // 处理控制命令
    function handleControlCommand(action, data) {
      let result = {};
      
      switch (action) {
        case 'start':
          currentState = '运行中';
          result = { status: 'success', message: '系统已启动' };
          break;
          
        case 'stop':
          currentState = '已停止';
          result = { status: 'success', message: '系统已停止' };
          break;
          
        case 'reset':
          currentState = '未启动';
          dataStore = [];
          updateDataList();
          result = { status: 'success', message: '系统已重置', data: dataStore };
          break;
          
        case 'update':
          result = { status: 'success', message: '配置已更新', data: data };
          break;
          
        case 'add':
          if (data && data.value) {
            dataStore.push(data.value);
            updateDataList();
            result = { status: 'success', message: '数据已添加', data: dataStore };
          } else {
            result = { status: 'error', message: '添加失败,数据为空' };
          }
          break;
          
        case 'delete':
          if (data && typeof data.index === 'number' && data.index >= 0 && data.index < dataStore.length) {
            const deleted = dataStore.splice(data.index, 1);
            updateDataList();
            result = { status: 'success', message: `已删除: ${deleted[0]}`, data: dataStore };
          } else {
            result = { status: 'error', message: '删除失败,索引无效' };
          }
          break;
          
        case 'query':
          result = { status: 'success', message: '数据查询成功', data: dataStore };
          break;
          
        default:
          result = { status: 'error', message: `未知命令: ${action}` };
      }
      
      // 更新UI
      currentStateEl.textContent = currentState;
      actionResultEl.textContent = `命令 ${action} 已处理: ${result.message}`;
      
      // 向控制端返回处理结果
      wsManager.send({
        type: 'status',
        action,
        status: result.status,
        data: result
      });
    }
    
    // 更新数据列表显示
    function updateDataList() {
      dataListEl.innerHTML = '';
      if (dataStore.length === 0) {
        dataListEl.innerHTML = '<div class="item">无数据</div>';
        return;
      }
      
      dataStore.forEach((item, index) => {
        const itemEl = document.createElement('div');
        itemEl.className = 'item';
        itemEl.textContent = `${index + 1}. ${item}`;
        dataListEl.appendChild(itemEl);
      });
    }
    
    // 添加日志条目
    function logEvent(message) {
      const logEl = document.createElement('div');
      logEl.className = 'log-entry';
      logEl.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
      eventLogEl.prepend(logEl); // 添加到日志顶部
      
      // 限制日志数量
      if (eventLogEl.children.length > 50) {
        eventLogEl.removeChild(eventLogEl.lastChild);
      }
    }
    
    // 初始化数据列表
    updateDataList();
  </script>
</body>
</html>

实现说明

这个系统通过 WebSocket 实现了手机控制端和电脑展示端的实时通信,主要特点包括:

  1. 实时通信:使用 WebSocket 实现全双工通信,支持即时命令发送和状态反馈
  2. 断线重连:WebSocket 连接断开后会自动尝试重新连接
  3. 状态同步:两端连接状态实时更新,用户可以直观了解当前连接情况
  4. 完整的增删改查:支持数据的添加、删除、查询等操作
  5. 双向数据交互:控制端发送命令,展示端处理后返回结果
  6. 事件日志:展示端记录所有接收的命令和系统事件

使用方法

  1. 安装依赖:npm install ws express
  2. 启动服务器:node server.js
  3. 在手机浏览器中访问:http://服务器IP:8080/controller.html
  4. 在电脑浏览器中访问:http://服务器IP:8080/display.html

系统启动后,两端会自动尝试连接,连接成功后即可通过手机端控制电脑端的展示内容。

相关推荐
学传打活2 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续1)
微信公众平台·1024程序员节·汉字·昆仑正义文化
fangzhanpeng16810 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心12 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u01030552718 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u01030552719 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝20 天前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u01030552720 天前
Java图像处理实战指南
人工智能·1024程序员节
u01030552721 天前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u01030552723 天前
Java实用类应用精讲
人工智能·1024程序员节
u01030552723 天前
Java I/O核心操作实战
人工智能·1024程序员节