当前位置:   article > 正文

spring websocket实现前后端通信_registerwebsockethandlers

registerwebsockethandlers

  项目要用websocket实现一个前后端实时通信的功能,做完之后感触颇多,写个博客回顾下整个历程,也希望能给后面的同志有点帮助。

百度网盘示例源码:链接:https://pan.baidu.com/s/1Gi3qRyLO-lTnkVn4MqGIJA 密码:4ovr

我使用springmvc的websocket组件,官网地址:点击打开链接

示例内容:用户登陆之后往设置session设置登陆名,之后跳转到发送消息页面,加载页面时创建websocket连接,这时,springmvc拦截器拦截到websocket请求,把session中登陆名保存到attributes中,这个值会映射到WebSocketSession里,从而在SpringWebSocketHandler类中使用, 这部分看不懂没关联,结合下面的代码来看就懂了。

步骤一:添加maven依赖,注意两点问题

1、spring的websocket依赖容器支持,我选用的是tomcat7.077,tomcat7以下是不支持websocket的

2、javax-servlet-api和java-websocket-api两个包都限定了<scope>provided</scope>因为这两个包tomcat容器已经带了,provided表示编译时使用,打包不会包含在war包里,如不知道重启启动会报错。

  1. <dependency>
  2. <groupId>javax.servlet</groupId>
  3. <artifactId>javax.servlet-api</artifactId>
  4. <version>3.1.0</version>
  5. <scope>provided</scope>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework</groupId>
  9. <artifactId>spring-webmvc</artifactId>
  10. <version>4.1.5.RELEASE</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>jstl</groupId>
  14. <artifactId>jstl</artifactId>
  15. <version>1.2</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>taglibs</groupId>
  19. <artifactId>standard</artifactId>
  20. <version>1.1.2</version>
  21. </dependency>
  22. <!-- spring websocket -->
  23. <dependency>
  24. <groupId>org.springframework</groupId>
  25. <artifactId>spring-messaging</artifactId>
  26. <version>4.1.7.RELEASE</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework</groupId>
  30. <artifactId>spring-websocket</artifactId>
  31. <version>4.1.7.RELEASE</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>javax.websocket</groupId>
  35. <artifactId>javax.websocket-api</artifactId>
  36. <version>1.0</version>
  37. <scope>provided</scope>
  38. </dependency>

步骤二:编辑SpringWebSocketConfig,根据spring文档,编写websocketConfig,这里可参看文档,xml配置和使用注解两种方式,我选择注解方式

registerWebSocketHandlers:这个方法是向spring容器注册一个handler地址,我把他理解成requestMapping

addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来搞事情。

setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口

withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。

  1. @Configuration
  2. @EnableWebSocket
  3. public class SpringWebSocketConfig implements WebSocketConfigurer {
  4. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  5. registry.addHandler(webSocketHandler(),"/websocket/socketServer")
  6. .addInterceptors(new SpringWebSocketHandlerInterceptor()).setAllowedOrigins("*");
  7. registry.addHandler(webSocketHandler(), "/sockjs/socketServer").setAllowedOrigins("http://localhost:28180")
  8. .addInterceptors(new SpringWebSocketHandlerInterceptor()).withSockJS();
  9. }
  10. @Bean
  11. public TextWebSocketHandler webSocketHandler(){
  12. return new SpringWebSocketHandler();
  13. }
  14. }

步骤三:编写SpringWebSocketHandlerInterceptor

这个是创建websocket连接是的拦截器,记录建立连接的用户的session以便根据不同session来通信

  1. public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor {
  2. @Override
  3. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
  4. Map<String, Object> attributes) throws Exception {
  5. System.out.println("Before Handshake");
  6. if (request instanceof ServletServerHttpRequest) {
  7. ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
  8. HttpSession session = servletRequest.getServletRequest().getSession(false);
  9. if (session != null) {
  10. //使用userName区分WebSocketHandler,以便定向发送消息
  11. String userName = (String) session.getAttribute("SESSION_USERNAME"); //一般直接保存user实体
  12. if (userName!=null) {
  13. attributes.put("WEBSOCKET_USERID",userName);
  14. }
  15. }
  16. }
  17. return super.beforeHandshake(request, response, wsHandler, attributes);
  18. }
  19. @Override
  20. public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
  21. Exception ex) {
  22. super.afterHandshake(request, response, wsHandler, ex);
  23. }
  24. }

