使用webSocket实现及时通信

骑猪看日落 2022-05-29 23:44 549阅读 0赞

本方案是为了解决前后台及时通信设计的,例如后台代码触发一个事件,可以及时的传递给前台,也就是消息的推送功能.
关于消息的推送,方案1是使用定时任务,Cron表达式设置每分钟处理一下后台逻辑进行事件的判断.方案2是使用webSocket建立消息通信通道,挂起一个线程进行时间的判断和消息推送.虽然都能实现消息推送的功能,但是方案二明显效率更高,对服务器造成的压力相对于方案1来说也更小,这里就简单介绍下使用第二种方案进行消息的及时推送实现方法.
首先说下使用webSocket进行访问是路径格式:ws:// path + “wsMy?jspCode=” + jspCode
以WS开头,后面可以追加参数,上文路径是中的wsMy是为了接下来的过滤器过滤,防止恶意访问.
首先添加webSocket依赖:

  1. <!--spring-websocket-->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-websocket</artifactId>
  5. <version>4.0.1.RELEASE</version>
  6. </dependency>

接下来就是前台页面上的触发访问webSocket:

  1. function getUserId() {
  2. $.ajax({
  3. type : "post",
  4. url : "${ctx}/memo/getCurrentUserId",
  5. beforeSend: function () {
  6. //加载中
  7. waitload();
  8. },
  9. success : function(data) {
  10. currentUserId = data;
  11. closewait();
  12. //动态获取当前系统服务器IP地址
  13. //若执行成功的话,则隐藏进度条提示
  14. //${ctx}为定义的项目名称,此处不展示,因地制宜
  15. var path = addrAndPort+"${ctx}/"
  16. console.log("当前系统的IP及端口号为:"+path);
  17. var userId = currentUserId;
  18. if(userId==-1){
  19. window.location.href="${ctx}/memo/testWebSocket";
  20. }
  21. var jspCode = userId;
  22. var websocket;
  23. if ('WebSocket' in window) {
  24. //alert("webSocket"+"ws://" + path + "wsMy?jspCode=" + jspCode);
  25. <shiro:hasPermission name="user_add_custommenu">
  26. websocket = new WebSocket("ws://" + path + "wsMy?jspCode=" + jspCode);
  27. </shiro:hasPermission>
  28. } else if ('MozWebSocket' in window) {
  29. websocket = new MozWebSocket("ws://" + path + "wsMy?jspCode=" + jspCode);
  30. } else {
  31. websocket = new SockJS("http://" + path + "wsMy/sockjs?jspCode=" + jspCode);
  32. }
  33. websocket.onopen = function(event) {
  34. console.log("WebSocket:已连接");
  35. console.log(event);
  36. };
  37. websocket.onmessage = function(event) {
  38. if (event.data =="木有消息啊"){
  39. //alert("木有消息啊,测试webSocket是否死亡")
  40. }else {
  41. alert("您有新的提醒,请进入系统首页进行查看")
  42. window.flushParent();
  43. var aaa = event.data.split("*");
  44. var id = aaa[0];
  45. var content = aaa[1];
  46. window.layer.alert(content, {
  47. skin: 'layui-layer-molv' //样式类名 自定义样式
  48. ,closeBtn: 1 // 是否显示关闭按钮
  49. ,anim: 1 //动画类型
  50. ,btn: ['取消提醒','确定'] //按钮
  51. ,icon: 6 // icon
  52. ,yes:function(){
  53. $.ajax({
  54. type : "post",
  55. data :{
  56. id :id
  57. },
  58. url : "${ctx}/memo/cancleNotepadByMap",
  59. success : function(data) {
  60. window.location.reload();
  61. //若执行成功的话,则隐藏进度条提示
  62. if (data != null && data != 'undefined'
  63. && data == 1) {
  64. layer.msg('该提醒取消成功!', {icon: 6,time:1000});
  65. parent.flushParent();
  66. layer_close();
  67. window.location.reload();
  68. }else if (data == -1) {
  69. layer.msg('记事本名称已经存在!', {icon: 5,time:1000});
  70. }else if (data == 0) {
  71. layer.msg('很抱歉!添加失败!', {icon: 5,time:1000});
  72. }else{
  73. layer.msg('系统异常!请与系统管理员联系!', {icon: 5,time:1000});
  74. }
  75. }
  76. });
  77. }
  78. ,btn2:function(){
  79. layer.msg('按钮2')
  80. }});
  81. }
  82. };
  83. websocket.onerror = function(event) {
  84. console.log("WebSocket:发生错误 ");
  85. console.log(event);
  86. };
  87. websocket.onclose = function(event) {
  88. console.log("WebSocket:已关闭");
  89. console.log(event);
  90. }
  91. }
  92. })
  93. }

