欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

js實(shí)現(xiàn)實(shí)時(shí)刷新的三種形式(setInterval、WebSocket、EventSource)

 更新時(shí)間:2024年05月06日 11:56:42   作者:muzidigbig  
本文主要介紹了js實(shí)現(xiàn)實(shí)時(shí)刷新的三種形式(setInterval、WebSocket、EventSource),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一、純前端

可能有很多的同學(xué)有用 setInterval 控制 ajax 不斷向服務(wù)端請(qǐng)求最新數(shù)據(jù)的經(jīng)歷看下面的代碼: 

setInterval(function() {
    $.get('/get/data-list', function(data, status) {
        console.log(data)
    })
}, 5000)

這樣每隔5秒前端會(huì)向后臺(tái)請(qǐng)求一次數(shù)據(jù),實(shí)現(xiàn)上看起來(lái)很簡(jiǎn)單但是有個(gè)很重要的問(wèn)題,就是我們沒(méi)辦法控制網(wǎng)速的穩(wěn)定,不能保證在下次發(fā)請(qǐng)求的時(shí)候上一次的請(qǐng)求結(jié)果已經(jīng)順利返回,這樣勢(shì)必會(huì)有隱患,有聰明的同學(xué)馬上會(huì)想到用 setTimeout 配合遞歸看下面的代碼:

function poll() {
    setTimeout(function() {
        $.get('/get/data-list', function(data, status) {
            console.log(data)
            poll()
        })
    }, 5000)
}

當(dāng)結(jié)果返回之后再延時(shí)觸發(fā)下一次的請(qǐng)求,這樣雖然沒(méi)辦法保證兩次請(qǐng)求之間的間隔時(shí)間完全一致但是至少可以保證數(shù)據(jù)返回的節(jié)奏是穩(wěn)定的,看似已經(jīng)實(shí)現(xiàn)了需求但是這么搞我們先不去管他的性能就代碼結(jié)構(gòu)也算不上優(yōu)雅,為了解決這個(gè)問(wèn)題可以讓服務(wù)端長(zhǎng)時(shí)間和客戶端保持連接進(jìn)行數(shù)據(jù)互通h5新增了 WebSocket 和 EventSource 用來(lái)實(shí)現(xiàn)長(zhǎng)輪詢,下面我們來(lái)分析一下這兩者的特點(diǎn)以及使用場(chǎng)景。

二、服務(wù)器端

WebSocket

js跨域的解決方案 

是什么: WebSocket是一種通訊手段,基于TCP協(xié)議,默認(rèn)端口也是80和443,協(xié)議標(biāo)識(shí)符是ws(加密為wss),它實(shí)現(xiàn)了瀏覽器與服務(wù)器的全雙工通信,擴(kuò)展了瀏覽器與服務(wù)端的通信功能,使服務(wù)端也能主動(dòng)向客戶端發(fā)送數(shù)據(jù),不受跨域的限制(需后端配合)。

有什么用: WebSocket用來(lái)解決http不能持久連接的問(wèn)題,因?yàn)榭梢噪p向通信所以可以用來(lái)實(shí)現(xiàn)聊天室,以及其他由服務(wù)端主動(dòng)推送的功能例如 實(shí)時(shí)天氣、股票報(bào)價(jià)、余票顯示、消息通知等。

EventSource

是什么: EventSource的官方名稱應(yīng)該是 Server-sent events(縮寫SSE)服務(wù)端派發(fā)事件,EventSource 基于http協(xié)議只是簡(jiǎn)單的單項(xiàng)通信,實(shí)現(xiàn)了服務(wù)端推的過(guò)程客戶端無(wú)法通過(guò)EventSource向服務(wù)端發(fā)送數(shù)據(jù)。喜聞樂(lè)見(jiàn)的是ie并沒(méi)有良好的兼容當(dāng)然也有解決的辦法比如 npm install event-source-polyfill。雖然不能實(shí)現(xiàn)雙向通信但是在功能設(shè)計(jì)上他也有一些優(yōu)點(diǎn)比如可以自動(dòng)重連接,event IDs,以及發(fā)送隨機(jī)事件的能力(WebSocket要借助第三方庫(kù)比如socket.io可以實(shí)現(xiàn)重連。)

有什么用: 因?yàn)槭軉雾?xiàng)通信的限制EventSource只能用來(lái)實(shí)現(xiàn)像股票報(bào)價(jià)、新聞推送、實(shí)時(shí)天氣這些只需要服務(wù)器發(fā)送消息給客戶端場(chǎng)景中。EventSource的使用更加便捷這也是他的優(yōu)點(diǎn)。

