uniapp websocket 封装

368 阅读1分钟

功能

  • websocket基本的常用功能(连接、断开连接、发送/接收消息)
  • 心跳检测
  • websocket断开重连

前端代码:WebSocket.js

class WebSocketClient {
	constructor(options = {}) {
		this.url = options.url || '';
		this.protocols = options.protocols || [];
		this.heartbeatInterval = options.heartbeatInterval || 30000; // 心跳间隔,默认30秒
		this.reconnectInterval = options.reconnectInterval || 5000; // 重连间隔,默认5秒
		this.reconnectAttempts = options.reconnectAttempts || 5; // 重连尝试次数
		this.connectTimeout = options.connectTimeout || 10000; // 连接超时时间,默认10秒
		this.heartbeatTimer = null;
		this.reconnectTimer = null;
		this.reconnectCount = 0;
		this.ws = null;
		this.status = 'closed'; // closed, connecting, connected
		this.messageCallbacks = [];
		this.connectPromise = null;
	}

	// 连接WebSocket
	connect() {
		if (this.status === 'connecting') {
			return this.connectPromise;
		}

		this.status = 'connecting';

		this.connectPromise = new Promise((resolve, reject) => {
			this.ws = uni.connectSocket({
				url: this.url,
				protocols: this.protocols,
				success: () => {
					console.log('WebSocket连接创建成功');
				},
				fail: (error) => {
					console.error('WebSocket连接创建失败:', error);
					this.reconnect().then(resolve).catch(reject);
				},
			});

			this.ws.onOpen(() => {
				console.log('WebSocket连接已打开');
				this.status = 'connected';
				this.reconnectCount = 0;
				this.startHeartbeat();
				resolve();
			});

			this.ws.onClose(() => {
				console.log('WebSocket连接关闭');
				const wasConnecting = this.status === 'connecting';
				this.status = 'closed';
				this.stopHeartbeat();
				this.reconnect().then(resolve).catch(reject);
			});

			this.ws.onError((error) => {
				console.error('WebSocket错误:', error);
				const wasConnecting = this.status === 'connecting';
				this.status = 'closed';
				this.reconnect().then(resolve).catch(reject);
			});

			this.ws.onMessage((res) => {
				console.log('收到消息:', res);
				// 处理接收到的消息
				const data = res.data;
				// 忽略心跳检测的响应
				if (!data.includes('心跳检测 pong')) {
					this.messageCallbacks.forEach((callback) => callback(data));
				}
			});

			// 设置连接超时
			setTimeout(() => {
				if (this.status === 'connecting') {
					const timeoutError = new Error('WebSocket连接超时');
					if (this.reconnectCount >= this.reconnectAttempts) {
						reject(timeoutError);
					} else {
						this.reconnect().then(resolve).catch(reject);
					}
				}
			}, this.connectTimeout);
		});

		return this.connectPromise;
	}

	// 发送消息
	send(data) {
		if (this.status !== 'connected') {
			console.warn('WebSocket未连接');
			return;
		}

		this.ws.send({
			data: typeof data === 'string' ? data : JSON.stringify(data),
			fail: (error) => {
				console.error('消息发送失败:', error);
			},
		});
	}

	// 监听消息
	onMessage(callback) {
		if (typeof callback === 'function') {
			this.messageCallbacks.push(callback);
		}
	}

	// 开始心跳
	startHeartbeat() {
		this.stopHeartbeat();
		this.heartbeatTimer = setInterval(() => {
			this.send(`心跳检测 ping ${new Date().toLocaleString()}`);
		}, this.heartbeatInterval);
	}

	// 停止心跳
	stopHeartbeat() {
		if (this.heartbeatTimer) {
			clearInterval(this.heartbeatTimer);
			this.heartbeatTimer = null;
		}
	}

	// 重连
	reconnect() {
		// 检查是否达到最大重连次数
		if (this.reconnectCount >= this.reconnectAttempts) {
			console.log('达到最大重连次数,停止重连');
			const error = new Error('WebSocket连接失败,已达到最大重连次数');
			return Promise.reject(error);
		}

		if (this.reconnectTimer) {
			clearTimeout(this.reconnectTimer);
		}

		return new Promise((resolve, reject) => {
			this.reconnectTimer = setTimeout(() => {
				console.log(`第${this.reconnectCount + 1}次重连尝试`);
				this.reconnectCount++;

				this.connect()
					.then(resolve)
					.catch((error) => {
						// 只有在达到最大重连次数时才reject
						if (this.reconnectCount >= this.reconnectAttempts) {
							reject(new Error('WebSocket连接失败,已达到最大重连次数'));
						} else {
							console.log(
								`重连失败,剩余${this.reconnectAttempts - this.reconnectCount}次重试机会`
							);
							// 继续尝试重连
							this.reconnect().then(resolve).catch(reject);
						}
					});
			}, this.reconnectInterval);
		});
	}

