使用 WebSocket 实现手机控制端和电脑展示端的实时通信,包含断线重连功能。

主要分为三个部分:WebSocket 服务器、电脑展示端页面和手机控制端页面。

1. WebSocket 服务器(Node.js)

首先需要搭建一个 WebSocket 服务器作为中间层,转发控制指令:

javascript 复制代码
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

// 存储连接的客户端,区分控制端和展示端
let controller = null;  // 手机控制端
let display = null;    // 电脑展示端

console.log('WebSocket服务器启动,端口: 8080');

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

  // 接收客户端消息
  ws.on('message', (message) => {
    try {
      const data = JSON.parse(message.toString());
      
      // 客户端类型注册
      if (data.type === 'register') {
        if (data.role === 'controller') {
          controller = ws;
          console.log('手机控制端已连接');
          // 通知控制端是否已有展示端连接
          ws.send(JSON.stringify({
            type: 'status',
            hasDisplay: !!display
          }));
        } else if (data.role === 'display') {
          display = ws;
          console.log('电脑展示端已连接');
          // 通知展示端是否已有控制端连接
          ws.send(JSON.stringify({
            type: 'status',
            hasController: !!controller
          }));
        }
      } 
      // 转发控制指令
      else if (data.type === 'control' && controller === ws && display) {
        console.log('转发控制指令:', data);
        display.send(JSON.stringify(data));
      }
    } catch (e) {
      console.error('消息处理错误:', e);
    }
  });

  // 客户端断开连接
  ws.on('close', () => {
    if (ws === controller) {
      controller = null;
      console.log('手机控制端已断开');
      // 通知展示端控制端已断开
      if (display) {
        display.send(JSON.stringify({
          type: 'status',
          hasController: false
        }));
      }
    } else if (ws === display) {
      display = null;
      console.log('电脑展示端已断开');
      // 通知控制端展示端已断开
      if (controller) {
        controller.send(JSON.stringify({
          type: 'status',
          hasDisplay: false
        }));
      }
    }
  });

  // 错误处理
  ws.on('error', (error) => {
    console.error('WebSocket错误:', error);
  });
});
2. 电脑展示端页面(display.html)
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;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      margin: 0;
      transition: background-color 0.5s;
    }
    #status {
      position: fixed;
      top: 20px;
      padding: 10px 20px;
      border-radius: 5px;
      background-color: #f0f0f0;
    }
    .connected {
      background-color: #4CAF50;
      color: white;
    }
    .disconnected {
      background-color: #f44336;
      color: white;
    }
    #displayArea {
      width: 80%;
      height: 60%;
      border: 2px solid #333;
      border-radius: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 24px;
      transition: all 0.3s;
    }
  </style>
</head>
<body>
  <div id="status" class="disconnected">未连接到控制端</div>
  <div id="displayArea">等待控制指令...</div>

  <script>
    let ws;
    let reconnectInterval;
    const reconnectDelay = 3000; // 重连间隔3秒

    // 连接WebSocket服务器
    function connect() {
      // 关闭可能存在的旧连接
      if (ws) {
        ws.close();
      }

      // 获取当前页面的主机地址,确保使用相同的IP
      const host = window.location.hostname;
      ws = new WebSocket(`ws://${host}:8080`);

      // 连接成功
      ws.onopen = () => {
        console.log('WebSocket连接成功');
        // 注册为展示端
        ws.send(JSON.stringify({
          type: 'register',
          role: 'display'
        }));
        // 清除重连计时器
        clearInterval(reconnectInterval);
        updateStatus(true);
      };

      // 接收消息
      ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log('收到消息:', data);
        
        // 处理状态更新
        if (data.type === 'status') {
          updateStatus(data.hasController);
        }
        // 处理控制指令
        else if (data.type === 'control') {
          handleControl(data.command, data.value);
        }
      };

      // 连接关闭
      ws.onclose = () => {
        console.log('WebSocket连接关闭');
        updateStatus(false);
        // 启动重连机制
        startReconnect();
      };

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

    // 启动重连机制
    function startReconnect() {
      console.log(`将在${reconnectDelay/1000}秒后尝试重连...`);
      reconnectInterval = setInterval(connect, reconnectDelay);
    }

    // 更新连接状态显示
    function updateStatus(connected) {
      const statusEl = document.getElementById('status');
      if (connected) {
        statusEl.textContent = '已连接到控制端';
        statusEl.className = 'connected';
      } else {
        statusEl.textContent = '未连接到控制端,正在尝试重连...';
        statusEl.className = 'disconnected';
      }
    }

    // 处理控制指令
    function handleControl(command, value) {
      const displayArea = document.getElementById('displayArea');
      
      switch(command) {
        case 'changeColor':
          document.body.style.backgroundColor = value;
          displayArea.textContent = `背景色已更改为: ${value}`;
          break;
        case 'changeText':
          displayArea.textContent = value;
          break;
        case 'scale':
          displayArea.style.transform = `scale(${value})`;
          displayArea.textContent = `缩放比例: ${value}`;
          break;
        case 'rotate':
          displayArea.style.transform = `rotate(${value}deg)`;
          displayArea.textContent = `旋转角度: ${value}°`;
          break;
        default:
          console.log('未知指令:', command);
      }
    }

    // 初始化连接
    connect();
  </script>
