使用 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

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

相关推荐
Eiceblue3 小时前
如何通过 C# 高效读写 Excel 工作表
c#·visual studio·1024程序员节
海林OneMoreTime3 小时前
Spring Boot 配置优先级
1024程序员节
张人玉3 小时前
WPF 触发器详解:定义、种类与示例
c#·wpf·1024程序员节·布局控件
Slow菜鸟3 小时前
NVM 安装 (Windows版本)
nvm·1024程序员节
凉虾皮3 小时前
2024包河初中组
学习·算法·1024程序员节
jdlxx_dongfangxing3 小时前
C++ STL 容器与算法详解
开发语言·c++·1024程序员节
PandaCave3 小时前
记录画图笔记
1024程序员节
前端与小赵3 小时前
我的创作纪念日
1024程序员节
最好结果3 小时前
MyBatis 精确查询逗号分隔字符串
mysql·mybatis·1024程序员节