系统采用前端技术栈,通过 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 实现了手机控制端和电脑展示端的实时通信,主要特点包括:
- 实时通信:使用 WebSocket 实现全双工通信,支持即时命令发送和状态反馈
- 断线重连:WebSocket 连接断开后会自动尝试重新连接
- 状态同步:两端连接状态实时更新,用户可以直观了解当前连接情况
- 完整的增删改查:支持数据的添加、删除、查询等操作
- 双向数据交互:控制端发送命令,展示端处理后返回结果
- 事件日志:展示端记录所有接收的命令和系统事件
使用方法
- 安装依赖:
npm install ws express - 启动服务器:
node server.js - 在手机浏览器中访问:
http://服务器IP:8080/controller.html - 在电脑浏览器中访问:
http://服务器IP:8080/display.html
系统启动后,两端会自动尝试连接,连接成功后即可通过手机端控制电脑端的展示内容。