短輪詢(Polling)的實現思路就是瀏覽器端每隔幾秒鐘向服務器端發送http請求,服務端在收到請求後,不管是否有數據更新,都直接進行響應。在服務端響應完成,就會關閉這個Tcp鏈接,以下圖所示:git
示例代碼實現以下:github
function Polling() { fetch(url).then(data => { // somthing }).catch(err => { console.log(err); }); } setInterval(polling, 5000);
Tcp
鏈接是很是消耗資源的,服務端響應完成就會關閉這個Tcp
鏈接,下一次請求再次創建Tcp
鏈接。客戶端發送請求後服務器端不會當即返回數據,服務器端會阻塞請求鏈接不會當即斷開,直到服務器端有數據更新或者是鏈接超時才返回,客戶端纔再次發出請求新建鏈接、如此反覆從而獲取最新數據。大體效果以下:web
客戶端的代碼以下:算法
function LongPolling() { fetch(url).then(data => { LongPolling(); }).catch(err => { LongPolling(); console.log(err); }); } LongPolling();
WebSocket是一種協議,是一種與HTTP 同等的網絡協議,二者都是應用層協議,都基於 TCP 協議。可是 WebSocket 是一種雙向通訊協議,在創建鏈接以後,WebSocket 的 server 與 client 都能主動向對方發送或接收數據。同時,WebSocket在創建鏈接時須要藉助 HTTP 協議,鏈接創建好了以後 client 與 server 之間的雙向通訊就與 HTTP 無關了。npm
相比於短輪詢、長輪詢的每次「請求-應答」都要client 與 server 創建鏈接的模式,WebSocket 是一種長鏈接的模式。就是一旦WebSocket 鏈接創建後,除非client 或者 server 中有一端主動斷開鏈接,不然每次數據傳輸以前都不須要HTTP 那樣請求數據。瀏覽器
另外,短輪詢、長輪詢服務端都是被動的響應,屬於單工通訊。而websocket客戶端、服務端都能主動的向對方發送消息,屬於全雙工通訊。緩存
WebSocket 對象提供了一組 API,用於建立和管理 WebSocket 鏈接,以及經過鏈接發送和接收數據。瀏覽器提供的WebSocket API很簡潔,調用示例以下:安全
var ws = new WebSocket('wss://example.com/socket'); // 建立安全WebSocket 鏈接(wss) ws.onerror = function (error) { ... } // 錯誤處理 ws.onclose = function () { ... } // 關閉時調用 ws.onopen = function () { ws.send("Connection established. Hello server!");} // 鏈接創建時調用向服務端發送消息 ws.onmessage = function(msg) { ... }// 接收服務端發送的消息複製代碼
HTTP、WebSocket 等應用層協議,都是基於 TCP 協議來傳輸數據的。咱們能夠把這些高級協議理解成對 TCP 的封裝。既然你們都使用 TCP 協議,那麼你們的鏈接和斷開,都要遵循 TCP 協議中的三次握手和四次握手 ,只是在鏈接以後發送的內容不一樣,或者是斷開的時間不一樣。對於 WebSocket 來講,它必須依賴 HTTP 協議進行一次握手 ,握手成功後,數據就直接從 TCP 通道傳輸,與 HTTP 無關了。服務器
服務器收到客戶端的握手請求後,一樣採用HTTP協議回饋。
HTTP的版本爲HTTP1.1,返回碼是101,表示升級到websocket協議
Connection字段,包含Upgrade
Upgrade字段,包含websocket
Sec-WebSocket-Accept字段,詳細介紹一下:websocket
Sec-WebSocket-Accept字段生成步驟:
- 將Sec-WebSocket-Key與協議中已定義的一個GUID 「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」進行拼接。
- 將步驟1中生成的字符串進行SHA1編碼。
- 將步驟2中生成的字符串進行Base64編碼。
客戶端經過驗證服務端返回的Sec-WebSocket-Accept的值, 來肯定兩件事情:
- 服務端是否理解WebSocket協議, 若是服務端不理解,那麼它就不會返回正確的Sec-WebSocket-Accept,則創建WebSocket鏈接失敗。
- 服務端返回的Response是對於客戶端的這次請求的,而不是以前的緩存。 主要是防止有些緩存服務器返回緩存的Response.
大致上Websocket的身份認證都是發生在握手階段,經過請求中的內容來認證。一個常見的例子是在url中附帶參數。
new WebSocket("ws://localhost:3000?token=xxxxxxxxxxxxxxxxxxxx");
淘寶的直播彈幕也是用這種方式作的身份認證。另外,websocket是採用http協議握手的,能夠用請求中攜帶cookie的方式作身份認證。
以npm的ws模塊實現爲例,其建立Websocket服務器時提供了verifyClient方法。
const wss = new WebSocket.Server({ host: SystemConfig.WEBSOCKET_server_host, port: SystemConfig.WEBSOCKET_server_port, // 驗證token識別身份 verifyClient: (info) => { const token = url.parse(info.req.url, true).query.token let user console.log('[verifyClient] start validate') // 若是token過時會爆TokenExpiredError if (token) { try { user = jwt.verify(token, publicKey) console.log(`[verifyClient] user ${user.name} logined`) } catch (e) { console.log('[verifyClient] token expired') return false } } // verify token and parse user object if (user) { info.req.user = user return true } else { info.req.user = { name: `遊客${parseInt(Math.random() * 1000000)}`, mail: '' } return true } } })
相關的ws源碼位於ws/websocket-server
// ... if (this.options.verifyClient) { const info = { origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], secure: !!(req.connection.authorized || req.connection.encrypted), req }; if (this.options.verifyClient.length === 2) { this.options.verifyClient(info, (verified, code, message) => { if (!verified) return abortHandshake(socket, code || 401, message); this.completeUpgrade(extensions, req, socket, head, cb); }); return; } if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); } this.completeUpgrade(extensions, req, socket, head, cb); }