WebSocket做爲一種通訊協議,屬於服務器推送技術的一種,IE10+支持。html
服務器推送技術不止一種,有短輪詢、長輪詢、WebSocket、Server-sent Events(SSE)等,他們各有優缺點:前端
# | 短輪詢 | 長輪詢 | Websocket | sse |
---|---|---|---|---|
通信方式 | http | http | 基於TCP長鏈接通信 | http |
觸發方式 | 輪詢 | 輪詢 | 事件 | 事件 |
優勢 | 兼容性好容錯性強,實現簡單 | 比短輪詢節約資源 | 全雙工通信協議,性能開銷小、安全性高,有必定可擴展性 | 實現簡便,開發成本低 |
缺點 | 安全性差,佔較多的內存資源與請求數 | 安全性差,佔較多的內存資源與請求數 | 傳輸數據須要進行二次解析,增長開發成本及難度 | 只適用高級瀏覽器 |
適用範圍 | b/s服務 | b/s服務 | 網絡遊戲、銀行交互和支付 | 服務端到客戶端單向推送 |
短輪詢最簡單,在一些簡單的場景也會常用,就是隔一段時間就發起一個ajax請求。那麼長輪詢是什麼呢?java
長輪詢(Long Polling)是在Ajax輪詢基礎上作的一些改進,在沒有更新的時候再也不返回空響應,並且把鏈接保持到有更新的時候,客戶端向服務器發送Ajax請求,服務器接到請求後hold住鏈接,直到有新消息才返回響應信息並關閉鏈接,客戶端處理完響應信息後再向服務器發送新的請求。它是一個解決方案,但不是最佳的技術方案。jquery
若是說短輪詢是客戶端不斷打電話問服務端有沒有消息,服務端回覆後馬上掛斷,等待下次再打;長輪詢是客戶端一直打電話,服務端接到電話不掛斷,有消息的時候再回復客戶端並掛斷。web
SSE(Server-Sent Events)與長輪詢機制相似,區別是每一個鏈接不僅發送一個消息。客戶端發送一個請求,服務端保持這個鏈接直到有新消息發送回客戶端,仍然保持着鏈接,這樣鏈接就能夠支持消息的再次發送,由服務器單向發送給客戶端。然而IE直到11都不支持,很少說了....ajax
爲何已經有了輪詢還要WebSocket呢,是由於短輪詢和長輪詢有個缺陷:通訊只能由客戶端發起。spring
那麼若是後端想往前端推送消息須要前端去輪詢,不斷查詢後端是否有新消息,而輪詢的效率低且浪費資源(必須不停 setInterval 或 setTimeout 去鏈接,或者 HTTP 鏈接始終打開),WebSocket提供了一個文明優雅的全雙工通訊方案。通常適合於對數據的實時性要求比較強的場景,如通訊、股票、直播、共享桌面,特別適合於客戶端與服務頻繁交互的狀況下,如聊天室、實時共享、多人協做等平臺。後端
ws
(若是加密,則爲wss),服務器網址就是 URL。ex:ws://example.com:80/some/path
WebSocket協議標識符用ws
表示。`wss協議表示加密的WebSocket協議,對應HTTPs協議。結構以下:瀏覽器
如下是簡單代碼記錄,親測可用,基於springMVC的服務器推送技術websocket。安全
1.gradle構建,websocket引入的包:"org.springframework:spring-websocket:4.2.4.RELEASE"
2.context.xml配置須要加的地方
xmlns:websocket="http://www.springframework.org/schema/websocket"
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket-4.1.xsd
3.websocket的配置類
4.websocke接受消息以及處理的類
5.頁面js代碼
6.簡單的運行效果
涉及到的代碼就這幾個,拷貝到springmvc項目裏就能夠。
1 package com.kitty.activity.common.websocket; 2 3 import org.springframework.stereotype.Service; 4 import org.springframework.web.socket.CloseStatus; 5 import org.springframework.web.socket.TextMessage; 6 import org.springframework.web.socket.WebSocketMessage; 7 import org.springframework.web.socket.WebSocketSession; 8 import org.springframework.web.socket.handler.TextWebSocketHandler; 9 10 import java.io.IOException; 11 import java.util.HashMap; 12 import java.util.Map; 13 import java.util.Set; 14 15 /** 16 * Created by liuxn on 2018/5/22 0022. 17 */ 18 @Service 19 public class MyHandler extends TextWebSocketHandler { 20 //在線用戶列表 21 private static final Map<Integer, WebSocketSession> users; 22 //用戶標識 23 private static final String CLIENT_ID = "userId"; 24 25 static { 26 users = new HashMap<>(); 27 } 28 29 @Override 30 public void afterConnectionEstablished(WebSocketSession session) throws Exception { 31 System.out.println("成功創建鏈接"); 32 Integer userId = getClientId(session); 33 if (userId != null) { 34 users.put(userId, session); 35 session.sendMessage(new TextMessage("你已成功創建socket鏈接")); 36 System.out.println(userId); 37 System.out.println(session); 38 } 39 } 40 41 @Override 42 public void handleTextMessage(WebSocketSession session, TextMessage message) { 43 // ... 44 System.out.println("收到客戶端消息:"+message.getPayload()); 45 46 WebSocketMessage message1 = new TextMessage("server:"+message); 47 try { 48 session.sendMessage(message1); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 } 52 } 53 54 @Override 55 public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { 56 if (session.isOpen()) { 57 session.close(); 58 } 59 System.out.println("鏈接出錯"); 60 users.remove(getClientId(session)); 61 } 62 63 @Override 64 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { 65 System.out.println("鏈接已關閉:" + status); 66 users.remove(getClientId(session)); 67 } 68 69 /** 70 * 發送信息給指定用戶 71 * @param clientId 72 * @param message 73 * @return 74 */ 75 public boolean sendMessageToUser(Integer clientId, TextMessage message) { 76 if (users.get(clientId) == null) return false; 77 WebSocketSession session = users.get(clientId); 78 System.out.println("sendMessage:" + session+",msg:"+message.getPayload()); 79 if (!session.isOpen()) return false; 80 try { 81 session.sendMessage(message); 82 } catch (IOException e) { 83 e.printStackTrace(); 84 return false; 85 } 86 return true; 87 } 88 89 /** 90 * 廣播信息 91 * @param message 92 * @return 93 */ 94 public boolean sendMessageToAllUsers(TextMessage message) { 95 boolean allSendSuccess = true; 96 Set<Integer> clientIds = users.keySet(); 97 WebSocketSession session = null; 98 for (Integer clientId : clientIds) { 99 try { 100 session = users.get(clientId); 101 if (session.isOpen()) { 102 session.sendMessage(message); 103 } 104 } catch (IOException e) { 105 e.printStackTrace(); 106 allSendSuccess = false; 107 } 108 } 109 110 return allSendSuccess; 111 } 112 113 @Override 114 public boolean supportsPartialMessages() { 115 return false; 116 } 117 118 /** 119 * 獲取用戶標識 120 * @param session 121 * @return 122 */ 123 private Integer getClientId(WebSocketSession session) { 124 try { 125 Integer clientId = (Integer) session.getAttributes().get(CLIENT_ID); 126 return clientId; 127 } catch (Exception e) { 128 return null; 129 } 130 } 131 }
1 package com.kitty.activity.common.websocket; 2 3 import org.springframework.beans.factory.annotation.Autowired; 4 import org.springframework.stereotype.Controller; 5 import org.springframework.web.bind.annotation.PathVariable; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.ResponseBody; 8 import org.springframework.web.servlet.ModelAndView; 9 import org.springframework.web.socket.TextMessage; 10 11 import javax.servlet.http.HttpSession; 12 13 14 @Controller 15 public class SocketController { 16 17 @Autowired 18 MyHandler handler; 19 20 //玩家登陸 21 @RequestMapping("/external/login/{userId}") 22 public ModelAndView login(HttpSession session, @PathVariable("userId") Integer userId) { 23 System.out.println("登陸接口,userId=" + userId); 24 session.setAttribute("userId", userId); 25 System.out.println(session.getAttribute("userId")); 26 27 return new ModelAndView("phone/websocket_test"); 28 } 29 30 //模擬給指定玩家發消息 31 @RequestMapping("/external/message") 32 @ResponseBody 33 public String sendMessage(Integer userId,String message) { 34 boolean hasSend = handler.sendMessageToUser(userId, new TextMessage(message)); 35 System.out.println(hasSend); 36 return "success"; 37 } 38 39 40 //模擬給全部玩家發消息 41 @RequestMapping("/external/message/all") 42 @ResponseBody 43 public String sendAll(String message) { 44 boolean hasSend = handler.sendMessageToAllUsers(new TextMessage(message)); 45 System.out.println(hasSend); 46 return "success"; 47 } 48 49 }
1 package com.kitty.activity.common.websocket; 2 3 import org.springframework.context.annotation.Bean; 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.web.socket.WebSocketHandler; 6 import org.springframework.web.socket.config.annotation.EnableWebSocket; 7 import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 8 import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 9 10 11 @Configuration 12 @EnableWebSocket 13 public class WebSocketConfig implements WebSocketConfigurer { 14 15 @Override 16 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 17 registry.addHandler(myHandler(), "/external/myHandler").addInterceptors(new WebSocketInterceptor()); 18 } 19 20 @Bean 21 public WebSocketHandler myHandler() { 22 return new MyHandler(); 23 } 24 }
1 package com.kitty.activity.common.websocket; 2 3 import org.springframework.http.server.ServerHttpRequest; 4 import org.springframework.http.server.ServerHttpResponse; 5 import org.springframework.http.server.ServletServerHttpRequest; 6 import org.springframework.web.socket.WebSocketHandler; 7 import org.springframework.web.socket.server.HandshakeInterceptor; 8 9 import javax.servlet.http.HttpSession; 10 import java.util.Map; 11 12 /** 13 * 14 */ 15 public class WebSocketInterceptor implements HandshakeInterceptor { 16 17 @Override 18 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Map<String, Object> map) throws Exception { 19 if (request instanceof ServletServerHttpRequest) { 20 System.out.println("*****beforeHandshake******"); 21 ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; 22 HttpSession session = serverHttpRequest.getServletRequest().getSession(); 23 // Map parameterMap = serverHttpRequest.getServletRequest().getParameterMap(); 24 // System.out.println(parameterMap); 25 if (session != null) { 26 map.put("userId", session.getAttribute("userId")); 27 } 28 29 } 30 return true; 31 } 32 33 @Override 34 public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { 35 System.out.println("******afterHandshake******"); 36 } 37 }
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 3 <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 4 <c:set var="ctx" value="${pageContext.request.contextPath}"/> 5 <html> 6 <head> 7 <meta charset="utf-8"> 8 <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport"> 9 <meta content="yes" name="apple-mobile-web-app-capable"> 10 <meta content="black" name="apple-mobile-web-app-status-bar-style"> 11 <meta content="telephone=no" name="format-detection"> 12 <meta content="email=no" name="format-detection"> 13 <meta name="baseUrl" content="${ctx}"/> 14 <meta name="roleId" content="${roleId}"/> 15 <title>websocket</title> 16 <script src="${ctx}/assets/js/jquery.min.js"></script> 17 <script src="http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js"></script> 18 19 </head> 20 21 <body> 22 <div class="act-outline" width="100%"> 23 it is work. 24 </div> 25 <script> 26 //websocket 27 $(function () { 28 var baseUrl = $('meta[name="baseUrl"]').attr("content"); 29 //判斷當前瀏覽器是否支持WebSocket 30 var websocket; 31 if ('WebSocket' in window) { 32 websocket = new WebSocket('ws://' + window.location.host + '/activity/external/myHandler'); 33 // var ws = new WebSocket('ws://192.168.3.26:8999/activity/external/myHandler') //也能夠指定ip 34 } else if ('MozWebSocket' in window) { 35 websocket = new MozWebSocket("ws://" + window.location.host + '/activity/external/myHandler'); //未測試 36 } else { 37 websocket = new SockJS("http://" + window.location.host + '/activity/external/myHandler'); //未測試 38 } 39 40 websocket.onopen = function () { 41 console.log("正在打開鏈接,準備發消息給服務器..."); 42 websocket.send("{text:hello}"); 43 } 44 websocket.onclose = function () { 45 console.log("服務器關閉鏈接:onclose"); 46 } 47 48 websocket.onmessage = function (msg) { 49 console.log("收到服務器推送數據:"+msg.data); 50 } 51 52 53 }) 54 </script> 55 </body> 56 </html>
備註:http://ip:port/activity 是項目根目錄。
參考連接地址:
https://blog.csdn.net/u014520745/article/details/62046396
https://www.cnblogs.com/interdrp/p/7903736.html