ChatGPT对话用Websocket和使用EventSource区别

546 阅读4分钟

  当涉及实现 GPT 打字时,开发人员通常会考虑使用两种主要的技术:Microsoft 的 fetch-event-source 库和 WebSocket。这两种技术都能够实现实时的双向通信,但它们在一些方面有着不同的特点和适用场景。下面我们来对这两种技术进行对比。

fetch-event-source

 microsoft/fetch-event-source是一个由Microsoft开发的JavaScript库,用于在浏览器中实现Fetch Event Source功能。Fetch Event Source是一种基于事件的HTTP客户端技术,用于从服务器端获取数据的推送通知。

优点:

  1. 简单易用: fetch-event-source 是一个基于 EventSource API 的库,使用起来非常简单。只需要创建一个 EventSource 实例并指定服务器端点,即可实现服务器端推送消息的接收。
  2. 基于 HTTP: fetch-event-source 底层基于 HTTP 协议,可以利用现有的 HTTP 基础设施进行通信。这意味着不需要额外的服务器端配置,也能够轻松地穿越防火墙。
  3. 适用于简单场景: 对于简单的实时通信需求,如实时聊天、通知推送等,fetch-event-source 是一个非常合适的选择。

缺点:

  1. 单向通信: fetch-event-source 只支持从服务器端向客户端的单向通信,无法实现客户端向服务器端的双向通信。
  2. 限制: 由于基于 HTTP 长连接,fetch-event-source 在某些情况下可能会受到连接超时、重连机制等限制。

        以下是一个简单的示例,演示如何使用microsoft/fetch-event-source库来实现GPT打字

    import { fetchEventSource } from '@microsoft/fetch-event-source'
    const ctrl = new AbortController()
    const eventSource: any = fetchEventSource('https://xxx.xx.xx/gptchat/gpt', {
      method: 'POST',
      mode: 'no-cors',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        uuid: 1212,
        input: '000042776159',
        genType: '专家研究报告',
        break: 0,
      }),
      signal: ctrl.signal,
      onmessage(ev) {
        console.log(ev.data)
        eventSource.abort()
      },
      onclose() {
        eventSource.abort()
      },
      onerror(error) {
        eventSource.abort()
      },
    })

        在这个示例中,我们创建了一个新的EventSource对象,并指定了要连接的服务器端点'/gpt-typing'。然后,我们监听了几种事件,包括消息事件(onmessage)、连接打开事件(onopen)、连接关闭事件(onclose)和错误事件(onerror)。当服务器端发送消息时,会触发onmessage事件,并将消息内容显示在控制台上。

        这样,我们就可以利用microsoft/fetch-event-source库来实现在浏览器中接收GPT打字的推送通知。

 import { useWebSocket } from '@vueuse/core'

    export interface IWebSocketConfig {
      wsUrl: string
      isHeartbeat?: boolean
      isAutoReconnect?: boolean
      immediate?: boolean
      autoReconnect?: {
        retries?: number
        delay?: number
      }
      heartbeat?: {
        message?: string
        interval?: number
        pongTimeout?: number
      }
      onFailedCall?: Function
      onConnectedCall?: Function
      onDisconnectedCall?: Function
      onErrorCall?: Function
      onMessageCall?: Function
    }

    export const useWebSocketHook = (options?: IWebSocketConfig): any => {
      const {
        autoReconnect = {},
        isHeartbeat = false,
        isAutoReconnect = false,
        immediate = false,
        heartbeat = {},
        onConnectedCall,
        onDisconnectedCall,
        onErrorCall,
        onMessageCall,
        onFailedCall,
        wsUrl,
      } = options ?? {}

      const { status, data, send, open, close, ws } = useWebSocket(wsUrl, {
        autoReconnect: isAutoReconnect
          ? {
              retries: 3, // 最大重试次数。
              delay: 1000, // 延迟
              onFailed() {
                console.log('*** Failed to connect WebSocket after 3 retries ***')
                onFailedCall && onFailedCall()
              },
              ...autoReconnect,
            }
          : false,
        immediate: immediate,
        heartbeat: isHeartbeat
          ? {
              message: 'ping',
              interval: 1000,
              pongTimeout: 50000,
              ...heartbeat,
            }
          : false,
        onConnected(ws: WebSocket) {
          onConnectedCall && onConnectedCall(ws)
        },
        onDisconnected(ws: WebSocket, event: CloseEvent) {
          onDisconnectedCall && onDisconnectedCall(ws, event)
        },
        onError(ws: WebSocket, event: Event) {
          onErrorCall && onErrorCall(ws, event)
        },
        onMessage(ws: WebSocket, event: MessageEvent) {
          onMessageCall && onMessageCall(ws, event)
        },
      })

      return {
        status,
        data,
        send,
        open,
        close,
        ws,
      }
    }

    import { useWebSocketHook } from '@/hooks/useWebSocket'

    const { send, open, close, data, status } = useWebSocketHook({
      isAutoReconnect: true,
      wsUrl: import.meta.env.VITE_SOCKET_URL,
      onConnectedCall: (ws: WebSocket) => {
        console.log('连接成功!', ws, status)
        state.wsReadyState = ws.readyState
        if (state.isSendData) {
          handleSendData()
        }
      },
      onMessageCall: () => {
        handleReceivedData(data.value)
      },
      onDisconnectedCall: (ws: WebSocket) => {
        console.log('断开连接!', ws)
        state.wsReadyState = ws.readyState
      },
      onErrorCall: (ws: WebSocket) => {
        console.log('连接失败!', ws.readyState)
        state.wsReadyState = ws.readyState
      },
      onFailedCall: () => {
        state.wsReadyState = -1
        state.isLoading = false
        state.generateLoading = false
      },
    })

WebSocket

优点:

  1. 双向通信: WebSocket 支持全双工通信,客户端和服务器端都可以随时发送消息,实现实时的双向通信。
  2. 低延迟: WebSocket 使用 TCP 协议,相比 HTTP 长连接具有更低的延迟和更高的效率,适合实时性要求较高的场景。
  3. 灵活性: WebSocket 提供了更灵活的消息传输方式,可以发送文本、二进制数据等多种类型的消息。

缺点:

  1. 复杂度: 相对于 fetch-event-source,WebSocket 的使用稍显复杂,需要处理连接管理、心跳检测等细节。
  2. 部署限制: WebSocket 通常需要额外的服务器端配置和支持,某些环境下可能存在部署限制。

对比总结

  • 适用场景: 对于简单的实时通信需求,如消息通知、事件推送等,fetch-event-source 是一个简单、轻量级的选择;而对于需要实现双向通信、低延迟等要求较高的场景,则应选择 WebSocket。
  • 复杂度: fetch-event-source 更简单易用,适合于快速实现简单的实时通信功能;WebSocket 虽然稍显复杂,但提供了更多灵活性和功能扩展的可能性。
  • 性能和效率: WebSocket 相比 fetch-event-source 在延迟和效率上更有优势,特别是在需要大量实时数据传输的场景下,WebSocket 更能满足需求。

综上所述,开发人员应根据具体的需求和场景选择合适的技术来实现 GPT 打字功能。如果是简单的实时通信需求,fetch-event-source 可能更适合;而对于更复杂、性能要求更高的场景,则应选择 WebSocket。