因为本案例中访问的是当前服务器的路径,所以要对当前服务器的IP以及端口号进行获取,然后赋值给webSocket的访问路径:

  1. /*获取当前用户id*/
  2. var addrAndPort = "";
  3. function getAddrAndport() {
  4. $.ajax({
  5. type : "get",
  6. url : "${ctx}/getAddrAndport",
  7. beforeSend: function () {
  8. //加载中
  9. waitload();
  10. },
  11. success : function(data) {
  12. addrAndPort = data;
  13. }
  14. })
  15. }
  16. 后台获取IP以及端口号返回到前台:
  17. @RequestMapping(value = "/getAddrAndport", method = { RequestMethod.GET,
  18. RequestMethod.POST })
  19. @ResponseBody
  20. public Object getAddrAndport(HttpServletRequest request,
  21. HttpServletResponse response) {
  22. String addrAndPort = "";
  23. addrAndPort = request.getLocalAddr()+":"+request.getLocalPort();
  24. return addrAndPort;
  25. }
  26. 之前查找资料,获取当前系统的IP以及端口号的方法,杂乱,还是直接从HttpServletRequest 中直接获取最为直接和准确.

当前台页面触发访问的时候,后台服务器接收到访问请求并且进行处理,这时候我把后台关于webSocket的请求分为三个文件:
1:HandShake.java

  1. package com.yyx.webSocket;
  2. import org.springframework.http.server.ServerHttpRequest;
  3. import org.springframework.http.server.ServerHttpResponse;
  4. import org.springframework.http.server.ServletServerHttpRequest;
  5. import org.springframework.web.socket.WebSocketHandler;
  6. import org.springframework.web.socket.server.HandshakeInterceptor;
  7. import java.util.*;
  8. import java.util.HashMap;
  9. import java.util.Iterator;
  10. import java.util.Map;
  11. import java.util.Map.Entry;
  12. /** * Created by zhangrui on 2018/1/22. */
  13. public class HandShake implements HandshakeInterceptor{
  14. @Override
  15. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
  16. // TODO Auto-generated method stub
  17. String jspCode = ((ServletServerHttpRequest) request).getServletRequest().getParameter("jspCode");
  18. // 标记用户
  19. // String userId = (String) ((ServletServerHttpRequest) request).getServletRequest().getSession().getAttribute("userId");
  20. if(jspCode!=null){
  21. attributes.put("jspCode", jspCode);
  22. }else{
  23. return false;
  24. }
  25. return true;
  26. }
  27. @Override
  28. public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
  29. // TODO Auto-generated method stub
  30. }
  31. }

2.MyWebSocketConfig.java

  1. package com.yyx.webSocket;
  2. import org.springframework.stereotype.Component;
  3. import org.springframework.web.servlet.config.annotation.EnableWebMvc;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  5. import org.springframework.web.socket.config.annotation.EnableWebSocket;
  6. import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
  7. import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
  8. import javax.annotation.Resource;
  9. /** * Created by zhangrui on 2018/1/22. */
  10. @Component
  11. @EnableWebMvc
  12. @EnableWebSocket
  13. public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
  14. @Resource
  15. MyWebSocketHandler handler;
  16. @Override
  17. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  18. // TODO Auto-generated method stub
  19. registry.addHandler(handler, "/wsMy");
  20. registry.addHandler(handler, "/wsMy/sockjs").withSockJS();
  21. }
  22. }

