使用 WebSocket 实现手机控制端和电脑展示端的实时通信,包含断线重连功能。
主要分为三个部分:WebSocket 服务器、电脑展示端页面和手机控制端页面。
1. WebSocket 服务器(Node.js)
首先需要搭建一个 WebSocket 服务器作为中间层,转发控制指令:
// 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)
<!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();}// 获取当前页面的主机地址,确保使用相同的IPconst 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)
<!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();}// 获取当前页面的主机地址,确保使用相同的IPconst 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>使用说明
环境准备:
- 安装 Node.js
- 安装 WebSocket 模块:
npm install ws
启动服务器:
- 运行命令:
node server.js - 服务器将在 8080 端口启动
- 运行命令:
部署页面:
- 将 display.html 和 controller.html 放在 Web 服务器(如 Nginx、Apache)或使用简单的 HTTP 服务器
- 例如:使用 Node.js 的 http-server:
npx http-server
使用方法:
- 确保手机和电脑连接到同一网络
- 在电脑上打开展示端页面:
http://[服务器IP]:8080/display.html - 在手机上打开控制端页面:
http://[服务器IP]:8080/controller.html - 现在可以通过手机控制电脑页面的各种属性了
断线重连机制说明
- 当连接断开时,客户端会自动尝试重连
- 重连间隔为 3 秒,可通过修改
reconnectDelay变量调整 - 重连过程中会显示状态提示
- 重连成功后会自动恢复通信,无需手动操作
这个实现提供了基本的控制功能(颜色、文本、缩放、旋转),你可以根据需要扩展更多控制指令和 UI 元素。