WebSocket & EventSource 的區(qū)別

  • WebSocket基于TCP協(xié)議,EventSource基于http協(xié)議。
  • EventSource是單向通信,而websocket是雙向通信。
  • EventSource只能發(fā)送文本,而websocket支持發(fā)送二進(jìn)制數(shù)據(jù)。
  • 在實(shí)現(xiàn)上EventSource比websocket更簡(jiǎn)單。
  • EventSource有自動(dòng)重連接(不借助第三方)以及發(fā)送隨機(jī)事件的能力。
  • websocket的資源占用過(guò)大EventSource更輕量。
  • websocket可以跨域,EventSource基于http跨域需要服務(wù)端設(shè)置請(qǐng)求頭。

EventSource的實(shí)現(xiàn)案例

不知道大家有沒(méi)有見(jiàn)過(guò) Content-Type:text/event-stream 的請(qǐng)求頭,這是 HTML5 中的 EventSource 是一項(xiàng)強(qiáng)大的 API,通過(guò)服務(wù)器推送實(shí)現(xiàn)實(shí)時(shí)通信。 

客戶端代碼

// 實(shí)例化 EventSource 參數(shù)是服務(wù)端監(jiān)聽(tīng)的路由
var source = new EventSource('/EventSource-test')
source.onopen = function (event) { // 與服務(wù)器連接成功回調(diào)
  console.log('成功與服務(wù)器連接')
}
// 監(jiān)聽(tīng)從服務(wù)器發(fā)送來(lái)的所有沒(méi)有指定事件類型的消息(沒(méi)有event字段的消息)
source.onmessage = function (event) { // 監(jiān)聽(tīng)未命名事件
  console.log('未命名事件', event.data)
}
source.onerror = function (error) { // 監(jiān)聽(tīng)錯(cuò)誤
  console.log('錯(cuò)誤')
}
// 監(jiān)聽(tīng)指定類型的事件(可以監(jiān)聽(tīng)多個(gè))
source.addEventListener("myEve", function (event) {
  console.log("myEve", event.data)
})

 服務(wù)端代碼(node.js)

const fs = require('fs')
const express = require('express') // npm install express
const app = express()
 
// 啟動(dòng)一個(gè)簡(jiǎn)易的本地server返回index.html
app.get('/', (req, res) => {
  fs.stat('./index.html', (err, stats) => {
    if (!err && stats.isFile()) {
      res.writeHead(200)
      fs.createReadStream('./index.html').pipe(res)
    } else {
      res.writeHead(404)
      res.end('404 Not Found')
    }
  })
})
 
// 監(jiān)聽(tīng)EventSource-test路由服務(wù)端返回事件流
app.get('/EventSource-test', (ewq, res) => {
  // 根據(jù) EventSource 規(guī)范設(shè)置報(bào)頭
  res.writeHead(200, {
    "Content-Type": "text/event-stream", // 規(guī)定把報(bào)頭設(shè)置為 text/event-stream
    "Cache-Control": "no-cache" // 設(shè)置不對(duì)頁(yè)面進(jìn)行緩存
  })
  // 用write返回事件流,事件流僅僅是一個(gè)簡(jiǎn)單的文本數(shù)據(jù)流,每條消息以一個(gè)空行(\n)作為分割。
  res.write(':注釋' + '\n\n')  // 注釋行
  res.write('data:' + '消息內(nèi)容1' + '\n\n') // 未命名事件
 
  res.write(  // 命名事件
    'event: myEve' + '\n' +
    'data:' + '消息內(nèi)容2' + '\n' +
    'retry:' + '2000' + '\n' +
    'id:' + '12345' + '\n\n'
  )
 
  setInterval(() => { // 定時(shí)事件
    res.write('data:' + '定時(shí)消息' + '\n\n')
  }, 2000)
})
 
// 監(jiān)聽(tīng) 6788
app.listen(6788, () => {
  console.log(`server runing on port 6788 ...`)
})

客戶端訪問(wèn) http://127.0.0.1:6788/ 會(huì)看到如下的輸出:

來(lái)總結(jié)一下相關(guān)的api,客戶端的api很簡(jiǎn)單都在注釋里了,服務(wù)端有一些要注意的地方:

事件流格式?