3:MyWebSockethandler.java访问成功后的处理逻辑,本文中是使用线程写的,因人而异.

  1. package com.yyx.webSocket;
  2. import com.google.gson.GsonBuilder;
  3. import com.yyx.zbhr.controller.MemoController;
  4. import com.yyx.zbhr.entity.MemoEntity;
  5. import com.yyx.zbhr.entity.NotepadEntity;
  6. import com.yyx.zbhr.entity.User;
  7. import com.yyx.zbhr.service.MMemoService;
  8. import com.yyx.zbhr.service.MPersonInfoService;
  9. import com.yyx.zbhr.utils.UserUtil;
  10. import org.apache.commons.collections.map.HashedMap;
  11. import org.apache.shiro.authz.annotation.RequiresAuthentication;
  12. import org.apache.shiro.authz.annotation.RequiresPermissions;
  13. import org.slf4j.Logger;
  14. import org.slf4j.LoggerFactory;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Component;
  17. import org.springframework.web.socket.*;
  18. import javax.servlet.http.HttpSession;
  19. import java.io.IOException;
  20. import java.net.URI;
  21. import java.text.SimpleDateFormat;
  22. import java.util.*;
  23. import java.util.Map.*;
  24. /** * Created by zhangrui on 2018/1/22. */
  25. @Component
  26. public class MyWebSocketHandler implements WebSocketHandler {
  27. public static final Map<String, WebSocketSession> userSocketSessionMap;
  28. private static Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class);
  29. private static Calendar fromCal=Calendar.getInstance();
  30. @Autowired(required = false)
  31. private MPersonInfoService mPersonInfoService;
  32. @Autowired(required = false)
  33. private MMemoService mMemoService;
  34. static {
  35. userSocketSessionMap = new HashMap<String, WebSocketSession>();
  36. }
  37. public static WebSocketSession session1 ;
  38. @Override
  39. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  40. // TODO Auto-generated method stub
  41. //HttpSession httpSession = (HttpSession) session;
  42. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm");
  43. //调用接口获取ID
  44. //Long userId = mMemoService.getCurrentUserId();
  45. Map<String,Object> map = new HashedMap();
  46. /*//wsMy?jspCode=23*/
  47. String uri = session.getUri().toString();
  48. String[] split = uri.split("=");
  49. int currentUserId = Integer.parseInt(split[1]);
  50. // TODO 自动shiro获取id失败
  51. map.put("userId",currentUserId);
  52. String jspCode = (String) session.getHandshakeAttributes().get("jspCode");
  53. if (userSocketSessionMap.get(jspCode) == null) {
  54. userSocketSessionMap.put(jspCode, session);
  55. }
  56. new Thread(new Runnable() {
  57. public void run() {
  58. while(true){
  59. Date date = new Date();
  60. String format = simpleDateFormat.format(date);
  61. List<NotepadEntity> notepadEntities = mMemoService.selectNotepadForNotice(map);
  62. for (int i = 0; i < notepadEntities.size(); i++) {
  63. String format1 = simpleDateFormat.format(notepadEntities.get(i).getStarTime());
  64. String format2 = simpleDateFormat.format(notepadEntities.get(i).getEndTime());
  65. StringBuffer message = new StringBuffer();
  66. int flag1 = date.compareTo(notepadEntities.get(i).getEndTime());
  67. int flag2 = date.compareTo(notepadEntities.get(i).getStarTime());
  68. if (flag1 < 0 && flag2 >= 0) {
  69. try {
  70. String noteType = "";
  71. if (notepadEntities.get(i).getNoteType() == 1){
  72. noteType = "工作";
  73. }else if (notepadEntities.get(i).getNoteType() == 2){
  74. noteType = "生活";
  75. }else if (notepadEntities.get(i).getNoteType() == 3){
  76. noteType = "家庭";
  77. }else if (notepadEntities.get(i).getNoteType() == 4){
  78. noteType = "私密";
  79. }else {
  80. noteType = "其他";
  81. }
  82. message.append(notepadEntities.get(i).getId()+"*");
  83. message.append("<b>日程类型:</b></br>"+noteType+"</br>");
  84. message.append("<b>起始时间:</b></br>"+format1+"</br>");
  85. message.append("<b>终止时间:</b></br>"+format2+"</br>");
  86. message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
  87. message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
  88. session.sendMessage(new TextMessage(message,true));
  89. //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型\":\"" + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题:\":\"" + notepadEntities.get(i).getTitle() + "\";\"内容:\":\"" + notepadEntities.get(i).getContent() + "\"")));
  90. //session.sendMessage(new TextMessage("日程提醒\n日程事件:"+format1+"--"+format2+"\n"+"标题:"+notepadEntities.get(i).getTitle()+"\n"+"内容:"+notepadEntities.get(i).getContent()+"类型:"+noteType+"\n"+""));
  91. } catch (IOException e) {
  92. e.printStackTrace();
  93. }
  94. }else if(flag1>0){
  95. try {
  96. String noteType = "";
  97. if (notepadEntities.get(i).getNoteType() == 1){
  98. noteType = "工作";
  99. }else if (notepadEntities.get(i).getNoteType() == 2){
  100. noteType = "生活";
  101. }else if (notepadEntities.get(i).getNoteType() == 3){
  102. noteType = "家庭";
  103. }else if (notepadEntities.get(i).getNoteType() == 4){
  104. noteType = "私密";
  105. }else {
  106. noteType = "其他";
  107. }
  108. message.append(notepadEntities.get(i).getId()+"*");
  109. message.append("<h3 style=\"color:red\">日程过期提醒</h3>");
  110. message.append("<b>日程类型:</b></br>"+noteType+"</br>");
  111. message.append("<b>起始时间:</b></br>"+format1+"</br>");
  112. message.append("<b>终止时间:</b></br>"+format2+"</br>");
  113. message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>");
  114. message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>");
  115. session.sendMessage(new TextMessage(message,true));
  116. //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型\":\"" + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题:\":\"" + notepadEntities.get(i).getTitle() + "\";\"内容:\":\"" + notepadEntities.get(i).getContent() + "\"")));
  117. //session.sendMessage(new TextMessage("日程提醒\n日程事件:"+format1+"--"+format2+"\n"+"标题:"+notepadEntities.get(i).getTitle()+"\n"+"内容:"+notepadEntities.get(i).getContent()+"类型:"+noteType+"\n"+""));
  118. } catch (IOException e) {
  119. e.printStackTrace();
  120. }
  121. }else {
  122. String noteType = "";
  123. if (notepadEntities.get(i).getNoteType() == 1){
  124. noteType = "工作";
  125. }else if (notepadEntities.get(i).getNoteType() == 2){
  126. noteType = "生活";
  127. }else if (notepadEntities.get(i).getNoteType() == 3){
  128. noteType = "家庭";
  129. }else if (notepadEntities.get(i).getNoteType() == 4){
  130. noteType = "私密";
  131. }else {
  132. noteType = "其他";
  133. }
  134. //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"日程类型 \":\" " + noteType + "\";\"日程时间\":\"" + simpleDateFormat.format(notepadEntities.get(i).getStarTime())+"--"+simpleDateFormat.format(notepadEntities.get(i).getEndTime()) + "\";\"标题\":\"" + notepadEntities.get(i).getTitle() + "\";\"内容 \":\"" + notepadEntities.get(i).getContent() + "\"")));
  135. //session.sendMessage(new TextMessage(new GsonBuilder().create().toJson("\"number\":\"" + i + "\";\"deptId\":\"" + format + "\";\"事件:\":\"" + "这只是哥哥拿来测试的事件" + i + "\"")));
  136. /* message.append("<b>日程类型:</b></br>"+noteType+"</br>"); message.append("<b>起始时间:</b></br>"+format1+"</br>"); message.append("<b>终止时间:</b></br>"+format2+"</br>"); message.append("<b>主题:</b></br>"+notepadEntities.get(i).getTitle()+"</br>"); message.append("<b>内容:</b></br>"+notepadEntities.get(i).getContent()+"</br>"); session.sendMessage(new TextMessage(message,true));*/
  137. try {
  138. session.sendMessage(new TextMessage("木有消息啊",true));
  139. } catch (IOException e) {
  140. e.printStackTrace();
  141. }
  142. }
  143. }
  144. try {
  145. Thread.sleep(1000*60);
  146. } catch (InterruptedException e) {
  147. e.printStackTrace();
  148. }
  149. }
  150. }
  151. }).start();
  152. }
  153. @Override
  154. public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
  155. // TODO Auto-generated method stub
  156. //Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);
  157. //msg.setDate(new Date());
  158. // sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
  159. session.sendMessage(message);
  160. }
  161. @Override
  162. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  163. // TODO Auto-generated method stub
  164. if (session.isOpen()) {
  165. session.close();
  166. }
  167. Iterator<Map.Entry<String, WebSocketSession>> it = userSocketSessionMap
  168. .entrySet().iterator();
  169. // 移除Socket会话
  170. while (it.hasNext()) {
  171. Map.Entry<String, WebSocketSession> entry = it.next();
  172. if (entry.getValue().getId().equals(session.getId())) {
  173. userSocketSessionMap.remove(entry.getKey());
  174. System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
  175. break;
  176. }
  177. }
  178. }
  179. @Override
  180. public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
  181. // TODO Auto-generated method stub
  182. System.out.println("Websocket:" + session.getId() + "已经关闭");
  183. Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
  184. .entrySet().iterator();
  185. // 移除Socket会话
  186. while (it.hasNext()) {
  187. Entry<String, WebSocketSession> entry = it.next();
  188. if (entry.getValue().getId().equals(session.getId())) {
  189. userSocketSessionMap.remove(entry.getKey());
  190. //TODO 对webSocket资源进行回收
  191. System.gc();
  192. System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
  193. break;
  194. }
  195. }
  196. }
  197. @Override
  198. public boolean supportsPartialMessages() {
  199. return false;
  200. }
  201. public void broadcast(final TextMessage message) throws IOException {
  202. Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
  203. .entrySet().iterator();
  204. // 多线程群发
  205. while (it.hasNext()) {
  206. final Entry<String, WebSocketSession> entry = it.next();
  207. if (entry.getValue().isOpen()) {
  208. new Thread(new Runnable() {
  209. public void run() {
  210. try {
  211. if (entry.getValue().isOpen()) {
  212. entry.getValue().sendMessage(message);
  213. }
  214. } catch (IOException e) {
  215. e.printStackTrace();
  216. }
  217. }
  218. }).start();
  219. }
  220. }
  221. }
  222. public void sendMessageToJsp(final TextMessage message,String type) throws IOException {
  223. Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap
  224. .entrySet().iterator();
  225. // 多线程群发
  226. while (it.hasNext()) {
  227. final Entry<String, WebSocketSession> entry = it.next();
  228. if (entry.getValue().isOpen() && entry.getKey().contains(type)) {
  229. new Thread(new Runnable() {
  230. public void run() {
  231. try {
  232. if (entry.getValue().isOpen()) {
  233. entry.getValue().sendMessage(message);
  234. }
  235. } catch (IOException e) {
  236. e.printStackTrace();
  237. }
  238. }
  239. }).start();
  240. }
  241. }
  242. }
  243. }