</body>
</html>
3. 手机控制端页面(controller.html)
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>手机控制端</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      padding: 20px;
      margin: 0;
      background-color: #f5f5f5;
    }
    #status {
      padding: 10px;
      border-radius: 5px;
      text-align: center;
      margin-bottom: 20px;
    }
    .connected {
      background-color: #4CAF50;
      color: white;
    }
    .disconnected {
      background-color: #f44336;
      color: white;
    }
    .control-group {
      background-color: white;
      padding: 15px;
      border-radius: 10px;
      margin-bottom: 15px;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    }
    h3 {
      margin-top: 0;
      color: #333;
    }
    button {
      width: 100%;
      padding: 12px;
      margin-bottom: 10px;
      border: none;
      border-radius: 5px;
      background-color: #2196F3;
      color: white;
      font-size: 16px;
      cursor: pointer;
    }
    button:hover {
      background-color: #0b7dda;
    }
    input[type="color"], input[type="text"] {
      width: 100%;
      padding: 10px;
      margin-bottom: 10px;
      border: 1px solid #ddd;
      border-radius: 5px;
      box-sizing: border-box;
    }
    input[type="range"] {
      width: 100%;
      margin: 15px 0;
    }
    .range-value {
      text-align: center;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <div id="status" class="disconnected">未连接到展示端</div>

  <div class="control-group">
    <h3>背景颜色控制</h3>
    <input type="color" id="bgColor" value="#ffffff">
    <button onclick="sendCommand('changeColor', document.getElementById('bgColor').value)">
      更改背景色
    </button>
  </div>

  <div class="control-group">
    <h3>文本控制</h3>
    <input type="text" id="displayText" placeholder="输入要显示的文本">
    <button onclick="sendCommand('changeText', document.getElementById('displayText').value)">
      更新显示文本
    </button>
  </div>

  <div class="control-group">
    <h3>缩放控制</h3>
    <input type="range" id="scale" min="0.5" max="2" step="0.1" value="1">
    <div class="range-value" id="scaleValue">1.0</div>
    <button onclick="sendCommand('scale', document.getElementById('scale').value)">
      应用缩放
    </button>
  </div>

  <div class="control-group">
    <h3>旋转控制</h3>
    <input type="range" id="rotate" min="0" max="360" step="15" value="0">
    <div class="range-value" id="rotateValue">0°</div>
    <button onclick="sendCommand('rotate', document.getElementById('rotate').value)">
      应用旋转
    </button>
  </div>

  <script>
    let ws;
    let reconnectInterval;
    const reconnectDelay = 3000; // 重连间隔3秒
    let isConnected = false;

    // 更新滑块显示值
    document.getElementById('scale').addEventListener('input', function() {
      document.getElementById('scaleValue').textContent = this.value;
    });
    document.getElementById('rotate').addEventListener('input', function() {
      document.getElementById('rotateValue').textContent = this.value + '°';
    });

    // 连接WebSocket服务器
    function connect() {
      // 关闭可能存在的旧连接
      if (ws) {
        ws.close();
      }

      // 获取当前页面的主机地址,确保使用相同的IP
      const host = window.location.hostname;
      ws = new WebSocket(`ws://${host}:8080`);

      // 连接成功
      ws.onopen = () => {
        console.log('WebSocket连接成功');
        // 注册为控制端
        ws.send(JSON.stringify({
          type: 'register',
          role: 'controller'
        }));
        // 清除重连计时器
        clearInterval(reconnectInterval);
      };

      // 接收消息
      ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log('收到消息:', data);
        
        // 处理状态更新
        if (data.type === 'status') {
          isConnected = data.hasDisplay;
          updateStatus(isConnected);
        }
      };

      // 连接关闭
      ws.onclose = () => {
        console.log('WebSocket连接关闭');
        isConnected = false;
        updateStatus(false);
        // 启动重连机制
        startReconnect();
      };

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

    // 启动重连机制
    function startReconnect() {
      console.log(`将在${reconnectDelay/1000}秒后尝试重连...`);
      reconnectInterval = setInterval(connect, reconnectDelay);
    }

    // 更新连接状态显示
    function updateStatus(connected) {
      const statusEl = document.getElementById('status');
      if (connected) {
        statusEl.textContent = '已连接到展示端';
        statusEl.className = 'connected';
      } else {
        statusEl.textContent = '未连接到展示端,正在尝试重连...';
        statusEl.className = 'disconnected';
      }
    }

    // 发送控制指令
    function sendCommand(command, value) {
      if (!isConnected || !ws) {
        alert('未连接到展示端,请稍后重试');
        return;
      }

      try {
        ws.send(JSON.stringify({
          type: 'control',
          command: command,
          value: value
        }));
        console.log(`发送指令: ${command} = ${value}`);
      } catch (e) {
        console.error('发送指令失败:', e);
        alert('发送指令失败,请重试');
      }
    }

    // 初始化连接
    connect();
  </script>
</body>
</html>

使用说明

  1. 环境准备

    • 安装 Node.js
    • 安装 WebSocket 模块:npm install ws
  2. 启动服务器

    • 运行命令:node server.js
    • 服务器将在 8080 端口启动
  3. 部署页面

    • 将 display.html 和 controller.html 放在 Web 服务器(如 Nginx、Apache)或使用简单的 HTTP 服务器
    • 例如:使用 Node.js 的 http-server:npx http-server
  4. 使用方法

    • 确保手机和电脑连接到同一网络
    • 在电脑上打开展示端页面:http://[服务器IP]:8080/display.html
    • 在手机上打开控制端页面:http://[服务器IP]:8080/controller.html
    • 现在可以通过手机控制电脑页面的各种属性了

断线重连机制说明

  • 当连接断开时,客户端会自动尝试重连
  • 重连间隔为 3 秒,可通过修改reconnectDelay变量调整
  • 重连过程中会显示状态提示
  • 重连成功后会自动恢复通信,无需手动操作

这个实现提供了基本的控制功能(颜色、文本、缩放、旋转),你可以根据需要扩展更多控制指令和 UI 元素。

相关推荐
金融小师妹9 小时前
基于多源政策信号解析与量化因子的“12月降息预期降温”重构及黄金敏感性分析
人工智能·深度学习·1024程序员节
GIS数据转换器14 小时前
基于GIS的智慧旅游调度指挥平台
运维·人工智能·物联网·无人机·旅游·1024程序员节
南方的狮子先生1 天前
【C++】C++文件读写
java·开发语言·数据结构·c++·算法·1024程序员节
Neil今天也要学习1 天前
永磁同步电机无速度算法--基于三阶LESO的反电动势观测器
算法·1024程序员节
开开心心_Every2 天前
专业视频修复软件,简单操作效果好
学习·elasticsearch·pdf·excel·音视频·memcache·1024程序员节
liu****2 天前
16.udp_socket(三)
linux·开发语言·数据结构·c++·1024程序员节
草莓熊Lotso3 天前
《算法闯关指南:优选算法--位运算》--38.消失的两个数字
服务器·c++·算法·1024程序员节
unable code3 天前
攻防世界-Misc-can_has_stdio?
网络安全·ctf·misc·1024程序员节
思茂信息3 天前
CST License(Flexnet)设置与问题处理方法
服务器·网络·单片机·3d·php·1024程序员节·cst
2301_797892834 天前
论文阅读:《Hypergraph Motif Representation Learning》
论文阅读·1024程序员节