WebSocket是什麼?javascript
WebSocket是一種在單個TCP鏈接上進行全雙向通訊的協議。
WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,容許服務端主動向客戶端推送數據。
在WebSocket API中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸。
Websocket 經過HTTP/1.1 協議的101狀態碼進行握手。html
握手java
在客戶端,new WebSocket實例化一個新的WebSocket客戶端對象,請求相似 ws://yourdomain:port/path 的服務端WebSocket URL,客戶端WebSocket對象會自動解析並識別爲WebSocket請求,並鏈接服務端端口,執行雙方握手過程,客戶端發送數據格式相似:web
能夠看到,客戶端發起的WebSocket鏈接報文相似傳統HTTP報文,Upgrade:websocket參數值代表這是WebSocket類型請求,Sec-WebSocket-Key是WebSocket客戶端發送的一個 base64編碼的密文,要求服務端必須返回一個對應加密的Sec-WebSocket-Accept應答,不然客戶端會拋出Error during WebSocket handshake錯誤,並關閉鏈接。spring
服務端收到報文後返回的數據格式相似:瀏覽器
Sec-WebSocket-Accept的值是服務端採用與客戶端一致的密鑰計算出來後返回客戶端的,HTTP/1.1 101 Switching Protocols表示服務端接受WebSocket協議的客戶端鏈接,通過這樣的請求-響應處理後,兩端的WebSocket鏈接握手成功, 後續就能夠進行TCP通信了。安全
保持長鏈接服務器
服務器和客戶端經過發送Ping/Pong+Frame(RFC+6455+-+The+WebSocket+Protocol)。這種 Frame+是一種特殊的數據包,它只包含一些元數據而不須要真正的 Data+Payload,能夠在不影響應用的狀況下維持住中間網絡的鏈接狀態。websocket
例子1網絡
pom文件:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>1.3.5.RELEASE</version> </dependency>
java:
package com.test; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; @ServerEndpoint(value = "/websocket") @Component public class WebSocketController { //當前在線鏈接數。應該把它設計成線程安全的。 private static int onlineCount = 0; //concurrent包的線程安全Set,用來存放每一個客戶端對應的WebSocket對象。 private static CopyOnWriteArraySet<WebSocketController> webSocketSet = new CopyOnWriteArraySet<WebSocketController>(); //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據 private Session session; /** * 鏈接創建成功調用的方法 */ @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this);//加入set中 addOnlineCount();//在線數加1 System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount()); try { sendMessage("init success!"); } catch (IOException e) { e.printStackTrace(); } } /** * 鏈接關閉調用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this);//從set中刪除 subOnlineCount();//在線數減1 System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount()); } /** * 收到客戶端消息後調用的方法 * @param message 客戶端發送過來的消息 */ @OnMessage public void onMessage(String message, Session session) { System.out.println("來自客戶端的消息:" + message); try { session.getBasicRemote().sendText("service:"+message); } catch (IOException e) { e.printStackTrace(); } } /** * 發生錯誤時調用 */ @OnError public void onError(Session session, Throwable error) { System.out.println("發生錯誤"); error.printStackTrace(); } /** * 發送消息 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 羣發自定義消息 */ public static void sendInfo(String message) { if(webSocketSet!=null && webSocketSet.size()>0) { for (WebSocketController item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); continue; } } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketController.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketController.onlineCount--; } }
package com.test; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
html:
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>WebSocket</title> </head> <body> Welcome Use WebSocket<br/> <input id="text" type="text" /> <button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button> <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("error"); }; //鏈接成功創建的回調方法 websocket.onopen = function(event){ setMessageInnerHTML("open"); } //接收到消息的回調方法 websocket.onmessage = function(event){ setMessageInnerHTML(event.data); } //鏈接關閉的回調方法 websocket.onclose = function(){ setMessageInnerHTML("close"); } //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。 window.onbeforeunload = function(){ websocket.close(); } //將消息顯示在網頁上 function setMessageInnerHTML(innerHTML){ document.getElementById('message').innerHTML += innerHTML + '<br/>'; } //關閉鏈接 function closeWebSocket(){ websocket.close(); } //發送消息 function send(){ var message = document.getElementById('text').value; websocket.send(message); } </script> </html>
例子2(一個websocket分發多個功能,經過參數區分)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>1.3.5.RELEASE</version> </dependency>
package com.cuc.happyseat.config.websocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * 開啓WebSocket支持 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
WebSocket服務類對照下面的代碼看:
第20行,@ServerEndpoint("/websocket/{userID}"),括號中的內容就是客戶端請求Socket鏈接時的訪問路徑,userID是我要求客戶端傳來的參數,我這裏算是爲了標識該客戶端吧。
第28行,在該類中添加屬性 userID,並添加對應的getUserID()方法。
第46行,在onOpen()方法即創建鏈接的時候就接收參數userID,須要標識@PathParam("userID") 。接收參數後直接賦值給屬性userID。
第140-157行,是針對特定客戶端發送消息。服務器和客戶端在創建鏈接成功後就生成了一個WebSocket對象,並存在集合中,對象裏特有的屬性是咱們設置的userID。因此經過惟一的userID就能標識服務器與該客戶端創建的那個鏈接啦!這樣要求發送消息時,傳入userID與消息,服務器在本身的WebSocket鏈接集合中遍歷找到對應客戶端的鏈接,就能夠直接發消息過去啦~~
package com.cuc.happyseat.websocket; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.EncodeException; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import org.springframework.stereotype.Component; /*@ServerEndpoint註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端, * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端 */ @ServerEndpoint("/websocket/{userID}") @Component public class WebSocketServer { //每一個客戶端都會有相應的session,服務端能夠發送相關消息 private Session session; //接收userID private Integer userID; //J.U.C包下線程安全的類,主要用來存放每一個客戶端對應的webSocket鏈接 private static CopyOnWriteArraySet<WebSocketServer> copyOnWriteArraySet = new CopyOnWriteArraySet<WebSocketServer>(); public Integer getUserID() { return userID; } /** * @Name:onOpen * @Description:打開鏈接。進入頁面後會自動發請求到此進行鏈接 * @Author:mYunYu * @Create Date:14:46 2018/11/15 * @Parameters:@PathParam("userID") Integer userID * @Return: */ @OnOpen public void onOpen(Session session, @PathParam("userID") Integer userID) { this.session = session; this.userID = userID; System.out.println(this.session.getId()); copyOnWriteArraySet.add(this); } /** * @Name:onClose * @Description:用戶關閉頁面,即關閉鏈接 * @Author:mYunYu * @Create Date:14:46 2018/11/15 * @Parameters: * @Return: */ @OnClose public void onClose() { copyOnWriteArraySet.remove(this); } /** * @Name:onMessage * @Description:測試客戶端發送消息,測試是否聯通 * @Author:mYunYu * @Create Date:14:46 2018/11/15 * @Parameters: * @Return: */ @OnMessage public void onMessage(String message) { System.out.println("websocket收到客戶端發來的消息:"+message); } /** * @Name:onError * @Description:出現錯誤 * @Author:mYunYu * @Create Date:14:46 2018/11/15 * @Parameters: * @Return: */ @OnError public void onError(Session session, Throwable error) { System.out.println("發生錯誤:" + error.getMessage() + "; sessionId:" + session.getId()); error.printStackTrace(); } public void sendMessage(Object object){ //遍歷客戶端 for (WebSocketServer webSocket : copyOnWriteArraySet) { System.out.println("websocket廣播消息:" + object.toString()); try { //服務器主動推送 webSocket.session.getBasicRemote().sendObject(object) ; } catch (Exception e) { e.printStackTrace(); } } } /** * @Name:sendMessage * @Description:用於發送給客戶端消息(羣發) * @Author:mYunYu * @Create Date:14:46 2018/11/15 * @Parameters: * @Return: */ public void sendMessage(String message) { //遍歷客戶端 for (WebSocketServer webSocket : copyOnWriteArraySet) { System.out.println("websocket廣播消息:" + message); try { //服務器主動推送 webSocket.session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } /** * @throws Exception * @Name:sendMessage * @Description:用於發送給指定客戶端消息 * @Author:mYunYu * @Create Date:14:47 2018/11/15 * @Parameters: * @Return: */ public void sendMessage(Integer userID, String message) throws Exception { Session session = null; WebSocketServer tempWebSocket = null; for (WebSocketServer webSocket : copyOnWriteArraySet) { if (webSocket.getUserID() == userID) { tempWebSocket = webSocket; session = webSocket.session; break; } } if (session != null) { //服務器主動推送 tempWebSocket.session.getBasicRemote().sendText(message); } else { System.out.println("沒有找到你指定ID的會話:{}"+ "; userId:" + userID); } } }