	// 关闭连接
	close() {
		this.status = 'closed';
		this.stopHeartbeat();
		if (this.reconnectTimer) {
			clearTimeout(this.reconnectTimer);
			this.reconnectTimer = null;
		}
		if (this.ws) {
			this.ws.close({
				success: () => {
					console.log('WebSocket已手动关闭');
				},
			});
		}
	}
}

export default WebSocketClient;

参数

url (String)

  • 必填项
  • WebSocket 服务器的完整地址
  • 例如:'ws://example.com' 或 'wss://example.com'(安全连接)

protocols (Array)

  • 可选,默认 []
  • WebSocket 协议数组
  • 用于指定子协议,服务器可以根据协议选择合适的处理方式

heartbeatInterval (Number)

  • 可选,默认 30000(30秒)
  • 心跳检测的时间间隔,单位:毫秒
  • 用于保持连接活跃,防止连接因超时被关闭

reconnectInterval (Number)

  • 可选,默认 5000(5秒)
  • 重连尝试的时间间隔,单位:毫秒
  • 连接断开后,每次重连之间的等待时间

reconnectAttempts (Number)

  • 可选,默认 5
  • 最大重连尝试次数
  • 达到最大次数后将停止重连并抛出错误

connectTimeout (Number)

  • 可选,默认 10000(10秒)
  • 连接超时时间,单位:毫秒
  • 如果在指定时间内未建立连接,将触发超时错误

前端使用

<template>
	<div> </div>
</template>

<script setup>
	import { ref, onMounted, onUnmounted } from 'vue';
	import { onLoad } from '@dcloudio/uni-app';
	import WebSocketClient from '@/utils/WebSocket';

	const ws = ref(null);

	async function connectWebSocket() {
		try {
			// 创建 WebSocket 实例
			ws.value = new WebSocketClient({
				url: 'ws://192.168.1.4:4500',
				protocols: [],
				heartbeatInterval: 1000 * 2,
				reconnectInterval: 5000,
				reconnectAttempts: 2,
				connectTimeout: 10000,
			});

			// 连接
			await ws.value.connect();
			console.log('WebSocket连接成功');

			// 连接成功后注册消息处理
			ws.value.onMessage((data) => {
				console.log('页面收到消息:', data);
			});

			// 发送测试消息
			ws.value.send('Hello Server!');
		} catch (error) {
			console.error('WebSocket连接失败:', error);
			uni.showToast({
				title: error.message || '连接失败,请检查网络',
				icon: 'none',
				duration: 3000,
			});
		}
	}

	// 在组件挂载时连接
	onMounted(() => {
		connectWebSocket();
	});

	// 在组件卸载时清理连接
	onUnmounted(() => {
		if (ws.value) {
			ws.value.close();
			ws.value = null;
		}
	});
</script>

<style lang="scss" scoped></style>

后端代码

nodejs示例:

const WebSocket = require('ws');
const os = require('os');

// 获取本机IP地址
function getLocalIP() {
	const interfaces = os.networkInterfaces();
	for (let devName in interfaces) {
		const iface = interfaces[devName];
		for (let i = 0; i < iface.length; i++) {
			const alias = iface[i];
			if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
				return alias.address;
			}
		}
	}
	return 'localhost';
}

const PORT = 4500;
const HOST = getLocalIP();

// 创建 WebSocket 服务器
const wss = new WebSocket.Server({ port: PORT });

// 监听连接事件
wss.on('connection', function connection(ws) {
	console.log('新的客户端已连接');

	// 监听消息事件
	ws.on('message', function incoming(message) {
		// console.log('收到消息:', message.toString());
		let msg = message.toString();
		console.log('收到消息:', msg);
		// 和前端约定好的心跳包,如果消息是ping,则返回pong
		if (msg.includes('心跳检测 ping')) {
			ws.send(`心跳检测 pong ${new Date().toLocaleString()}`);
		} else {
			// 向客户端发送消息
			ws.send(`服务器收到消息:${msg}`);
		}
	});

	// 监听关闭事件
	ws.on('close', function close() {
		console.log('客户端已断开连接');
	});

	// 发送欢迎消息
	ws.send('欢迎连接到 WebSocket 服务器!');
});

console.log(`WebSocket 服务器已启动,连接地址: ws://${HOST}:${PORT}`);