React+WebSocket应用闲置后onmessage事件无法触发问题求助
问题描述
我用React UI、WebSocket及AWS API Gateway后端开发实时聊天应用,活跃状态下运行正常,但闲置一段时间后,React UI不再响应onmessage触发器接收的消息。
相关组件代码
this.socket = MessengerService.getSocket(this.props.tokenStore.token as string, this.props.mode.toLowerCase()) this.socket.addEventListener('message', (event) => { const message = JSON.parse(event.data) audio.play() this.setState({ messages: [ ...this.state.messages, message ] }) this.scrollToLatest() })
MessengerService单例代码
export class MessengerService { private static mode: string; private static wsUrl: string; private static socket: any; private static instance: MessengerService; private constructor(token: string, mode: string) { MessengerService.mode = mode MessengerService.setURL(token, mode) MessengerService.socket = new WebSocket(MessengerService.wsUrl) MessengerService.socket.onopen = () => { console.log('connected') } MessengerService.socket.onerror = (error: any) => { MessengerService.socket.close() }; MessengerService.socket.onclose = (event: any) => { console.log('disconnected') MessengerService.reconnect() } MessengerService.socket.addEventListener('message', (event: any) => { const message = JSON.parse(event.data) if (checkNotificationPermission()) { showNotification(message.content) } }) } public static getSocket(token: string, mode: string) { if (!MessengerService.instance) { MessengerService.instance = new MessengerService(token, mode); } return MessengerService.socket; } private static setURL(token: string, mode: string) { MessengerService.wsUrl = `${APP_CONSTANTS.CHAT_WS_BASE_URL}/chat?Authorization=${token}&userType=${mode.toLowerCase()}` } public static sendMessage(message: string) { // Wait for socket to connect if (MessengerService.socket.readyState !== WebSocket.OPEN) { const wait = setInterval(() => { console.log('waiting for connection') if (MessengerService.socket.readyState === WebSocket.OPEN) { MessengerService.socket.send(message) clearInterval(wait) } }, 100) } else { MessengerService.socket.send(message) } } public static async reconnect(): Promise<WebSocket> { const token = await getCurrentToken() return new Promise((resolve, reject) => { setTimeout(() => { console.log('reconnecting') MessengerService.setURL(token, MessengerService.mode) MessengerService.socket = new WebSocket(MessengerService.wsUrl) resolve(MessengerService.socket) }, 1000) }) } }
问题原因
- 重连后未重新绑定组件的消息监听:组件仅在初始化时给首次创建的Socket实例添加
message事件监听,但reconnect方法会创建新的Socket实例覆盖静态属性,新实例未绑定组件内的监听函数,导致后续消息无法触发UI更新。 - AWS API Gateway闲置超时:AWS API Gateway的WebSocket连接默认有10分钟左右的闲置超时,超时后会主动断开连接,服务端触发重连后,组件未关联新Socket的监听。
解决方案
方案1:在MessengerService中添加监听管理机制
修改MessengerService,支持外部注册/移除消息监听,重连后自动重新绑定所有监听:
export class MessengerService { private static mode: string; private static wsUrl: string; private static socket: any; private static instance: MessengerService; // 存储所有外部注册的消息监听 private static messageListeners: Array<(event: any) => void> = []; private constructor(token: string, mode: string) { MessengerService.mode = mode MessengerService.setURL(token, mode) MessengerService.createSocket() } // 封装Socket创建逻辑,统一绑定监听 private static createSocket() { MessengerService.socket = new WebSocket(MessengerService.wsUrl) MessengerService.socket.onopen = () => { console.log('connected') } MessengerService.socket.onerror = (error: any) => { MessengerService.socket.close() }; MessengerService.socket.onclose = (event: any) => { console.log('disconnected') MessengerService.reconnect() } // 绑定内部消息监听并触发外部监听 MessengerService.socket.addEventListener('message', (event: any) => { const message = JSON.parse(event.data) if (checkNotificationPermission()) { showNotification(message.content) } MessengerService.messageListeners.forEach(listener => listener(event)) }) } public static getSocket(token: string, mode: string) { if (!MessengerService.instance) { MessengerService.instance = new MessengerService(token, mode); } return MessengerService.socket; } // 注册外部消息监听 public static addMessageListener(listener: (event: any) => void) { MessengerService.messageListeners.push(listener) if (MessengerService.socket?.readyState === WebSocket.OPEN) { MessengerService.socket.addEventListener('message', listener) } } // 移除外部消息监听(防止内存泄漏) public static removeMessageListener(listener: (event: any) => void) { MessengerService.messageListeners = MessengerService.messageListeners.filter(l => l !== listener) if (MessengerService.socket) { MessengerService.socket.removeEventListener('message', listener) } } private static setURL(token: string, mode: string) { MessengerService.wsUrl = `${APP_CONSTANTS.CHAT_WS_BASE_URL}/chat?Authorization=${token}&userType=${mode.toLowerCase()}` } public static sendMessage(message: string) { if (MessengerService.socket.readyState !== WebSocket.OPEN) { const wait = setInterval(() => { console.log('waiting for connection') if (MessengerService.socket.readyState === WebSocket.OPEN) { MessengerService.socket.send(message) clearInterval(wait) } }, 100) } else { MessengerService.socket.send(message) } } public static async reconnect(): Promise<WebSocket> { const token = await getCurrentToken() return new Promise((resolve, reject) => { setTimeout(() => { console.log('reconnecting') MessengerService.setURL(token, MessengerService.mode) MessengerService.createSocket() resolve(MessengerService.socket) }, 1000) }) } }
修改React组件代码,使用注册监听的方式,并在组件卸载时移除:
componentDidMount() { this.socket = MessengerService.getSocket(this.props.tokenStore.token as string, this.props.mode.toLowerCase()) // 保存监听函数到实例,方便卸载时移除 this.messageHandler = (event) => { const message = JSON.parse(event.data) audio.play() this.setState({ messages: [...this.state.messages, message] }) this.scrollToLatest() } MessengerService.addMessageListener(this.messageHandler) } componentWillUnmount() { MessengerService.removeMessageListener(this.messageHandler) }
方案2:组件中监听Socket重连事件,重新绑定监听
若不想修改MessengerService,可在组件中监听Socket的close事件,重连后重新绑定监听:
componentDidMount() { this.setupSocketListener() } setupSocketListener() { this.socket = MessengerService.getSocket(this.props.tokenStore.token as string, this.props.mode.toLowerCase()) this.socket.addEventListener('message', this.messageHandler) // 监听close事件,重连后重新绑定 this.socket.addEventListener('close', () => { setTimeout(() => { this.setupSocketListener() }, 1500) }) } messageHandler = (event) => { const message = JSON.parse(event.data) audio.play() this.setState({ messages: [...this.state.messages, message] }) this.scrollToLatest() } componentWillUnmount() { if (this.socket) { this.socket.removeEventListener('message', this.messageHandler) } }
额外优化:添加心跳机制
为避免AWS API Gateway闲置超时,在MessengerService中添加心跳,定期发送空消息保持连接:
private static heartbeatInterval: any; private static createSocket() { MessengerService.socket = new WebSocket(MessengerService.wsUrl) MessengerService.socket.onopen = () => { console.log('connected') // 每5分钟发送一次心跳(小于API Gateway默认10分钟超时) MessengerService.heartbeatInterval = setInterval(() => { if (MessengerService.socket.readyState === WebSocket.OPEN) { MessengerService.socket.send(JSON.stringify({ type: 'heartbeat' })) } }, 300000) } MessengerService.socket.onclose = (event: any) => { console.log('disconnected') clearInterval(MessengerService.heartbeatInterval) MessengerService.reconnect() } // ... 其他监听逻辑 }
内容的提问来源于stack exchange,提问作者stark_1
相关产品推荐
相关产品推荐

