XMLHttpRequest:可以從客戶端發送請求到服務器端而且獲得響應。一個小demo在這裏
但XHR不適合快速來回發送消息(如聊天室),而且沒法將這一次調用和下一次調用聯繫起來(每次發起請求,服務器都要肯定請求來自何方)。html
輪詢:瀏覽器按期向服務器發送消息並接受服務器回答是否有數據更新。(考慮輪詢間隔時間過長/太短的問題)html5
COMET(長輪詢):在每次請求的時候,服務器端保持該鏈接在一段時間內處於打開狀態,而不是在響應完成以後當即關閉。
在打開狀態的這一段時間裏,有數據更新能夠當即獲得響應。
上一個長鏈接關閉以後,瀏覽器當即打開一個新的長鏈接繼續請求。java
COMET(流):瀏覽器向服務器發送一個請求,而服務器保持鏈接打開,而後週期性向瀏覽器發送數據。node
Server-Sent events:適合服務器端向客戶端不斷的數據推送,更容易實現了comet。但整個通訊徹底單向,沒法知道瀏覽器是否響應。git
WebSocket:瀏覽器可以保持對Web服務器打開的鏈接,從而與服務器長時間交換數據。適合聊天室、大型多人遊戲、端到端寫做工具等。github
WebSocket 是 HTML5 一種新的協議。實現了瀏覽器與服務器全雙工通訊,能更好的節省服務器資源和帶寬並達到實時通信,它創建在 TCP 之上,同 HTTP 同樣經過 TCP 來傳輸數據。URL 模式變爲了 ws:// 或 wss://。web
在 JavaScript 建立了WebSocket以後,會有一個 HTTP 請求發起鏈接。在取得服務器響應後,創建的鏈接會從 HTTP 協議交換爲 WebSocket 協議。也就是說,須要支持這種協議的專門服務器才能工做。json
// 應該傳入絕對url,ws://www.example.com/server var socket = new WebSocket(url);
表示當前狀態的 readyState 屬性,其值永遠從0開始:
WebSocket.OPENING(0) 正在創建鏈接
WebSocket.OPEN(1) 已經創建鏈接
WebSocket.CLOSING(2) 正在關閉鏈接
WebSocket.CLOSE(3) 已經關閉鏈接api
事件
open:成功創建鏈接
send:向服務器發送數據,只能發送純文本數據,要傳JSON數據須要先序列化
message:接收到服務器傳來數據,數存據在evt.data,一樣接收的數據也是字符串,須要手工解析
error:發生錯誤
close:鏈接關閉瀏覽器
WebSocket 實現了瀏覽器與服務器全雙工通訊,能更好的節省服務器資源和帶寬並達到實時通信,它創建在 TCP 之上,同 HTTP 同樣經過 TCP 來傳輸數據,可是它和 HTTP 最大不一樣是:
WebSocket 是一種雙向通訊協議,在創建鏈接後,WebSocket 服務器和 Browser/Client Agent 都能主動的向對方發送或接收數據,就像 Socket 同樣;
WebSocket 須要相似 TCP 的客戶端和服務器端經過握手鍊接,鏈接成功後才能相互通訊。
一旦 WebSocket 鏈接創建後,後續數據都以幀序列的形式傳輸。在客戶端斷開 WebSocket 鏈接或 服務器端斷掉鏈接前,不須要客戶端和服務端從新發起鏈接請求。
GET ws://localhost:3000/ HTTP/1.1 Host: localhost:3000 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: file:// Sec-WebSocket-Version: 13 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Sec-WebSocket-Key: 23li+QvHohBue1I3rm2VmA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
可見,發起請求的是傳統 HTTP 協議。「Upgrade:websocket」 代表這是 WebSocket 類型請求。
「Sec-WebSocket-Key」 是 WebSocket 客戶端發送的一個 base64 編碼的密文,要求服務端必須返回一個對應加密的 「Sec-WebSocket-Accept」 應答,不然客戶端會拋出 「Error during WebSocket handshake」 錯誤,並關閉鏈接。
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: dpuNuwoCREWFf0z2sQ1F2u2Sb50= Origin: file://
「Sec-WebSocket-Accept」 的值是服務端採用與客戶端一致的密鑰計算出來後返回客戶端的。
「HTTP/1.1 101 Switching Protocols」 表示服務端接受 WebSocket 協議的客戶端鏈接,握手成功,後續就能夠進行 TCP 通信了。
<div id="output"></div> <script> function checkBrowser() { if (!window.WebSocket) { console.log("Not support WebSocket!"); } } function openWS(e) { log("ws opened.", e); sendMessage("Conan"); }; function closeWS(e) { log("ws closed", e); log("was clean?" + e.wasClean + " code=" + e.code + " reason=" + e.reason); }; function receiveMsg(e) { log("RECEIVED: " + e.data, e); // ws.close(); }; function errorOccured(e) { log('Error occured: ' + e.data, e); }; function sendMessage(msg) { ws.send(msg); log("SEND : "+ msg); }; function log(s, e) { var output = document.getElementById("output"); var p = document.createElement("p"); p.style.wordWrap = "break-word"; p.style.padding = "10px"; p.style.background = "#eee"; p.textContent = "LOG : " + s; output.appendChild(p); } var ws, output; window.onload = function() { output = document.getElementById("output"); ws = new WebSocket("ws://localhost:3000"); ws.onopen = openWS; ws.onmessage = receiveMsg; ws.onclose = closeWS; ws.onerror = errorOccured; }; </script>
process.title = 'node-chat'; // Port where we'll run the websocket server var webSocketsServerPort = 3000; // websocket and http servers var webSocketServer = require('websocket').server; var http = require('http'); // latest 100 messages var history = [ ]; // list of currently connected clients (users) var clients = [ ]; /** * Helper function for escaping input strings */ function htmlEntities(str) { return String(str).replace(/&/g, '&').replace(/>/g, '>').replace(/"/g, '"'); } // Array with some colors var colors = [ 'red', 'green', 'blue', 'magenta', 'purple', 'plum', 'orange' ]; // ... in random order colors.sort(function(a,b) { return Math.random() > 0.5; } ); /** * HTTP server */ var server = http.createServer(function(request, response) { // Not important for us. We're writing WebSocket server, not HTTP server }).listen(webSocketsServerPort); /** * WebSocket server */ var wsServer = new webSocketServer({ // WebSocket server is tied to a HTTP server. WebSocket request is just // an enhanced HTTP request. For more info http://tools.ietf.org/html/rfc6455#page-6 httpServer: server }); // This callback function is called every time someone tries to connect to the WebSocket server wsServer.on('request', function(request) { console.log((new Date()) + ' Connection from origin ' + request.origin + '.'); // accept connection - you should check 'request.origin' to make sure that // client is connecting from your website // (http://en.wikipedia.org/wiki/Same_origin_policy) var connection = request.accept(null, request.origin); // we need to know client index to remove them on 'close' event var index = clients.push(connection) - 1; var userName = false; var userColor = false; console.log((new Date()) + ' Connection accepted.'); // send back chat history if (history.length > 0) { connection.sendUTF(JSON.stringify( { type: 'history', data: history} )); } // user sent some message connection.on('message', function(message) { if (message.type === 'utf8') { // accept only text if (userName === false) { // first message sent by user is their name // remember user name userName = htmlEntities(message.utf8Data); // get random color and send it back to the user userColor = colors.shift(); connection.sendUTF(JSON.stringify({ type:'color', data: userColor })); console.log((new Date()) + ' User is known as: ' + userName + ' with ' + userColor + ' color.'); } else { // log and broadcast the message console.log((new Date()) + ' Received Message from ' + userName + ': ' + message.utf8Data); // we want to keep history of all sent messages var obj = { time: (new Date()).getTime(), text: htmlEntities(message.utf8Data), author: userName, color: userColor }; history.push(obj); history = history.slice(-100); // broadcast message to all connected clients var json = JSON.stringify({ type:'message', data: obj }); for (var i=0; i < clients.length; i++) { clients[i].sendUTF(json); } } } }); // user disconnected connection.on('close', function(connection) { if (userName !== false && userColor !== false) { console.log((new Date()) + " Peer " + connection.remoteAddress + " disconnected."); // remove user from the list of connected clients clients.splice(index, 1); // push back user's color to be reused by another user colors.push(userColor); } }); });