websocket服务器推送

1.1 服务器推送

WebSocket作为一种通信协议,属于服务器推送技术的一种,IE10+支持。

服务器推送技术不止一种,有短轮询、长轮询、WebSocket、Server-sent Events(SSE)等,他们各有优缺点

#短轮询长轮询Websocketsse
通讯方式 http http 基于TCP长连接通讯 http
触发方式 轮询 轮询 事件 事件
优点 兼容性好容错性强,实现简单 比短轮询节约资源 全双工通讯协议,性能开销小、安全性高,有一定可扩展性 实现简便,开发成本低
缺点 安全性差,占较多的内存资源与请求数 安全性差,占较多的内存资源与请求数 传输数据需要进行二次解析,增加开发成本及难度 只适用高级浏览器
适用范围 b/s服务 b/s服务 网络游戏、银行交互和支付 服务端到客户端单向推送

短轮询最简单,在一些简单的场景也会经常使用,就是隔一段时间就发起一个ajax请求。那么长轮询是什么呢?

长轮询(Long Polling)是在Ajax轮询基础上做的一些改进,在没有更新的时候不再返回空响应,而且把连接保持到有更新的时候,客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。它是一个解决方案,但不是最佳的技术方案。

如果说短轮询是客户端不断打电话问服务端有没有消息,服务端回复后立刻挂断,等待下次再打;长轮询是客户端一直打电话,服务端接到电话不挂断,有消息的时候再回复客户端并挂断。

SSE(Server-Sent Events)与长轮询机制类似,区别是每个连接不只发送一个消息。客户端发送一个请求,服务端保持这个连接直到有新消息发送回客户端,仍然保持着连接,这样连接就可以支持消息的再次发送,由服务器单向发送给客户端。然而IE直到11都不支持,不多说了....

1.2 WebSocket的特点

为什么已经有了轮询还要WebSocket呢,是因为短轮询和长轮询有个缺陷:通信只能由客户端发起。

那么如果后端想往前端推送消息需要前端去轮询,不断查询后端是否有新消息,而轮询的效率低且浪费资源(必须不停 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

posted @ 2018-05-23 11:45  shown  阅读(6364)  评论(0编辑  收藏  举报