1. 程式人生 > >Comet,SSE,WebSocket前後端的實現

Comet,SSE,WebSocket前後端的實現

() -a 自定義事件 配置 cat cache 開發 return local

Comet(服務器推送)的兩種方式

短輪詢

頁面定時向服務器發送請求, 步驟為:建立連接——數據傳輸——關閉連接...建立連接——數據傳輸——關閉連接

//前端js
  var xhr = new XMLHttpRequest();
  setInterval(()=>{
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4) {
        let result = xhr.responseText
        console.log(result);
      }
    }
    xhr.open('get', '/front/test');
    xhr.send(null);
  },3000)
}

//後端 node
const Koa = require('koa');
const app = new Koa();
const router = require('koa-router')()
const koaBody = require('koa-body');

router.get('/front/test', koaBody(), ctx=>{
  // ctx.set('Connection', 'close');  不設置response.header,默認長連接,否則為短鏈接
  ctx.body = { title: 'chuchur'}
});
app.use(router.routes());

app.listen(7005, () => {
  console.log('server run in http://localhost:7005');
});

長輪詢

長輪詢的方式是,頁面向服務器發起一個請求,服務器一直保持tcp連接打開,知道有數據可發送。發送完數據後,頁面關閉該連接,隨即又發起一個新的服務器請求,在這一過程中循環,步驟為:建立連接——數據傳輸...(保存連接)...數據傳輸——關閉連接

var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4) {
        let result = xhr.responseText
        console.log(result);
        xhr.open('get', '/front/test'); //在獲得數據後重新向服務器發起請求
        xhr.send(null);
      }
    }
    xhr.open('get', '/front/test');
    xhr.send(null);

短輪詢和長輪詢的區別是:短輪詢中服務器對請求立即響應,而長輪詢中服務器等待新的數據到來才響應,因此實現了服務器向頁面推送實時,並減少了頁面的請求次數。

http流

//前端
var xhr = new XMLHttpRequest();
var received = 0;   //最新消息在響應消息的位置
xhr.onreadystatechange = function () {
  if (xhr.readyState == 3) {
    let result = xhr.responseText.substring(received);
    console.log(result);
    received += result.length;
  } else if (xhr.readyState == 4) {
    console.log("完成消息推送");
  }
}
xhr.open('get', '/front/test');
xhr.send(null);

