HTTP 協議有一個缺陷:通訊只能由客戶端發起。
HTTP 協議作不到服務器主動向客戶端推送信息。(這種單向請求的特色,註定了若是服務器有連續的狀態變化,客戶端要獲知就很是麻煩。咱們只能使用「輪詢」:每隔一段時候,就發出一個詢問,瞭解服務器有沒有新的信息。最典型的場景就是聊天室)。javascript
WebSocket 協議的最大特色就是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。html
容許服務器端與客戶端進行全雙工(full-duplex)的通訊。舉例來講,HTTP 協議有點像發電子郵件,發出後必須等待對方回信;WebSocket 則是像打電話,服務器端和客戶端能夠同時向對方發送數據,它們之間存着一條持續打開的數據通道。java
特色包括:
(1)創建在 TCP 協議之上,服務器端的實現比較容易。
(2)與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容 易屏蔽,能經過各類 HTTP 代理服務器。
(3)數據格式比較輕量,性能開銷小,通訊高效。
(4)能夠發送文本,也能夠發送二進制數據。
(5)沒有同源限制,客戶端能夠與任意服務器通訊,徹底能夠取代 Ajax。
(6)協議標識符是ws(若是加密,則爲wss,對應 HTTPS 協議),服務器網址就是 URL。web
瀏覽器發出的 WebSocket 握手請求:canvas
GET / HTTP/1.1 Connection: Upgrade Upgrade: websocket Host: example.com Origin: null Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
Upgrade表示將通訊協議從HTTP/1.1轉向該字段指定的協議
Connection字段表示瀏覽器通知服務器,若是能夠的話,就升級到 WebSocket 協議後端
Origin字段用於提供請求發出的域名,供服務器驗證是否許可的範圍內(服務器也能夠不驗證)api
Sec-WebSocket-Key則是用於握手協議的密鑰,是 Base64 編碼的16字節隨機字符串瀏覽器
服務器的 WebSocket 迴應:安全
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s= Sec-WebSocket-Origin: null Sec-WebSocket-Location: ws://example.com/
Connection字段通知瀏覽器,須要改變協議服務器
Sec-WebSocket-Accept字段是服務器在瀏覽器提供的Sec-WebSocket-Key字符串後面,添加「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」字符串,而後再取 SHA-1 的哈希值。瀏覽器將對這個值進行驗證,以證實確實是目標服務器迴應了 WebSocket 請求
Sec-WebSocket-Location字段表示進行通訊的 WebSocket 網址
完成握手之後,WebSocket 協議就在 TCP 協議之上,開始傳送數據。
var ws = new WebSocket('wss://echo.websocket.org'); ws.onopen = function(evt) { console.log('Connection open ...'); ws.send('Hello WebSockets!'); }; ws.onmessage = function(evt) { console.log('Received Message: ' + evt.data); ws.close(); }; ws.onclose = function(evt) { console.log('Connection closed.'); };
創建鏈接和斷開鏈接 發送數據和接收數據 處理錯誤 WebSocket對象做爲一個構造函數,用於新建WebSocket實例
var ws = new WebSocket('ws://localhost:8080');
webSocket.readyState屬性返回實例對象的當前狀態,共有四種
CONNECTING:值爲0,表示正在鏈接。 OPEN:值爲1,表示鏈接成功,能夠通訊了。 CLOSING:值爲2,表示鏈接正在關閉。 CLOSED:值爲3,表示鏈接已經關閉,或者打開鏈接失敗。
例子:
switch (ws.readyState) { case WebSocket.CONNECTING: // do something break; case WebSocket.OPEN: // do something break; case WebSocket.CLOSING: // do something break; case WebSocket.CLOSED: // do something break; default: // this never happens break; }
實例對象的onopen屬性,用於指定鏈接成功後的回調函數。
ws.onopen = function () { ws.send('Hello Server!'); }
若是要指定多個回調函數,可使用addEventListener方法。
ws.addEventListener('open', function (event) { ws.send('Hello Server!'); });
實例對象的onclose屬性,用於指定鏈接關閉後的回調函數。
ws.onclose = function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event }; ws.addEventListener("close", function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event });
實例對象的onmessage屬性,用於指定收到服務器數據後的回調函數。
ws.onmessage = function(event) { var data = event.data; // 處理數據 }; ws.addEventListener("message", function(event) { var data = event.data; // 處理數據 });
服務器數據多是文本,也多是二進制數據(blob對象或Arraybuffer對象)
ws.onmessage = function(event){ if(typeOf event.data === String) { console.log("Received data string"); } if(event.data instanceof ArrayBuffer){ var buffer = event.data; console.log("Received arraybuffer"); } }
可使用binaryType屬性,顯式指定收到的二進制數據類型
/ 收到的是 blob 數據 ws.binaryType = "blob"; ws.onmessage = function(e) { console.log(e.data.size); }; // 收到的是 ArrayBuffer 數據 ws.binaryType = "arraybuffer"; ws.onmessage = function(e) { console.log(e.data.byteLength); };
實例對象的send()方法用於向服務器發送數據
ws.send('your message');
// Blob對象表示一個不可變的, 原始數據的相似文件對象。Blob表示的數據不必定是一個JavaScript原生格式 blob對象本質上是js中的一個對象,裏面能夠儲存大量的二進制編碼格式的數據。
發送 Blob 對象的例子。
var file = document .querySelector('input[type="file"]') .files[0]; ws.send(file);
發送 ArrayBuffer 對象的例子。
// Sending canvas ImageData as ArrayBuffer var img = canvas_context.getImageData(0, 0, 400, 320); var binary = new Uint8Array(img.data.length); for (var i = 0; i < img.data.length; i++) { binary[i] = img.data[i]; } ws.send(binary.buffer);
實例對象的bufferedAmount屬性,表示還有多少字節的二進制數據沒有發送出去。它能夠用來判斷髮送是否結束。
var data = new ArrayBuffer(10000000); socket.send(data); if (socket.bufferedAmount === 0) { // 發送完畢 } else { // 發送還沒結束 }
實例對象的onerror屬性,用於指定報錯時的回調函數。
socket.onerror = function(event) { // handle error event }; socket.addEventListener("error", function(event) { // handle error event });
WebSocketTest.java:
package me.gacl.websocket; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; /** * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端, * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端 */ @ServerEndpoint("/websocket") public class WebSocketTest { //靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。 private static int onlineCount = 0; //concurrent包的線程安全Set,用來存放每一個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通訊的話,可使用Map來存放,其中Key能夠爲用戶標識 private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>(); //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 private Session session; /** * 鏈接創建成功調用的方法 * @param session 可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 */ @OnOpen public void onOpen(Session session){ this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在線數加1 System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount()); } /** * 鏈接關閉調用的方法 */ @OnClose public void onClose(){ webSocketSet.remove(this); //從set中刪除 subOnlineCount(); //在線數減1 System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount()); } /** * 收到客戶端消息後調用的方法 * @param message 客戶端發送過來的消息 * @param session 可選的參數 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("來自客戶端的消息:" + message); //羣發消息 for(WebSocketTest item: webSocketSet){ try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); continue; } } } /** * 發生錯誤時調用 * @param session * @param error */ @OnError public void onError(Session session, Throwable error){ System.out.println("發生錯誤"); error.printStackTrace(); } /** * 這個方法與上面幾個方法不同。沒有用註解,是根據本身須要添加的方法。 * @param message * @throws IOException */ public void sendMessage(String message) throws IOException{ this.session.getBasicRemote().sendText(message); //this.session.getAsyncRemote().sendText(message); } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketTest.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketTest.onlineCount--; } }
pom.xml:
<dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> </dependencies>
index.jsp:
<%@ page language="java" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <title>Java後端WebSocket的Tomcat實現</title> </head> <body> Welcome<br/><input id="text" type="text"/> <button onclick="send()">發送消息</button> <hr/> <button onclick="closeWebSocket()">關閉WebSocket鏈接</button> <hr/> <div id="message"></div> </body> <script type="text/javascript"> var websocket = null; //判斷當前瀏覽器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://localhost:8080/websocket"); } else { alert('當前瀏覽器 Not support websocket') } //鏈接發生錯誤的回調方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket鏈接發生錯誤"); }; //鏈接成功創建的回調方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket鏈接成功"); } //接收到消息的回調方法 websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } //鏈接關閉的回調方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket鏈接關閉"); } //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。 window.onbeforeunload = function () { closeWebSocket(); } //將消息顯示在網頁上 function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //關閉WebSocket鏈接 function closeWebSocket() { websocket.close(); } //發送消息 function send() { var message = document.getElementById('text').value; websocket.send(message); } </script> </html>