You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    })
  }
}
问题原因
  1. 重连后未重新绑定组件的消息监听:组件仅在初始化时给首次创建的Socket实例添加message事件监听,但reconnect方法会创建新的Socket实例覆盖静态属性,新实例未绑定组件内的监听函数,导致后续消息无法触发UI更新。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 06:17:13