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\n");
44                     } else {
45                         log.error("FmSendJMSMgm Thread init failed, Program Exit!!!\n");
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 ------ 

 

posted @ 2020-09-08 20:18  天蝎座鹿  阅读(3038)  评论(4编辑  收藏  举报