步骤四:编写SpringWebSocketHandler 

  1. public class SpringWebSocketHandler extends TextWebSocketHandler {
  2. private static final Map<String, WebSocketSession> users; //Map来存储WebSocketSession,key用USER_ID 即在线用户列表
  3. //用户标识
  4. private static final String USER_ID = "WEBSOCKET_USERID"; //对应监听器从的key
  5. static {
  6. users = new HashMap<String, WebSocketSession>();
  7. }
  8. public SpringWebSocketHandler() {}
  9. /**
  10. * 连接成功时候,会触发页面上onopen方法
  11. */
  12. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  13. System.out.println("成功建立websocket连接!");
  14. String userId = (String) session.getAttributes().get(USER_ID);
  15. users.put(userId,session);
  16. System.out.println("当前线上用户数量:"+users.size());
  17. //这块会实现自己业务,比如,当用户登录后,会把离线消息推送给用户
  18. //TextMessage returnMessage = new TextMessage("成功建立socket连接,你将收到的离线");
  19. //session.sendMessage(returnMessage);
  20. }
  21. /**
  22. * 关闭连接时触发
  23. */
  24. public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
  25. String userId= (String) session.getAttributes().get(USER_ID);
  26. System.out.println("用户"+userId+"已退出!");
  27. users.remove(userId);
  28. System.out.println("剩余在线用户"+users.size());
  29. }
  30. /**
  31. * js调用websocket.send时候,会调用该方法
  32. */
  33. @Override
  34. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  35. super.handleTextMessage(session, message);
  36. /**
  37. * 收到消息,自定义处理机制,实现业务
  38. */
  39. System.out.println("服务器收到消息:"+message);
  40. if(message.getPayload().startsWith("#anyone#")){ //单发某人
  41. sendMessageToUser((String)session.getAttributes().get(USER_ID), new TextMessage("服务器单发:" +message.getPayload())) ;
  42. }else if(message.getPayload().startsWith("#everyone#")){
  43. sendMessageToUsers(new TextMessage("服务器群发:" +message.getPayload()));
  44. }else{
  45. }
  46. }
  47. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  48. if(session.isOpen()){
  49. session.close();
  50. }
  51. System.out.println("传输出现异常,关闭websocket连接... ");
  52. String userId= (String) session.getAttributes().get(USER_ID);
  53. users.remove(userId);
  54. }
  55. public boolean supportsPartialMessages() {
  56. return false;
  57. }
  58. /**
  59. * 给某个用户发送消息
  60. *
  61. * @param userId
  62. * @param message
  63. */
  64. public void sendMessageToUser(String userId, TextMessage message) {
  65. for (String id : users.keySet()) {
  66. if (id.equals(userId)) {
  67. try {
  68. if (users.get(id).isOpen()) {
  69. users.get(id).sendMessage(message);
  70. }
  71. } catch (IOException e) {
  72. e.printStackTrace();
  73. }
  74. break;
  75. }
  76. }
  77. }
  78. /**
  79. * 给所有在线用户发送消息
  80. *
  81. * @param message
  82. */
  83. public void sendMessageToUsers(TextMessage message) {
  84. for (String userId : users.keySet()) {
  85. try {
  86. if (users.get(userId).isOpen()) {
  87. users.get(userId).sendMessage(message);
  88. }
  89. } catch (IOException e) {
  90. e.printStackTrace();
  91. }
  92. }
  93. }
  94. }

步骤五:配置文件扫描config类  我的SpringWebSocketConfig配置在包com.thunisoft.config下

	<context:component-scan base-package="com.thunisoft.ssm.controller,com.thunisoft.config"></context:component-scan>

步骤六:编写springmvc controller

  1. @Controller
  2. public class WebSocketController {
  3. @Bean//这个注解会从Spring容器拿出Bean
  4. public SpringWebSocketHandler infoHandler() {
  5. return new SpringWebSocketHandler();
  6. }
  7. @RequestMapping("/websocket/loginPage")
  8. public String loginPage(HttpServletRequest request, HttpServletResponse response) throws Exception {
  9. return "/order/login";
  10. }
  11. @RequestMapping("/websocket/login")
  12. public String login(HttpServletRequest request, HttpServletResponse response) throws Exception {
  13. String username = request.getParameter("username");
  14. System.out.println(username+"登录");
  15. HttpSession session = request.getSession(false);
  16. session.setAttribute("SESSION_USERNAME", username); //一般直接保存user实体
  17. return "/order/send";
  18. }
  19. @RequestMapping("/websocket/send")
  20. @ResponseBody
  21. public String send(HttpServletRequest request) {
  22. String username = request.getParameter("username");
  23. infoHandler().sendMessageToUser(username, new TextMessage("你好,测试!!!!"));
  24. return null;
  25. }
  26. @RequestMapping("/websocket/broad")
  27. @ResponseBody
  28. public String broad() {
  29. infoHandler().sendMessageToUsers(new TextMessage("发送一条小Broad"));
  30. System.out.println("群发成功");
  31. return "broad";
  32. }
  33. }

