websocket服務器推送

1.1 服務器推送

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

1.2 WebSocket的特色

爲何已經有了輪詢還要WebSocket呢,是由於短輪詢和長輪詢有個缺陷:通訊只能由客戶端發起。spring

那麼若是後端想往前端推送消息須要前端去輪詢,不斷查詢後端是否有新消息,而輪詢的效率低且浪費資源(必須不停 setInterval 或 setTimeout 去鏈接,或者 HTTP 鏈接始終打開),WebSocket提供了一個文明優雅的全雙工通訊方案。通常適合於對數據的實時性要求比較強的場景,如通訊、股票、直播、共享桌面,特別適合於客戶端與服務頻繁交互的狀況下,如聊天室、實時共享、多人協做等平臺。後端

特色

  1. 創建在 TCP 協議之上,服務器端的實現比較容易。
  2. 與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容易屏蔽,能經過各類 HTTP 代理服務器。
  3. 數據格式比較輕量,性能開銷小,通訊高效。服務器與客戶端之間交換的標頭信息大概只有2字節;
  4. 能夠發送文本,也能夠發送二進制數據。
  5. 沒有同源限制,客戶端能夠與任意服務器通訊。
  6. 協議標識符是ws(若是加密,則爲wss),服務器網址就是 URL。ex:ws://example.com:80/some/path
  7. 不用頻繁建立及銷燬TCP請求,減小網絡帶寬資源的佔用,同時也節省服務器資源;
  8. WebSocket是純事件驅動的,一旦鏈接創建,經過監聽事件能夠處理到來的數據和改變的鏈接狀態,數據都以幀序列的形式傳輸。服務端發送數據後,消息和事件會異步到達。
  9. 無超時處理。

HTTP與WS協議結構

WebSocket協議標識符用ws表示。`wss協議表示加密的WebSocket協議,對應HTTPs協議。結構以下:瀏覽器

  • HTTP: TCP > HTTP
  • HTTPS: TCP > TLS > HTTP
  • WS: TCP > WS
  • WSS: TCP > TLS > WS

如下是簡單代碼記錄,親測可用,基於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 }
MyHandler
 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 }
SocketController
 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 }
WebSocketConfig
 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 }
WebSocketInterceptor
 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>
jsp腳本頁面

 備註:http://ip:port/activity 是項目根目錄。

參考連接地址:

https://blog.csdn.net/u014520745/article/details/62046396

https://www.cnblogs.com/interdrp/p/7903736.html

相關文章
相關標籤/搜索