• springboot使用websocket进行实时传送信息实战(包含服务端和客户端)


    1.背景


    公司对接告警信息平台,需要做一个服务,作为websocket客户端去接收传过来的信息,实时返回信息并对信息进行处理入库

    2.实现方案


    本来想用一个服务,对信息进行接收和处理。但是基于之前的经验,为了防止服务部署重启的时候丢失信息,改用两个服务:1.collcet接收服务,2.deal-send处理入库服务。collect服务作为websocket客户端,进行接收传过来的信息,并同时作为websocket服务端,传信息给deal-send服务;deal-send作为websocket客户端,接收collect传过来的信息,进行处理入库。

    3.代码实例


    • collect服务主要依赖

    pom.xml

     1         <dependency>
     2             <groupId>org.springframework.boot</groupId>
     3             <artifactId>spring-boot-starter-websocket</artifactId>
     4         </dependency>
     5 
     6         <!--websocket作为客户端-->
     7         <dependency>
     8             <groupId>org.java-websocket</groupId>
     9             <artifactId>Java-WebSocket</artifactId>
    10             <version>1.4.0</version>
    11         </dependency>
    12 
    13         <!--使用Spring RetryTemplate进行重试-->
    14         <dependency>
    15             <groupId>org.springframework.retry</groupId>
    16             <artifactId>spring-retry</artifactId>
    17             <version>1.2.4.RELEASE</version>
    18         </dependency>
    • deal-send服务主要依赖

    pom.xml

    1         <!--websocket作为客户端-->
    2         <dependency>
    3             <groupId>org.java-websocket</groupId>
    4             <artifactId>Java-WebSocket</artifactId>
    5             <version>1.4.0</version>
    6         </dependency>
    •  collect配置类

    WebSocketConfig.java

     1 package com.newland.auto.pilot.collect.config;
     2 
     3 import org.springframework.context.annotation.Bean;
     4 import org.springframework.context.annotation.Configuration;
     5 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
     6 
     7 /**
     8  * @author:wk
     9  * @date:2020/6/10 开启WebSocket支持
    10  */
    11 
    12 @Configuration
    13 public class WebSocketConfig {
    14 
    15     @Bean
    16     public ServerEndpointExporter serverEndpointExporter() {
    17         return new ServerEndpointExporter();
    18     }
    19 
    20 }
    • collect服务websocket客户端,由于告警信息平台偶尔会断网,websocket的长连接就会被断开,所以在onClose方法里加入了spring-retry提供的重试方法,在断开websocket连接的时候会进行重连操作。onMessage方法里加了发送信息到deal-send服务的调用,在接收到告警信息平台的时候,会将信息发到deal-send服务。

    MyWebSocketCient.java

     1 package com.newland.auto.pilot.collect.config;
     2 
     3 import com.newland.auto.pilot.collect.ApplicationRunnerImpl;
     4 import lombok.extern.slf4j.Slf4j;
     5 import org.java_websocket.client.WebSocketClient;
     6 import org.java_websocket.handshake.ServerHandshake;
     7 import org.springframework.retry.RecoveryCallback;
     8 import org.springframework.retry.RetryCallback;
     9 import org.springframework.retry.RetryContext;
    10 import org.springframework.retry.policy.AlwaysRetryPolicy;
    11 import org.springframework.retry.support.RetryTemplate;
    12 
    13 import java.net.URI;
    14 import java.util.Map;
    15 
    16 /**
    17  * @author:wk
    18  * @date:2020/5/29
    19  */
    20 @Slf4j
    21 public class MyWebSocketClient extends WebSocketClient {
    22 
    23     public MyWebSocketClient(URI serverUri, Map<String, String> headers) {
    24         super(serverUri, headers);
    25     }
    26 
    27     @Override
    28     public void onOpen(ServerHandshake arg0) {
    29         log.info("------ MyWebSocket onOpen ------");
    30     }
    31 
    32     @Override
    33     public void onClose(int arg0, String arg1, boolean arg2) {
    34         log.info("------ MyWebSocket onClose ------{}", arg1);
    35         log.info("启用断开重连!");
    36 
    37         try {
    38             // Spring-retry提供了RetryOperations接口的实现类RetryTemplate
    39             RetryTemplate template = new RetryTemplate();
    40 
    41             // 一直重试策略
    42             AlwaysRetryPolicy policy = new AlwaysRetryPolicy();
    43 
    44             // 设置重试策略
    45             template.setRetryPolicy(policy);
    46 
    47             // 执行
    48             String result = template.execute(
    49                     new RetryCallback<String, Exception>() {
    50 
    51                         public String doWithRetry(RetryContext context) throws Exception {
    52                             log.info("------------------------执行关闭连接重试操作--------------------------");
    53                             // 调用连接websocket方法
    54                             ApplicationRunnerImpl.connectWebsocket();
    55 
    56                             return "------------------------重连成功--------------------------";
    57                         }
    58                     },
    59                     // 当重试执行完闭,操作还未成为,那么可以通过RecoveryCallback完成一些失败事后处理。
    60                     new RecoveryCallback<String>() {
    61                         public String recover(RetryContext context) throws Exception {
    62 
    63                             return "------------------------重试执行完闭,操作未完成!!--------------------------";
    64                         }
    65                     }
    66             );
    67             log.info(result);
    68         } catch (Exception ex) {
    69             log.error(ex.getMessage(), ex);
    70         }
    71     }
    72 
    73     @Override
    74     public void onError(Exception arg0) {
    75         log.info("------ MyWebSocket onError ------{}", arg0);
    76     }
    77 
    78     @Override
    79     public void onMessage(String arg0) {
    80         log.info("-------- 接收到服务端数据: " + arg0 + "--------");
    81         try {
    82             WebSocketServer.sendInfo(arg0, "deal-send");
    83         } catch (Exception e) {
    84             log.info("发送消息到deal-send客户端失败,信息:" + e.getMessage());
    85         }
    86     }
    87 }
    • collect服务的websocket服务端,提供给deal-send服务连接,sendInfo方法里加入对用户的监听,deal-send用户在线时发送信息,不在线时远程调用deal-send服务的重连接口,在deal-send服务的websocket客户端断开连接时进行主动调用,使deal-send服务的websocket重新连接上。

    WebSocketServer.java

      1 package com.newland.auto.pilot.collect.config;
      2 
      3 
      4 import lombok.extern.slf4j.Slf4j;
      5 import org.apache.commons.lang3.StringUtils;
      6 import org.springframework.http.ResponseEntity;
      7 import org.springframework.stereotype.Component;
      8 import org.springframework.web.client.RestTemplate;
      9 
     10 import javax.websocket.*;
     11 import javax.websocket.server.PathParam;
     12 import javax.websocket.server.ServerEndpoint;
     13 import java.io.IOException;
     14 import java.util.concurrent.ConcurrentHashMap;
     15 import java.util.concurrent.atomic.LongAdder;
     16 
     17 /**
     18  * @author:wk
     19  * @date:2020/6/10
     20  */
     21 @Slf4j
     22 @ServerEndpoint("/server/ws/{userId}")
     23 @Component
     24 public class WebSocketServer {
     25 
     26     /**
     27      * LongAdder记录当前在线连接数,线程安全。
     28      */
     29     LongAdder onlineCounter = new LongAdder();
     30     /**
     31      * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     32      */
     33     private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
     34     /**
     35      * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     36      */
     37     private Session session;
     38     /**
     39      * 接收userId
     40      */
     41     private String userId = "";
     42     /**
     43      * 调用deal-send重连接口的uri
     44      */
     45     static String reConnectWebsocketUri = "http://localhost:9042/newland/reConnectWebsocket";
     46 
     47 
     48     /**
     49      * 连接建立成功调用的方法
     50      */
     51     @OnOpen
     52     public void onOpen(Session session, @PathParam("userId") String userId) {
     53         this.session = session;
     54         log.info("onopen,this.session:" + session);
     55         this.userId = userId;
     56         if (webSocketMap.containsKey(userId)) {
     57             webSocketMap.remove(userId);
     58             webSocketMap.put(userId, this);
     59             
     60         } else {
     61             webSocketMap.put(userId, this);
     62             
     63             onlineCounter.increment();
     64             //在线数加1
     65         }
     66 
     67         log.info("用户连接:" + userId + ",当前在线人数为:" + onlineCounter.sum());
     68 
     69         try {
     70             sendMessage("连接成功");
     71         } catch (IOException e) {
     72             log.error("用户:" + userId + ",网络异常!!!!!!");
     73         }
     74     }
     75 
     76     /**
     77      * 连接关闭调用的方法
     78      */
     79     @OnClose
     80     public void onClose() {
     81         if (webSocketMap.containsKey(userId)) {
     82             webSocketMap.remove(userId);
     83             //从set中删除
     84             onlineCounter.decrement();
     85         }
     86         log.info("用户退出:" + userId + ",当前在线人数为:" + onlineCounter.sum());
     87     }
     88 
     89     /**
     90      * 收到客户端消息后调用的方法
     91      *
     92      * @param message 客户端发送过来的消息
     93      */
     94     @OnMessage
     95     public void onMessage(String message, Session session) {
     96         log.info("用户消息:" + userId + ",报文:" + message);
     97     }
     98 
     99     /**
    100      * @param session
    101      * @param error
    102      */
    103     @OnError
    104     public void onError(Session session, Throwable error) {
    105         log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
    106     }  
    107 108 
    109     /**
    110      * 实现服务器主动推送
    111      */
    112     public void sendMessage(String message) throws IOException {
    113         this.session.getBasicRemote().sendText(message);
    114     }
    115 
    116 
    117     /**
    118      * 发送自定义消息
    119      */
    120     public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
    121         log.info("发送消息到:" + userId);
    122         if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
    123             webSocketMap.get(userId).sendMessage(message);
    124         } else {
    125             log.error("用户:" + userId + ",不在线!");
    126             log.info("---------开始远程调用重连websocket接口----------");
    127             RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory());
    128             ResponseEntity<String> response = restTemplate.getForEntity(reConnectWebsocketUri, String.class);
    129 
    130             log.info("执行请求后,返回response body:" + response.getBody());
    131             // 判断返回状态是否为200
    132             if (response.getStatusCodeValue() == 200) {
    133                 log.info("返回状态为200!");
    134                 // 解析响应体
    135                 String content = response.getBody();
    136                 log.info(content);
    137             } else {
    138                 log.info("返回状态为" + response.getStatusCodeValue() + "!");
    139             }
    140         }
    141     }
    142 
    143 
    144 }
    • deal-send服务的websocket客户端连接

    WebSocketConfig.java

     1 package com.newland.auto.pilot.deal.send.config;
     2 
     3 import com.alibaba.fastjson.JSON;
     4 import com.newland.auto.pilot.deal.send.alarmDeal.AlarmConvert;
     5 import com.newland.auto.pilot.deal.send.jms.FmSendJMSMgm;
     6 import com.newland.privatefm.alarmSend.AlarmMsg;
     7 import com.newland.privatefm.alarmSend.SendManager;
     8 import lombok.extern.slf4j.Slf4j;
     9 import org.apache.commons.lang3.StringUtils;
    10 import org.java_websocket.client.WebSocketClient;
    11 import org.java_websocket.drafts.Draft_6455;
    12 import org.java_websocket.handshake.ServerHandshake;
    13 import org.springframework.beans.factory.annotation.Autowired;
    14 import org.springframework.context.annotation.Bean;
    15 import org.springframework.stereotype.Component;
    16 
    17 import java.net.URI;
    18 import java.util.List;
    19 
    20 /**
    21  * @author:wk
    22  * @date:2020/6/12
    23  * @Description: 配置websocket客户端
    24  */
    25 
    26 @Slf4j
    27 @Component
    28 public class WebSocketConfig {
    29     @Autowired
    30     private AlarmConvert alarmConvert;
    31 
    32     @Bean
    33     public WebSocketClient webSocketClient() {
    34         try {
    35             WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://localhost:9041" + "/server/ws/deal-send"), new Draft_6455()) {
    36                 @Override
    37                 public void onOpen(ServerHandshake handshakedata) {
    38                     log.info("------ MyWebSocket onOpen ------");
    39                     log.info("FmSendJMSMgm Thread Begin ...");
    40                     FmSendJMSMgm sendJMSMgm = new FmSendJMSMgm();
    41                     if (sendJMSMgm.init()) {
    42                         sendJMSMgm.start();
    43                         log.info("FmSendJMSMgm Thread End
    ");
    44                     } else {
    45                         log.error("FmSendJMSMgm Thread init failed, Program Exit!!!
    ");
    46                         System.exit(0);
    47                     }
    48                 }
    49 
    50                 @Override
    51                 public void onMessage(String message) {
    52                     log.info("-------- 接收到服务端数据: " + message + "--------");
    53 
    54                     if (StringUtils.isNotBlank(message)) {
    55                         try {
    56                             //解析发送的报文
    57                             List<AlarmMsg> alarmMsgList = alarmConvert.alarmMsgConvert(message);
    58                             alarmMsgList.parallelStream().forEach(alarmMsg -> {
    59    
    60 //Jms发送数据 61 log.info("Jms发送数据:" + JSON.toJSONString(alarmMsg)); 62 SendManager.sendManager.putData(alarmMsg); 63 log.info("Jms发送数据成功!"); 64 }); 65 } catch (Exception e) { 66 log.error(e.getMessage()); 67 } 68 } 69 70 } 71 72 @Override 73 public void onClose(int code, String reason, boolean remote) { 74 log.info("------ MyWebSocket onClose ------{}", reason); 75 } 76 77 @Override 78 public void onError(Exception ex) { 79 log.info("------ MyWebSocket onError ------{}", ex); 80 } 81 }; 82 webSocketClient.connect(); 83 return webSocketClient; 84 } catch (Exception e) { 85 log.error(e.getMessage, e); 86 } 87 return null; 88 } 89 90 }
    • deal-send服务的远程重连接口

    ReConnectWsController.java

     1 package com.newland.auto.pilot.deal.send.controller;
     2 
     3 import com.newland.auto.pilot.deal.send.config.WebSocketConfig;
     4 import lombok.extern.slf4j.Slf4j;
     5 import org.springframework.beans.factory.annotation.Autowired;
     6 import org.springframework.stereotype.Controller;
     7 import org.springframework.web.bind.annotation.GetMapping;
     8 import org.springframework.web.bind.annotation.RequestMapping;
     9 import org.springframework.web.bind.annotation.ResponseBody;
    10 
    11 /**
    12  * @author:wk
    13  * @date:2020/9/4 调用方法进行重连websocket
    14  */
    15 
    16 @Slf4j
    17 @Controller
    18 @ResponseBody
    19 @RequestMapping(value = "/newland")
    20 public class ReConnectWsController {
    21     @Autowired
    22     WebSocketConfig webSocketConfig;
    23 
    24     @GetMapping("/reConnectWebsocket")
    25     public String reConnectWebsocket() throws Exception {
    26         String returnMsg = "---------调用重连websocket接口成功-----------";
    27 
    28         log.info("---------开始调用重连websocket接口----------");
    29         webSocketConfig.webSocketClient().reconnect();
    30         log.info(returnMsg);
    31         return returnMsg;
    32     }
    33 }
    • collect服务websocket客户端重连日志
    [2020-09-05 11:25:18] 126481 [WebSocketTimer] INFO -c.n.a.p.c.config.MyWebSocketClient[36] - ------ MyWebSocket onClose ------The connection was closed because the other endpoint did not respond with a pong in time. For more information check: https://github.com/TooTallNate/Java-WebSocket/wiki/Lost-connection-detection 
    [2020-09-05 11:25:18] 126485 [WebSocketTimer] INFO -c.n.a.p.c.config.MyWebSocketClient[37] - 启用断开重连! 
    [2020-09-05 11:25:18] 126501 [WebSocketTimer] INFO -c.n.a.p.c.config.MyWebSocketClient[54] - ------------------------执行关闭连接重试操作-------------------------- 
    [2020-09-05 11:25:18] 126505 [WebSocketTimer] INFO -c.n.a.p.c.collect.TokenCollect[31] - 开始执行请求! 
    [2020-09-05 11:25:18] 126506 [WebSocketTimer] INFO -c.n.a.p.c.collect.TokenCollect[39] - 开始使用restTemplate.exchange!! 
    [2020-09-05 11:25:18] 126705 [WebSocketTimer] INFO -c.n.a.p.c.collect.TokenCollect[41] - 执行请求后,返回response body:{"accessSession":"x-xxxxxxxxxxx","roaRand":"0xxxxxxxxxx","expires":1800,"additionalInfo":null} 
    [2020-09-05 11:25:18] 126706 [WebSocketTimer] INFO -c.n.a.p.c.collect.TokenCollect[44] - 返回状态为200! 
    [2020-09-05 11:25:18] 126706 [WebSocketTimer] INFO -c.n.a.p.c.collect.TokenCollect[47] - {"accessSession":"x-xxxxxxxxxxx","roaRand":"0xxxxxxxxx","expires":1800,"additionalInfo":null} 
    [2020-09-05 11:25:18] 126706 [WebSocketTimer] INFO -c.n.a.p.c.collect.TokenCollect[50] - accessToken:x-xxxxxuxxxxxx
    [2020-09-05 11:25:18] 126711 [WebSocketTimer] INFO -c.n.a.p.c.collect.SubscribeCollect[46] - 开始执行请求! 
    [2020-09-05 11:25:18] 126917 [WebSocketTimer] INFO -c.n.a.p.c.collect.SubscribeCollect[49] - 执行请求后,返回response body:{"output":{"subscription-result":"ok","identifier":"9xxxxxxxx","url":"/restconf/streams/ws/v1/identifier/34xxxxx"}} 
    [2020-09-05 11:25:18] 126917 [WebSocketTimer] INFO -c.n.a.p.c.collect.SubscribeCollect[52] - 返回状态为200! 
    [2020-09-05 11:25:18] 126917 [WebSocketTimer] INFO -c.n.a.p.c.collect.SubscribeCollect[55] - {"output":{"subscription-result":"ok","identifier":"95xxxxxxx","url":"/restconf/streams/ws/v1/identifier/34xxxxxxx"}} 
    [2020-09-05 11:25:18] 126918 [WebSocketTimer] INFO -c.n.a.p.c.collect.SubscribeCollect[59] - url:/restconf/streams/ws/v1/identifier/34xxxxxxx 
    [2020-09-05 11:25:18] 126918 [WebSocketTimer] INFO -c.n.a.p.c.ApplicationRunnerImpl[58] - protocol:TLSv1.2 
    [2020-09-05 11:25:18] 126919 [WebSocketTimer] INFO -c.n.a.p.c.config.MyWebSocketClient[69] - ------------------------重连成功-------------------------- 
    [2020-09-05 11:25:19] 127069 [WebSocketConnectReadThread-41] INFO -c.n.a.p.c.config.MyWebSocketClient[30] - ------ MyWebSocket onOpen ------ 
    [2020-09-05 11:25:25] 133139 [WebSocketConnectReadThread-41] INFO -c.n.a.p.c.config.MyWebSocketClient[84] - -------- 接收到服务端数据: {"csn":111111,"name":"测试name","priority":"medium","occur-time":"2020-09-05T03:23:22Z","clear-time":"2020-09-05T03:26:31Z","status":"status1","category":"Power environment","domain":"domain1","source-objects":[{"id":"","type":"type1","name":"test","extend-id":"123456","device-type":"34"}],"detail":"detail1","message-type":"1","root-event-csns":[{"csn":"111","type":"1"}]}-------- 
    [2020-09-05 11:25:25] 133139 [WebSocketConnectReadThread-41] INFO -c.n.a.p.c.config.WebSocketServer[136] - 发送消息到:deal-send 
    • collect远程调用重连websocket接口日志
    [2020-09-05 11:23:33] 21128 [WebSocketConnectReadThread-33] INFO -c.n.a.p.c.config.WebSocketServer[136] - 发送消息到:deal-send 
    [2020-09-05 11:23:33] 21137 [WebSocketConnectReadThread-33] ERROR-c.n.a.p.c.config.WebSocketServer[140] - 用户:deal-send,不在线! 
    [2020-09-05 11:23:33] 21138 [WebSocketConnectReadThread-33] INFO -c.n.a.p.c.config.WebSocketServer[141] - ---------开始远程调用重连websocket接口---------- 
    [2020-09-05 11:23:33] 21573 [http-nio-9041-exec-1] INFO -o.a.c.c.C.[Tomcat].[localhost].[/][173] - Initializing Spring DispatcherServlet 'dispatcherServlet' 
    [2020-09-05 11:23:33] 21574 [http-nio-9041-exec-1] INFO -o.s.web.servlet.DispatcherServlet[524] - Initializing Servlet 'dispatcherServlet' 
    [2020-09-05 11:23:33] 21588 [http-nio-9041-exec-1] INFO -o.s.web.servlet.DispatcherServlet[546] - Completed initialization in 14 ms 
    [2020-09-05 11:23:33] 21649 [http-nio-9041-exec-1] INFO -c.n.a.p.c.config.WebSocketServer[49] - onopen,this.session:org.apache.tomcat.websocket.WsSession@32f5b990 
    [2020-09-05 11:23:33] 21650 [http-nio-9041-exec-1] INFO -c.n.a.p.c.config.WebSocketServer[62] - 用户连接:deal-send,当前在线人数为:1 
    • deal-send服务被远程调用重连websocket接口日志
    [2020-09-05 11:23:08] 87821 [WebSocketConnectReadThread-19] INFO -c.n.a.p.d.s.config.WebSocketConfig[74] - ------ MyWebSocket onClose ------ 
    [2020-09-05 11:23:33] 112532 [http-nio-9042-exec-1] INFO -o.a.c.c.C.[Tomcat].[localhost].[/][173] - Initializing Spring DispatcherServlet 'dispatcherServlet' 
    [2020-09-05 11:23:33] 112533 [http-nio-9042-exec-1] INFO -o.s.web.servlet.DispatcherServlet[524] - Initializing Servlet 'dispatcherServlet' 
    [2020-09-05 11:23:33] 112556 [http-nio-9042-exec-1] INFO -o.s.web.servlet.DispatcherServlet[546] - Completed initialization in 23 ms 
    [2020-09-05 11:23:33] 112630 [http-nio-9042-exec-1] INFO -c.n.a.p.d.s.c.ReConnectWsController[28] - ---------开始调用重连websocket接口---------- 
    [2020-09-05 11:23:33] 112828 [WebSocketConnectReadThread-43] INFO -c.n.a.p.d.s.config.WebSocketConfig[38] - ------ MyWebSocket onOpen ------ 
  • 相关阅读:
    jquery的each()详细介绍【转】
    牛客-小w的a=b问题
    HDU-6707-Shuffle Card(很数据结构的一道题)
    HDU-6672-Seq
    牛客-随机数
    牛客-小阳的贝壳
    HDU-4417-Super Mario
    牛客-Corn Fields
    HDU-2665-Kth number
    线段树模板
  • 原文地址:https://www.cnblogs.com/kwblog/p/13631407.html
Copyright © 2020-2023  润新知