切換語言為:簡體

不止WebSocket可以實現長連線,它也可以

  • 爱糖宝
  • 2024-09-06
  • 2050
  • 0
  • 0

前言

首先聽到服務端訊息推送一般大多數可能都是使用到的websocket,但是websocket一般主要是用於聊天室,而SSE(Server-Sent Events)也是可以基於伺服器來與Web頁面來進行資料和訊息的推送的,但是與websocket不同的是,伺服器傳送事件是單項的,資料只能從服務端傳送到客戶端。例如我們可以看一下常用的chatGtp他的文字輸出就是基於伺服器的訊息推送進行輸出的。下面我們見看如何來建立一個通訊連線

EventSource

EventSource是一個例項,專門用來建立與伺服器的連線接收伺服器的訊息推送的,他會與伺服器建立一個HTTP的長連線,他會一致保持開啟,直到呼叫close關閉連線。ps:他是無法使用axios的因為axios內部其實使用的是XMLHttpRequest,他是無法支援接收服務端推送的訊息的。

我們先實現一個簡單的後臺服務,然後前臺來使用這個例項看一下效果是怎麼樣的。

const article = `警告:當不使用 HTTP/2 時.....。`
app.get('/chat_typing', (req, res) => {
  // 開啟 Server-sent events
  res.setHeader('Content-Type', 'text/event-stream')
  let index = 0
  let timerId = 0
  // 模擬每隔 0.1s 向前端推送一次
  timerId = setInterval(() => {
    // 獲取文字
    const data = article[index]
    // 下標累加
    index++
    // 響應結果
    if (data) {
      // data:表示資料內容,\n\n 表示結尾。
      res.write(`data: ${data}\n\n`)
    } else {
      res.end()
      clearInterval(timerId)
    }
  }, 100)
})

這裏我們簡單實現了一個node服務,主要的就是Content-Type這裏我們把他設定爲了text/event-stream,也就是服務端的訊息推送,然後我們每100毫秒推送一個字元過去,然後我們再前臺看一下接收的效果是什麼樣子的

<script setup>
import { ref } from 'vue'

const article = ref('')
let source
const OpenSSE = () => {
  source = new EventSource('http://localhost:3000/chat_typing')
  // 接收資訊
  source.addEventListener('message', (e) => {
    // 實時輸出字串
    article.value += e.data
  })
}
</script>

<template>
  <div>
    <div>
      <button @click="OpenSSE">開啟SSE</button>
    </div>
    <div>{{ article }}</div>
  </div>
</template>

前臺內我們就是宣告了一個EventSource例項,然後點選開啟的時候監聽他的message,拿到伺服器推送過來的訊息,進行組裝展示。就實現瞭如下的效果

不止WebSocket可以實現長連線,它也可以 

這裏我們可以看到他確實是開啟了一個長連線,一直在接收伺服器推送過來的資料,且請求型別也變成了eventsource,我們在某些情況下需要關閉這個連線只需要呼叫例項方法內的close方法即可對其進行關閉

const CloseSSE = () => {
  source.close()
}

最佳化連線

EventSource雖然可以讓我們實現一些簡單場景下的伺服器訊息推送的接收,但是他只支援get請求,並且只能透過路徑拼接的方式進行引數的攜帶。但是我們如果遇到像chat一樣會攜帶上下文進行數據傳輸的時候那麼就不太適合了,因為處理起來比較麻煩,並且有著一定長度的限制。那麼我們就可以使用fetch來進行連線,下面我們看一下如何使用fetch來建立連線獲取伺服器推送過來的訊息。

const OpenSSE = async () => {
  const res = await fetch('http://localhost:3000/chat_typing')
  console.log(res)
}

這裏我們呢就實現了連線,那麼如何去停呢,我們需要使用到 AbortController來終止請求

const OpenSSE = async () => {
  abort = new AbortController()
  const res = await fetch('http://localhost:3000/chat_typing', {
    signal: abort.signal,
  })
  console.log(res)
}
const CloseSSE = () => {
  abort.abort()
}

abort可以用來終止一個未完成的非同步操作,可以用來終止所有的響應和流。正好我們現在使用fetch返回過來的就是一個流,那麼解決了請求和終止操作,接下來就需要解析返回的流了

解析流

我們首先列印res.body可以看到內部存在一個方法,getReader,他呢就是用來獲取流資料的,我們是用res.body.getReader可以獲取到一個一個Promise,那麼我們就可以進行執行拿到Promise的值。

  const res = await fetch('http://localhost:3000/chat_typing', {
    signal: abort.signal,
  })
  const content = res.body.getReader()
  const decode = new TextDecoder()
  while (content) {
    const { done, value } = await content.read()
    if (done) break
    console.log(done, decode.decode(value))
  }

這裏呢我們逐步讀取流,content.read()是一個非同步操作,從流中讀取下一塊資料。它返回一個包含兩個屬性的物件,donevalue分別是讀取到的值和是否讀取完畢,當讀取完畢我們直接結束迴圈,但是此刻的value還是一個Uint8Array的資料格式,我們需要使用TextDecoder進行轉換為字串,到這裏就可以拿到數據流推過來的東西了。此刻我們可以再修改一下後臺,修改爲post請求來實驗一下,並且可以直接返回要推送的值。

不止WebSocket可以實現長連線,它也可以

app.post('/chat_typing')
res.write({data})
 const res = await fetch('http://localhost:3000/chat_typing', {
    method: 'POST',
    signal: abort.signal,
  })

這樣我們也就實現了可以使用fetch並且寫道引數來讀取數據流了,並且使用到了POST請求

結尾

這裏呢主要介紹的就是如何與後臺進行連線實現訊息推送,並且傳遞引數接收數據流,像這樣的訊息推送一般都是用來訊息訂閱,訊息推送,提醒等功能上。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.