事件流僅僅是一個(gè)簡(jiǎn)單的文本數(shù)據(jù)流,文本應(yīng)該使用UTF-8格式的編碼。每條消息后面都由一個(gè)空行作為分隔符。以冒號(hào)開(kāi)頭的行為注釋行,會(huì)被忽略。

注釋有何用?

注釋行可以用來(lái)防止連接超時(shí),服務(wù)器可以定期發(fā)送一條消息注釋行,以保持連接不斷。

EventSource規(guī)范中規(guī)定了那些字段?

  • event: 事件類型,如果指定了該字段,則在客戶端接收到該條消息時(shí),會(huì)在當(dāng)前的EventSource對(duì)象上觸發(fā)一個(gè)事件,事件類型就是該字段的字段值,你可以使用addEventListener()方法在當(dāng)前EventSource對(duì)象上監(jiān)聽(tīng)任意類型的命名事件,如果該條消息沒(méi)有event字段,則會(huì)觸發(fā)onmessage屬性上的事件處理函數(shù)。
  • data: 消息的數(shù)據(jù)字段,如果該條消息包含多個(gè)data字段,則客戶端會(huì)用換行符把它們連接成一個(gè)字符串來(lái)作為字段值。
  • id: 事件ID,會(huì)成為當(dāng)前EventSource對(duì)象的內(nèi)部屬性"最后一個(gè)事件ID"的屬性值。
  • retry: 一個(gè)整數(shù)值,指定了重新連接的時(shí)間(單位為毫秒),如果該字段值不是整數(shù),則會(huì)被忽略。

重連是干什么的?

上文提過(guò)retry字段是用來(lái)指定重連時(shí)間的,那為什么要重連呢,我們拿node來(lái)說(shuō),大家知道node的特點(diǎn)是單線程異步io,單線程就意味著如果server端報(bào)錯(cuò)那么服務(wù)就會(huì)停掉,當(dāng)然在node開(kāi)發(fā)的過(guò)程中會(huì)處理這些異常,但是一旦服務(wù)停掉了這時(shí)就需要用pm2之類的工具去做重啟操作,這時(shí)候server雖然正常了,但是客戶端的EventSource鏈接還是斷開(kāi)的這時(shí)候就用到了重連。

為什么案例中消息要用\n結(jié)尾?

\n是換行的轉(zhuǎn)義字符,EventSource規(guī)范規(guī)定每條消息后面都由一個(gè)空行作為分隔符,結(jié)尾加一個(gè)\n表示一個(gè)字段結(jié)束,加兩個(gè)\n表示一條消息結(jié)束。(兩個(gè)\n表示換行之后又加了一個(gè)空行)

注: 如果一行文本中不包含冒號(hào),則整行文本會(huì)被解析成為字段名,其字段值為空。

WebSocket的實(shí)現(xiàn)案例

WebSocket的客戶端原生api

// WebSocket 對(duì)象作為一個(gè)構(gòu)造函數(shù),用于新建 WebSocket 實(shí)例
var ws = new WebSocket('ws://localhost:8080')

// 用于指定連接成功后的回調(diào)函數(shù)
ws.onopen = function(){}

// 用于指定連接關(guān)閉后的回調(diào)函數(shù)
ws.onclose = function(){}

// 用于指定收到服務(wù)器數(shù)據(jù)后的回調(diào)函數(shù)
ws.onmessage = function(){}

// 實(shí)例對(duì)象的send()方法用于向服務(wù)器發(fā)送數(shù)據(jù)
ws.send('data')

// 用于指定報(bào)錯(cuò)時(shí)的回調(diào)函數(shù)
socket.onerror = function(){}

服務(wù)端的WebSocket如何實(shí)現(xiàn)

npm上有很多包對(duì)websocket做了實(shí)現(xiàn)比如 socket.io、WebSocket-Node、ws、還有很多,本文只對(duì) socket.io以及ws 做簡(jiǎn)單的分析,細(xì)節(jié)還請(qǐng)查看官方文檔。

socket.io和ws有什么不同

Socket.io: Socket.io是一個(gè)WebSocket庫(kù),包括了客戶端的js和服務(wù)器端的nodejs,它會(huì)自動(dòng)根據(jù)瀏覽器從WebSocket、AJAX長(zhǎng)輪詢、Iframe流等等各種方式中選擇最佳的方式來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)實(shí)時(shí)應(yīng)用(不支持WebSocket的情況會(huì)降級(jí)到AJAX輪詢),非常方便和人性化,兼容性非常好,支持的瀏覽器最低達(dá)IE5.5。屏蔽了細(xì)節(jié)差異和兼容性問(wèn)題,實(shí)現(xiàn)了跨瀏覽器/跨設(shè)備進(jìn)行雙向數(shù)據(jù)通信。