第三个文件是核心,具体的代码是实验代码,有一部分是冗余代码,粘贴出来仅供参考,值得一提的是,在webSocket进行sendMessage的时候,可选的message格式有多种,当时博主想传回类json,但是传到前台发现处理比较麻烦,所以就传回了StringBuffer类型的,至于StringBuffer和StringBuidder之间的选择,看情况决定,这里不多言.再展示下消息传递后的效果图:
这里写图片描述

对返回到前台的消息处理使用的layer进行弹出,比较好用,样式多.可以直接从layer官网查询样式进行使用,网址….百度.
最后一点,本文中使用的web容器及版本是Tomcat8.0.48,虽然有人说在Tomcat8.0之前,不支持webSocket消息通讯,说的是那时候webSocket协议未制定,但是….博主试了下使用Tomcat7.9可行,所以这里就不再多说废话啦,博主也只是个门外汉,更多更细的webSocket知识点还是留待大家自行发掘吧!
最后一点,请不要诟病博主的访问路径:getAddrAndport,虽然不规范,但是….这破习惯一下子难改啊…逐步改正.
完.

发表评论

表情:
评论列表 (有 0 条评论,549人围观)

还没有评论,来说两句吧...

相关阅读

    相关 使用webSocket实现及时通信

    本方案是为了解决前后台及时通信设计的,例如后台代码触发一个事件,可以及时的传递给前台,也就是消息的推送功能. 关于消息的推送,方案1是使用定时任务,Cron表达式设置每分钟