包含断线重连逻辑,采用「控制端 - 服务器 - 展示端」架构,通过 Vue 的响应式特性管理连接状态和指令交互。
技术栈
- 服务器:Node.js +
ws库(WebSocket 服务) - 控制端 / 展示端:Vue3(Composition API) + 原生 WebSocket API
- 构建工具:Vite(快速开发体验)
实现步骤
1. 服务器端(转发与连接管理)
与前方案核心逻辑一致,负责维护连接、转发指令和心跳检测(复用 server.js):
javascript
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = { controller: null, display: null }; // 存储两端连接
const HEARTBEAT_INTERVAL = 30000; // 30秒心跳检测
wss.on('connection', (ws) => {
let clientType = null;
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === 'register') {
clientType = msg.role;
clients[clientType] = ws;
ws.send(JSON.stringify({ type: 'connected', msg: `已作为${clientType}连接` }));
startHeartbeat(ws);
} else if (clientType === 'controller' && msg.type === 'control') {
// 转发控制指令到展示端
if (clients.display?.readyState === WebSocket.OPEN) {
clients.display.send(JSON.stringify({ type: 'control', data: msg.data }));
} else {
ws.send(JSON.stringify({ type: 'error', msg: '展示端未连接' }));
}
}
});
ws.on('close', () => {
if (clientType) clients[clientType] = null;
});
function startHeartbeat(ws) {
const timer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'heartbeat' }));
} else {
clearInterval(timer);
}
}, HEARTBEAT_INTERVAL);
}
});
console.log('WebSocket服务器运行于 ws://localhost:8080');
2. 控制端(平板 Vue 应用)
使用 Vue3 的 ref 和 onMounted 管理连接状态,通过按钮发送控制指令,包含断线自动重连逻辑。
(1)创建控制端项目
bash
npm create vite@latest controller -- --template vue
cd controller
npm install
(2)核心组件 src/App.vue
javascript
<template>
<div class="controller">
<h1>平板控制端</h1>
<p class="status" :class="{ connected: isConnected }">
状态:{{ statusText }}
</p>
<div class="controls">
<button @click="sendControl('showText', 'Hello 大屏!')">
显示文本
</button>
<button @click="sendControl('changeColor', randomColor)">
切换背景色
</button>
<button @click="sendControl('clear', null)">
清空内容
</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
// 状态管理
const ws = ref(null);
const isConnected = ref(false);
const statusText = ref('未连接');
const SERVER_URL = 'ws://localhost:8080';
const RECONNECT_DELAY = 3000; // 重连间隔3秒
let reconnectTimer = null;
// 生成随机颜色
const randomColor = () => {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
};
// 初始化WebSocket连接
const initWebSocket = () => {
// 关闭现有连接
if (ws.value) {
ws.value.close();
}
statusText.value = '连接中...';
ws.value = new WebSocket(SERVER_URL);
// 连接成功
ws.value.onopen = () => {
isConnected.value = true;
statusText.value = '已连接';
// 注册为控制端
ws.value.send(JSON.stringify({ type: 'register', role: 'controller' }));
// 清除重连定时器
if (reconnectTimer) clearTimeout(reconnectTimer);
};
// 接收服务器消息
ws.value.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'error') {
statusText.value = `错误:${msg.msg}`;
}
};
// 连接关闭(触发重连)
ws.value.onclose = () => {
isConnected.value = false;
statusText.value = '已断开,重连中...';
// 延迟重连,避免频繁尝试
reconnectTimer = setTimeout(initWebSocket, RECONNECT_DELAY);
};
// 连接错误
ws.value.onerror = (err) => {
console.error('WebSocket错误:', err);
statusText.value = '连接错误';
};
};
// 发送控制指令
const sendControl = (action, data) => {
if (ws.value?.readyState === WebSocket.OPEN) {
ws.value.send(JSON.stringify({
type: 'control',
data: { action, payload: data }
}));
} else {
statusText.value = '未连接,无法发送指令';
}
};
// 组件挂载时初始化连接
onMounted(initWebSocket);
// 组件卸载时清理
onUnmounted(() => {
if (ws.value) ws.value.close();
if (reconnectTimer) clearTimeout(reconnectTimer);
});
</script>
<style scoped>
.controller {
padding: 20px;
text-align: center;
}
.status {
font-size: 18px;
margin: 20px 0;
color: #ff4444;
}
.status.connected {
color: #00C851;
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 30px;
}
button {
padding: 15px 20px;
font-size: 16px;
cursor: pointer;
background: #2196F3;
color: white;
border: none;
border-radius: 8px;
}
button:hover {
background: #0b7dda;
}
</style>
3. 展示端(大屏 Vue 应用)
接收控制端指令并执行对应操作(更新文本、切换样式等),同样包含断线重连逻辑。
(1)创建展示端项目
bash
npm create vite@latest display -- --template vue
cd display
npm install
(2)核心组件 src/App.vue
javascript
<template>
<div class="display">
<h1>大屏展示端</h1>
<p class="status" :class="{ connected: isConnected }">
状态:{{ statusText }}
</p>
<div
class="display-area"
:style="{ backgroundColor: bgColor }"
>
{{ displayText }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
// 状态管理
const ws = ref(null);
const isConnected = ref(false);
const statusText = ref('未连接');
const displayText = ref('等待控制指令...');
const bgColor = ref('');
const SERVER_URL = 'ws://localhost:8080';
const RECONNECT_DELAY = 3000;
let reconnectTimer = null;
// 初始化WebSocket连接
const initWebSocket = () => {
if (ws.value) ws.value.close();
statusText.value = '连接中...';
ws.value = new WebSocket(SERVER_URL);
ws.value.onopen = () => {
isConnected.value = true;
statusText.value = '已连接';
ws.value.send(JSON.stringify({ type: 'register', role: 'display' }));
if (reconnectTimer) clearTimeout(reconnectTimer);
};
// 接收控制指令并处理
ws.value.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'control') {
handleControl(msg.data);
}
};
ws.value.onclose = () => {
isConnected.value = false;
statusText.value = '已断开,重连中...';
reconnectTimer = setTimeout(initWebSocket, RECONNECT_DELAY);
};
ws.value.onerror = (err) => {
console.error('WebSocket错误:', err);
statusText.value = '连接错误';
};
};
// 处理控制指令
const handleControl = (data) => {
switch (data.action) {
case 'showText':
displayText.value = data.payload;
break;
case 'changeColor':
bgColor.value = data.payload;
break;
case 'clear':
displayText.value = '已清空';
bgColor.value = '';
break;
}
};
onMounted(initWebSocket);
onUnmounted(() => {
if (ws.value) ws.value.close();
if (reconnectTimer) clearTimeout(reconnectTimer);
});
</script>
<style scoped>
.display {
padding: 30px;
text-align: center;
}
.status {
font-size: 20px;
margin: 20px 0;
color: #ff4444;
}
.status.connected {
color: #00C851;
}
.display-area {
margin: 50px auto;
padding: 60px 30px;
width: 80%;
min-height: 300px;
border: 5px solid #333;
border-radius: 10px;
font-size: 36px;
transition: all 0.3s ease;
}
</style>
核心特性说明
1. 响应式状态管理
- 控制端 / 展示端均通过 Vue 的
ref管理连接状态(isConnected)、状态文本(statusText)等,界面实时响应状态变化。 - 展示端的
displayText和bgColor与 DOM 绑定,接收指令后自动更新视图,体现 Vue 数据驱动的优势。
2. 断线重连机制
- 自动重连 :两端在
onclose事件中触发重连逻辑,通过setTimeout实现 3 秒间隔重试,直到连接成功。 - 连接清理 :组件卸载时(
onUnmounted)关闭 WebSocket 连接并清除重连定时器,避免内存泄漏。 - 状态可视化:通过颜色区分连接状态(绿色 = 已连接,红色 = 未连接 / 错误),用户可直观感知。
3. 指令交互流程
- 控制端点击按钮 → 调用
sendControl发送指令(包含action和payload)。 - 服务器接收指令 → 转发给展示端。
- 展示端
onmessage接收指令 → 调用handleControl执行对应操作(更新文本 / 颜色)。
运行方式
-
启动 WebSocket 服务器:
bashnode server.js -
分别启动控制端和展示端:
bash# 控制端 cd controller npm run dev # 访问 http://localhost:5173(平板或浏览器模拟) # 展示端 cd display npm run dev # 访问 http://localhost:5174(大屏或浏览器模拟) -
操作控制端按钮,观察大屏是否同步响应;断开服务器再重启,测试重连功能。
扩展性优化
- 多设备支持 :修改服务器
clients为数组,存储多个控制端 / 展示端,通过设备 ID 区分指令目标。 - 指令加密:对传输的指令进行简单加密(如 Base64),防止恶意指令干扰。
- 心跳响应 :展示端 / 控制端收到心跳包后回复
heartbeat_ack,增强连接稳定性检测。
此方案充分利用 Vue3 的响应式和组件化特性,结合 WebSocket 实现了低延迟的跨设备联动,适合会议、展览等场景的实时控制需求。