ws: 不像 socket.io 模塊, ws 是一個(gè)單純的websocket模塊,不提供向上兼容,不需要在客戶端掛額外的js文件。在客戶端不需要使用二次封裝的api使用瀏覽器的原生Websocket API即可通信。

基于socket.io實(shí)現(xiàn)WebSocket雙向通信

客戶端代碼

<button id="closeSocket">斷開(kāi)連接</button>
<button id="openSocket">恢復(fù)連接</button>
<script src="/socket.io/socket.io.js"></script>
<script>
// 建立連接 默認(rèn)指向 window.location
let socket = io('http://127.0.0.1:6788')
 
openSocket.onclick = () => {
  socket.open()  // 手動(dòng)打開(kāi)socket 也可以重新連接
}
closeSocket.onclick = () => {
  socket.close() // 手動(dòng)關(guān)閉客戶端對(duì)服務(wù)器的鏈接
}
 
socket.on('connect', () => { // 連接成功
  // socket.id是唯一標(biāo)識(shí),在客戶端連接到服務(wù)器后被設(shè)置。
  console.log(socket.id)
})
 
socket.on('connect_error', (error) => {
  console.log('連接錯(cuò)誤')
})
socket.on('disconnect', (timeout) => {
  console.log('斷開(kāi)連接')
})
socket.on('reconnect', (timeout) => {
  console.log('成功重連')
})
socket.on('reconnecting', (timeout) => {
  console.log('開(kāi)始重連')
})
socket.on('reconnect_error', (timeout) => {
  console.log('重連錯(cuò)誤')
})
 
// 監(jiān)聽(tīng)服務(wù)端返回事件
socket.on('serverEve', (data) => {
  console.log('serverEve', data)
})
 
let num = 0
setInterval(() => {
  // 向服務(wù)端發(fā)送事件
  socket.emit('feEve', ++num)
}, 1000)
 

  服務(wù)端代碼(node.js)

const app = require('express')()
const server = require('http').Server(app)
const io = require('socket.io')(server, {})
 
// 啟動(dòng)一個(gè)簡(jiǎn)易的本地server返回index.html
app.get('/', (req, res) => {
  res.sendfile(__dirname + '/index.html')
})
 
// 監(jiān)聽(tīng) 6788
server.listen(6788, () => {
  console.log(`server runing on port 6788 ...`)
})
 
// 服務(wù)器監(jiān)聽(tīng)所有客戶端 并返回該新連接對(duì)象
// 每個(gè)客戶端socket連接時(shí)都會(huì)觸發(fā) connection 事件
let num = 0
io.on('connection', (socket) => {
 
  socket.on('disconnect', (reason) => {
    console.log('斷開(kāi)連接')
  })
  socket.on('error', (error) => {
    console.log('發(fā)生錯(cuò)誤')
  })
  socket.on('disconnecting', (reason) => {
    console.log('客戶端斷開(kāi)連接但尚未離開(kāi)')
  })
 
  console.log(socket.id) // 獲取當(dāng)前連接進(jìn)入的客戶端的id
  io.clients((error, ids) => {
    console.log(ids)  // 獲取已連接的全部客戶機(jī)的ID
  })
 
  // 監(jiān)聽(tīng)客戶端發(fā)送的事件
  socket.on('feEve', (data) => {
    console.log('feEve', data)
  })
  // 給客戶端發(fā)送事件
  setInterval(() => {
    socket.emit('serverEve', ++num)
  }, 1000)
})
 
/*
  io.close()  // 關(guān)閉所有連接
*/

const io = require('socket.io')(server, {}) 第二個(gè)參數(shù)是配置項(xiàng),可以傳入如下參數(shù):

  • path: '/socket.io' 捕獲路徑的名稱
  • serveClient: false 是否提供客戶端文件
  • pingInterval: 10000 發(fā)送消息的時(shí)間間隔
  • pingTimeout: 5000 在該時(shí)間下沒(méi)有數(shù)據(jù)傳輸連接斷開(kāi)
  • origins: '*' 允許跨域