步骤七:编辑登陆jsp

  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  2. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  3. <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
  4. <c:set var="ctx" value="${pageContext.request.contextPath}"/>
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  9. <title>测试spring websocket</title>
  10. </head>
  11. <body>
  12. <form action="${ctx}/websocket/login">
  13. 登录名:<input type="text" name="username"/>
  14. <input type="submit" value="登录聊天室"/>
  15. </form>
  16. </body>

步骤八:编写通信页面

  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  2. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  3. <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
  4. <c:set var="ctx" value="${pageContext.request.contextPath}"/>
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  9. <title></title>
  10. <!--
  11. <link rel="stylesheet" href="/css/style.css"/>
  12. -->
  13. <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
  14. <script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
  15. <script type="text/javascript">
  16. var websocket = null;
  17. if ('WebSocket' in window) {
  18. websocket = new WebSocket("ws://localhost:8080/springfirst/websocket/socketServer");
  19. }
  20. else if ('MozWebSocket' in window) {
  21. websocket = new MozWebSocket("ws://localhost:8080/springfirst/websocket/socketServer");
  22. }
  23. else {
  24. websocket = new SockJS("http://localhost:8080/springfirst/sockjs/socketServer");
  25. }
  26. websocket.onopen = onOpen;
  27. websocket.onmessage = onMessage;
  28. websocket.onerror = onError;
  29. websocket.onclose = onClose;
  30. function onOpen(openEvt) {
  31. alert(openEvt.Data);
  32. }
  33. function onMessage(evt) {
  34. alert("super is:" + evt.data);
  35. }
  36. function onOpen() {
  37. }
  38. function onError() {}
  39. function onClose() {}
  40. function doSendUser() {
  41. alert(websocket.readyState + ":" + websocket.OPEN);
  42. if (websocket.readyState == websocket.OPEN) {
  43. var msg = document.getElementById("inputMsg").value;
  44. websocket.send("#anyone#"+msg);//调用后台handleTextMessage方法
  45. alert("发送成功!");
  46. } else {
  47. alert("连接失败!");
  48. }
  49. }
  50. function doSendUsers() {
  51. if (websocket.readyState == websocket.OPEN) {
  52. var msg = document.getElementById("inputMsg").value;
  53. websocket.send("#everyone#"+msg);//调用后台handleTextMessage方法
  54. alert("发送成功!");
  55. } else {
  56. alert("连接失败!");
  57. }
  58. }
  59. window.close=function()
  60. {
  61. websocket.onclose();
  62. }
  63. function websocketClose() {
  64. websocket.close();
  65. }
  66. </script>
  67. </head>
  68. <body>
  69. 请输入:<textarea rows="5" cols="10" id="inputMsg" name="inputMsg"></textarea>
  70. <button οnclick="doSendUser();">发送</button>
  71. <button οnclick="doSendUsers();">群发</button>
  72. <button οnclick="websocketClose();">关闭连接</button>
  73. </body>
  74. </html>

演示效果图

登陆:


单发:

群发:


后续配合nginx发布,在nginx代理的情况下需要配置,这个示例项目,我的跟目录是/springfirst ,springfirst是我的项目名,实际项目发布的时候是隐藏项目名的,所以配置/就可以。

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
                  proxy_set_header Connection "upgrade";

  1. upstream websocket {
  2. server 127.0.0.1:8080;
  3. }
  4.     server {
  5.         listen       80;
  6. server_name  max.eqshow.cnn;
  7.         #charset koi8-r;
  8.         #access_log  logs/host.access.log  main;
  9. location /springfirst {
  10.   proxy_pass http://websocket;
  11.   proxy_set_header   Host             $host;
  12.                   proxy_set_header   X-Real-IP        $remote_addr;
  13.                   proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
  14.   proxy_http_version 1.1;
  15.   proxy_set_header Upgrade $http_upgrade;
  16.                   proxy_set_header Connection "upgrade";
  17.         }
  18. }

总结: 做完之后感觉整个过程并不复杂,单其实经历了好几天,从最初的不知道什么是websocket,到后来不知道nginx配置出问题,从不知道到知道,其实遇到了很多问题。比如,不知道websocket需要容器和浏览器的支持,不知道跨域需要设置setAllowedOrigins("*"),demo写好怎么融入到项目中也遇到了很多问题。但这一切在有结果的时候都豁然开朗。同时也了解了器用分析法,先了解基本用法,写个demo,然后研究其原理。到目前为止也是浅薄的是了解而已,后面遇到问题在继续更新吧。

本文示例参考:https://blog.csdn.net/zmx729618/article/details/78584633

springmvc websocket:点击打开链接

http://www.runoob.com:点击打开链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/241999
推荐阅读
相关标签
  

闽ICP备14008679号