//node
router.get('/front/test', koaBody(), ctx=>{
  ctx.set({
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  ctx.body = { title: 'chuchur'}
});

http流不同於上述兩種輪詢,因為它在頁面整個生命周期內只使用一個HTTP連接,具體使用方法即頁面向瀏覽器發送一個請求,而服務器保持tcp連接打開,然後不斷向瀏覽器發送數據。

SSE eventSource

eventSource是用來解決web上服務器端向客戶端推送消息的問題的。不同於ajax輪詢的復雜和websocket的資源占用過大,eventSource(sse)是一個輕量級的,易使用的消息推送API ,大多數瀏覽器實現了SSE(Server-Sent Events,服務器發送事件) API,SSE支持短輪詢、長輪詢和HTTP流

前端實現

//生成EventSource對象,url必須同源
var evtSource = new EventSource("/front/test");  
//收到服務器發生的事件時觸發,如果連接斷開,還會自動重新連接
evtSource.onmessage = function (e) {
  console.log(e.data);
}
//成功與服務器發生連接時觸發
evtSource.onopen = function () {
  console.log("Server open")
}
//出現錯誤時觸發
evtSource.onerror = function (e) {
  console.log("Error")
}

//自定義事件
evtSource.addEventListener("test", function (e) {
  console.log(e.data);
}, false)

服務端實現

//node
router.get('/front/test', koaBody(), ctx=>{
  ctx.set({
    'Content-Type': 'text/event-stream',  //事件流的對應MIME格式為text/event-stream
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  });
  ctx.body = 
  'id:11\n'+
  'retry: 100\n'+
  'event:test\n'+
  'data: hello word\n\n'
});

服務端返回數據需要特殊的格式,它分為四種消息類型:event, data, id, retry

  • event指定自定義消息的名稱
  • data指定具體的消息體,可以是對象或者字符串
  • id為當前消息的標識符,可以不設置
  • retry設置當前http連接失敗後,重新連接的間隔。EventSource規範規定,客戶端在http連接失敗後默認進行重新連接,重連間隔為3s,通過設置retry字段可指定重連間隔;

每個字段都有名稱,緊接著有個”:“。當出現一個沒有名稱的字段而只有”:“時,這就會被服務端理解為”註釋“,並不會被發送至瀏覽器端,如: commision

WebSocket 全雙工通訊

WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。
瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以後,客戶端和服務器端就可以通過 TCP 連接直接交換數據。

前端實現

let socket = new WebSocket('ws://localhost:7005/ws/test')

// 建立連接
socket.onopen = () => {
  console.log('connection')
  //發送消息,告訴服務器,我上線了.
  socket.send(JSON.stringify({ name: 'chuchur' + Date.now(), msg: '我來了' }))
}
//接收消息
socket.onmessage = (evt) => {
  let data = JSON.parse(evt.data);
  console.log(data)
}

//socket 斷開
socket.onclose = () => {
  console.log('close')
}
//socket 發生錯誤
socket.onerror = () => {
  console.log('error')
}

後端的實現

//引入相關ws庫
....
const WebSocketServer = require('ws').Server;
//此處定義變量
const server = app.listen(7005, () => {
  console.log('server run in http://localhost:7005');
});
//拿到ws對象
const wss = new WebSocketServer({ server });

//鏈接成功
wss.on('connection', ws => {
  console.log('有人來了') //當客戶端連接之後
  //接收消息
  ws.on('message', data => {
    data = JSON.parse(data)
    console.log(data)

    if (data.name) {
      //廣播,告訴大家,有人來了
      wss.broadcast(JSON.stringify({ msg: data.name + '來了,大家歡迎' }))

      //記錄ID,可以實現客戶端一對一,或一對多通訊,ID為唯一值
      ws.id = data.name
    }
    //實現一對一通話
    if (data.toID) {
      wss.clients.forEach(client => {
        if (data.toID == client.id) {
          client.send(JSON.stringify({ msg: ws.name + '對你說:' + data.msg }))
          return;
        }
      })
    }
  })

  //發送消息
  ws.send(JSON.stringify({ msg: '你來了, 獎勵一顆糖' }));
})

//廣播
wss.broadcast = data => {
  wss.clients.forEach(client => {
    client.send(data);
  });
};

setInterval(() => {
  console.log('當前在線人數:' + wss.clients.size)
}, 1000);

ws轉發配置

//webpack 開發配置
devServer: {
 ...
 proxy: {
      '/ws': {
          target: 'http://120.0.0.1:7005', //轉發到node
          changeOrigin: true,
      }
  }
}
//nginx 部署
server {
    listen      80;
    server_name localhost;

    # 處理WebSocket連接:
    location ^~ /ws/ {
        proxy_pass         http://127.0.0.1:7005;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }

    # 其他所有請求:
    location / {
        proxy_pass       http://127.0.0.1:7005;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

關於更多的ws相關配置,可以參考 WS
開發過程中需要自行做轉發

總結

  • ajax短輪詢:省事,最簡單,不論服務端是否返回數據,埋頭苦幹,任勞任怨
  • 長輪詢:也是埋頭,只不過是,拿到數據才做出反應. 服務器有個阻塞的過程.
  • SSE:可以接收服務端推送.接收http流,雙向可控
  • Socket:全雙工通訊,功能強大, 耗資源

各有優缺點, 主要是看什麽場景用什麽.

Comet,SSE,WebSocket前後端的實現