一. 基本概念
單工:單向通信。即只能服務器->客戶端。例如: UDP協議
半雙工:既可以服務器->客戶端,也可以客戶端->服務器。但是同一時間,只能是一個方向。例如: http協議。
全雙工:雙向通信。同一時間內既可以客戶端->服務器;也可以服務器->客戶端。例如:webSocket協議
二. 雙向通信
1. 輪詢
輪詢即周期性(setInterval)的發起請求並返回響應(發送請求->響應數據->關閉連接->發送請求)。
如果響應的所需時間過長,它的再次請求也不會等待上一次的響應完成。
缺點:
1)請求過於頻繁,請求數過多,每次請求都要攜帶請求頭,浪費流量,消耗CPU的利用率
2)當客戶端過多時,會導致並發數量過高
3)后台數據不頻繁變化時, 頻繁發出的很多請求是無效請求
示例:
客戶端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <div id="root"></div> <script> const xhr = new XMLHttpRequest(); setInterval(function() { xhr.open('GET', '/clock', true); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { window.root.innerHTML = xhr.response; } } xhr.send(); }, 1000); </script> </body> </html>
服務器:
const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { res.send(new Date().toLocaleString()); }); app.listen(8888);
2. 長輪詢(Long Polling)
長輪詢是輪詢的優化。它會等待上一次的響應完成后(返回需要的數據)或者請求超時后,再發起下一次的請求。
優點:
如果響應時間大於輪詢的周期頻率,會減少請求次數,實現節約流量(請求header/請求次數),提高CPU利用率的作用。
示例:
客戶端:
<body> <div id="root"></div> <script> const xhr = new XMLHttpRequest(); // 自執行函數 (function longPolling() { xhr.open('GET', '/clock', true); xhr.onreadystatechange = function() { if(xhr.readyState === 4 && xhr.status === 200) { // 等響應返回后再次發起請求 window.root.innerHTML = xhr.response; longPolling(); } } xhr.send(); })(); </script> </body>
服務器:
const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { // 模擬響應時間較長的情況 setTimeout(function() { res.send(new Date().toLocaleString()); }, 3000); }); app.listen(8888);
3. iframe流
本質上是使用“長連接”,即請求發送后,定時返回響應,並不調用(res.end)斷開連接。
在頁面中嵌套一個隱藏的iframe,其src設置為一個長鏈接請求,服務器源源不斷的向客戶端推送數據。
示例:
客戶端
<body> <div id="root"></div> <iframe src='/clock' style="display: none"></iframe> </body>
服務器
const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { setInterval(function() { // 定時調用res.write/不調用res.end;即不斷開連接 // 時間必須用雙引號包裹,否則不會將其識別為字符串 res.write(` <script> parent.document.getElementById('root').innerHTML = "${new Date().toLocaleString()}" </script> `); }, 1000) }); app.listen(8888);
4. webSocket
WebSocket是H5提供的一種新的通信方式,可以使客戶端和服務器保持持久連接。
WebSocket使用ws協議,和http協議是同級協議,都是應用層協議。而TCP協議是傳輸層協議。
WebSocket基於TCP協議,可以基於TCP實現webSocket服務器。並且復用http協議的握手通道。
優點:
1)全雙工通信
2)請求頭體積小
缺點:
1)不兼容
2)客戶端和瀏覽器端用法不同
示例:
客戶端
<body> 用戶:<input id="username" /> 密碼:<input id="password" type="password" /> <br/> <button onclick="login()">登錄</button> <script> const ws = new WebSocket('ws://localhost:8888'); ws.onopen = function() { console.log('連接成功'); }; ws.onmessage = function(event) {// 監聽響應事件 console.log('接受到服務器響應',event.data); }; // 登錄函數 function login() { const username = document.getElementById('username').value; const password = document.getElementById('password').value; // 客戶端發送登錄信息 ws.send(JSON.stringify({type: 'login', username, password})); } </script> </body>
服務器
// 為靜態資源文件起服務;http協議 const express = require('express'); const app = express(); app.use(express.static(__dirname)); app.get('/clock', function(req, res) { res.send(new Date().toLocaleString()); }); app.listen(8080); // ws協議;webSocket通信的服務器 const WebSocketServer = require('ws').Server; const { sign } = require('jsonwebtoken');// 使用JWT const server = new WebSocketServer({port: 8888}); server.on('connection', function(socket) { console.log('連接成功'); socket.on('message', function(message) { console.log('服務器接收到消息:',message); const {username, password, type} = JSON.parse(message); if(type === 'login') { // 同步獲取token; sign的參數依次是data,secret,alg,callback(異步獲取token) const token = sign({username, password}, 'lyra');// 默認Hmac SHA256 socket.send(JSON.stringify({type: 'logined', token})); } }) });
5. socket.io
socket.io是一個webSocket庫,它包含瀏覽器端的js和服務器端的nodeJS, 目的是構建在不同瀏覽器和設備上的、通用的網絡實時應用。
優點:
1. 易用性:它封裝了客戶端和服務端
2. 跨平台
3. 自適應: 可以根據瀏覽器自動從webSocket/長輪詢/iframe流中選擇
基礎用法:
// 1. 全局廣播 io.emit('message', content); // 2. 向除自己之外的所有人廣播 socket.broadcast.emit('message', content); // 3.只廣播到特定人(socket對應的客戶端用戶) socket.emit('message', content); // 或者 socket.send(content);