• 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

  • 相关阅读:
    测试人员如何甩锅
    测试用例,写不写?
    软件测试很简单么?
    体系认证、白皮书与行业大会
    对话云层
    专项测试怎样才“好玩”
    k8s环境 构建 工具 kubeadmin 似于openstack自动化工具kollaansible等
    git clone 带入username passed
    数据库ACID REDIS ACID实现
    jforg devops 持续集成交付 软件交付流水线 知识点大方向汇总备忘
  • 原文地址:https://www.cnblogs.com/shown/p/9076113.html
Copyright © 2020-2023  润新知