上面基于socket.io的實(shí)現(xiàn)中 express 做為socket通信的依賴服務(wù)基礎(chǔ)
socket.io 作為socket通信模塊,實(shí)現(xiàn)了雙向數(shù)據(jù)傳輸。最后,需要注意的是,在服務(wù)器端 emit 區(qū)分以下三種情況:

  • socket.emit() :向建立該連接的客戶端發(fā)送
  • socket.broadcast.emit() :向除去建立該連接的客戶端的所有客戶端發(fā)送
  • io.sockets.emit() :向所有客戶端發(fā)送 等同于上面兩個(gè)的和
  • io.to(id).emit() : 向指定id的客戶端發(fā)送事件

基于ws實(shí)現(xiàn)WebSocket雙向通信

客戶端代碼:

let num = 0
let ws = new WebSocket('ws://127.0.0.1:6788')
ws.onopen = (evt) => {
  console.log('連接成功')
  setInterval(() => {
    ws.send(++ num)  // 向服務(wù)器發(fā)送數(shù)據(jù)
  }, 1000)
}
ws.onmessage = (evt) => {
  console.log('收到服務(wù)端數(shù)據(jù)', evt.data)
}
ws.onclose = (evt) => {
  console.log('關(guān)閉')
}
ws.onerror = (evt) => {
  console.log('錯(cuò)誤')
}
closeSocket.onclick = () => {
  ws.close()  // 斷開(kāi)連接
}

服務(wù)端代碼(node.js):

const fs = require('fs')
const express = require('express')
const app = express()
 
// 啟動(dòng)一個(gè)簡(jiǎn)易的本地server返回index.html
const httpServer = app.get('/', (req, res) => {
  res.writeHead(200)
  fs.createReadStream('./index.html').pipe(res)
}).listen(6788, () => {
  console.log(`server runing on port 6788 ...`)
})
 
// ws
const WebSocketServer = require('ws').Server
const wssOptions = {  
  server: httpServer,
  // port: 6789,
  // path: '/test'
}
const wss = new WebSocketServer(wssOptions, () => {
  console.log(`server runing on port ws 6789 ...`)
})
 
let num = 1
wss.on('connection', (wsocket) => {
  console.log('連接成功')
 
  wsocket.on('message', (message) => {
    console.log('收到消息', message)
  })
  wsocket.on('close', (message) => {
    console.log('斷開(kāi)了')
  })
  wsocket.on('error', (message) => {
    console.log('發(fā)生錯(cuò)誤')
  })
  wsocket.on('open', (message) => {
    console.log('建立連接')
  })
 
  setInterval(() => {
    wsocket.send( ++num )
  }, 1000)
})

上面代碼中在 new WebSocketServer 的時(shí)候傳入了 server: httpServer 目的是統(tǒng)一端口,雖然 WebSocketServer 可以使用別的端口,但是統(tǒng)一端口還是更優(yōu)的選擇,其實(shí)express并沒(méi)有直接占用6788端口而是express調(diào)用了內(nèi)置http模塊創(chuàng)建了http.Server監(jiān)聽(tīng)了6788。express只是把響應(yīng)函數(shù)注冊(cè)到該http.Server里面。類似的,WebSocketServer也可以把自己的響應(yīng)函數(shù)注冊(cè)到 http.Server中,這樣同一個(gè)端口,根據(jù)協(xié)議,可以分別由express和ws處理。我們拿到express創(chuàng)建的http.Server的引用,再配置到 wssOptions.server 里讓W(xué)ebSocketServer根據(jù)我們傳入的http服務(wù)來(lái)啟動(dòng),就實(shí)現(xiàn)了統(tǒng)一端口的目的。

  要始終注意,瀏覽器創(chuàng)建WebSocket時(shí)發(fā)送的仍然是標(biāo)準(zhǔn)的HTTP請(qǐng)求。無(wú)論是WebSocket請(qǐng)求,還是普通HTTP請(qǐng)求,都會(huì)被http.Server處理。具體的處理方式則是由express和WebSocketServer注入的回調(diào)函數(shù)實(shí)現(xiàn)的。WebSocketServer會(huì)首先判斷請(qǐng)求是不是WS請(qǐng)求,如果是,它將處理該請(qǐng)求,如果不是,該請(qǐng)求仍由express處理。所以,WS請(qǐng)求會(huì)直接由WebSocketServer處理,它根本不會(huì)經(jīng)過(guò)express。

到此這篇關(guān)于js實(shí)現(xiàn)實(shí)時(shí)刷新的三種形式(setInterval、WebSocket、EventSource)的文章就介紹到這了,更多相關(guān)js 實(shí)時(shí)刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論