[关闭]
@MrXiao 2018-12-12T17:23:16.000000Z 字数 10434 阅读 940

Spring4整合websocket

websocket


原文地址:Spring4整合websocket

  1. spring 4.0及以上增加了WebSocket的支持(这里使用4.2.5.RELEASE)。
  2. spring 支持STOMP协议的WebSocket通信。
  3. 应对不支持 WebSocket 的场景,许多浏览器不支持 WebSocket 协议;SockJS 是 WebSocket 技术的一种模拟。SockJS 会 尽可能对应 WebSocket API,但如果 WebSocket 技术 不可用的话,会从如下 方案中挑选最优可行方案:

    1. XHR streaming
    2. XDR streaming
    3. iFrame event source
    4. iFrame HTML file
    5. XHR polling
    6. XDR polling
    7. iFrame XHR polling
    8. JSONP polling
  4. WebSocket 是发送和接收消息的 底层API,而SockJS 是在 WebSocket 之上的 API;最后 STOMP(面向消息的简单文本协议)是基于 SockJS 的高级API。

  5. SockJS 所处理的URL 是 “http:” 或 “https:” 模式
  6. WebSocket 所处理的URL 是“ws:” or “wss:” 模式

下面我们来建立一个websocket实例

1、创建web工程

首先搭建一个简单可访问的web系统,这里提供一个我自己搭建的SSM框架供使用。

2、导入spring-websocket需要的jar

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-websocket</artifactId>
  4. <version>${spring.version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-messaging</artifactId>
  9. <version>${spring.version}</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>javax.servlet</groupId>
  13. <artifactId>javax.servlet-api</artifactId>
  14. <version>3.1.0</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.codehaus.jackson</groupId>
  18. <artifactId>jackson-mapper-asl</artifactId>
  19. <version>1.9.13</version>
  20. </dependency>
  21. <dependency>
  22. <groupId>com.fasterxml.jackson.core</groupId>
  23. <artifactId>jackson-core</artifactId>
  24. <version>2.8.0</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>com.fasterxml.jackson.core</groupId>
  28. <artifactId>jackson-databind</artifactId>
  29. <version>2.8.0</version>
  30. </dependency>

3、创建Websocket处理器

可以扩展 AbstractWebSocketHandler ,也可以扩展TextWebSocketHandler(文本处理器),TextWebSocketHandler 继承 AbstractWebSocketHandler

TestWebsocketHandler

  1. package com.websocket;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.web.socket.CloseStatus;
  7. import org.springframework.web.socket.PongMessage;
  8. import org.springframework.web.socket.TextMessage;
  9. import org.springframework.web.socket.WebSocketSession;
  10. import org.springframework.web.socket.handler.TextWebSocketHandler;
  11. /**
  12. * 〈websocket处理类〉
  13. *
  14. * @author xiaoyue
  15. * @create 2018/12/11 15:17
  16. * @since 1.0.0
  17. */
  18. public class TestWebsocketHandler extends TextWebSocketHandler {
  19. private Logger logger = LoggerFactory.getLogger(TextWebSocketHandler.class);
  20. private static final ArrayList<WebSocketSession> sessions;// 这个会出现性能问题,最好用Map来存储,key用userid
  21. static {
  22. sessions = new ArrayList<WebSocketSession>();
  23. }
  24. // 接收文本消息,并发送出去
  25. // js调用websocket.send时候,会调用该方法
  26. @Override
  27. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  28. super.handleTextMessage(session, message);
  29. TextMessage returnMessage = new TextMessage(message.getPayload() + " received at server");
  30. // session.sendMessage(returnMessage);
  31. sendMessageToUsers(returnMessage);
  32. }
  33. // 连接建立后处理
  34. @SuppressWarnings("unchecked")
  35. @Override
  36. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  37. sessions.add(session);
  38. System.out.println("connect to the websocket success......当前数量:" + sessions.size());
  39. // 处理离线消息
  40. }
  41. // 抛出异常时处理
  42. @Override
  43. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  44. if (session.isOpen()) {
  45. session.close();
  46. }
  47. logger.debug("websocket chat connection closed......");
  48. sessions.remove(session);
  49. }
  50. // 连接关闭后处理
  51. @Override
  52. public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
  53. logger.debug("websocket chat connection closed......");
  54. String username = (String) session.getAttributes().get("WEBSOCKET_USERNAME");
  55. System.out.println("用户" + username + "已退出!");
  56. sessions.remove(session);
  57. System.out.println("剩余在线用户" + sessions.size());
  58. }
  59. @Override
  60. public boolean supportsPartialMessages() {
  61. return false;
  62. }
  63. @Override
  64. protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
  65. super.handlePongMessage(session, message);
  66. }
  67. /**
  68. * 给某个用户发送消息
  69. *
  70. * @param userName
  71. * @param message
  72. */
  73. public void sendMessageToUser(String userName, TextMessage message) {
  74. for (WebSocketSession user : sessions) {
  75. if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
  76. try {
  77. if (user.isOpen()) {
  78. user.sendMessage(message);
  79. }
  80. } catch (IOException e) {
  81. e.printStackTrace();
  82. }
  83. break;
  84. }
  85. }
  86. }
  87. /**
  88. * 给所有在线用户发送消息
  89. *
  90. * @param message
  91. */
  92. public void sendMessageToUsers(TextMessage message) {
  93. for (WebSocketSession user : sessions) {
  94. try {
  95. if (user.isOpen()) {
  96. user.sendMessage(message);
  97. }
  98. } catch (IOException e) {
  99. e.printStackTrace();
  100. }
  101. }
  102. }
  103. }

4、创建WebSocket握手拦截器

TestHandshakeInterceptor

  1. package com.websocket;
  2. import java.util.Map;
  3. import javax.servlet.http.HttpSession;
  4. import org.springframework.http.server.ServerHttpRequest;
  5. import org.springframework.http.server.ServerHttpResponse;
  6. import org.springframework.http.server.ServletServerHttpRequest;
  7. import org.springframework.web.socket.WebSocketHandler;
  8. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
  9. /**
  10. * 〈握手(handshake)接口〉
  11. *
  12. * @author xiaoyue
  13. * @create 2018/12/11 15:18
  14. * @since 1.0.0
  15. */
  16. public class TestHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
  17. // 常用在注册用户信息,绑定WebSocketSession,在handler里根据用户信息获取WebSocketSession发送消息
  18. @Override
  19. public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
  20. Map<String, Object> attributes) throws Exception {
  21. System.out.println("Before Handshake");
  22. if (request instanceof ServletServerHttpRequest) {
  23. ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
  24. HttpSession session = servletRequest.getServletRequest().getSession(false);
  25. if (session != null) {
  26. // 使用userName区分WebSocketHandler,以便定向发送消息
  27. String userName = (String) session.getAttribute("SESSION_USERNAME");
  28. if (userName == null) {
  29. userName = "default-system";
  30. }
  31. attributes.put("WEBSOCKET_USERNAME", userName);
  32. }
  33. }
  34. return super.beforeHandshake(request, response, wsHandler, attributes);
  35. }
  36. @Override
  37. public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
  38. Exception ex) {
  39. System.out.println("After Handshake");
  40. super.afterHandshake(request, response, wsHandler, ex);
  41. }
  42. }

5、创建Websocket配置文件

采用注解的方式。

WebSocketConfig

  1. package com.websocket;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  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. * 〈WebSocket入口〉
  11. *
  12. * @author xiaoyue
  13. * @create 2018/12/11 15:37
  14. * @since 1.0.0
  15. */
  16. @Configuration
  17. // @EnableWebMvc//这个标注可以不加,如果有加,要extends WebMvcConfigurerAdapter
  18. @EnableWebSocket
  19. public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
  20. // 注册处理器,绑定对应的url
  21. @Override
  22. public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
  23. System.out.println("==========================注册socket");
  24. String websocket_url = "/websocket/socketServer.do";
  25. registry.addHandler(testWebSocketHandler(), websocket_url).addInterceptors(testHandshakeInterceptor());
  26. String sockjs_url = "/sockjs/socketServer.do";
  27. registry.addHandler(testWebSocketHandler(), sockjs_url).addInterceptors(testHandshakeInterceptor())
  28. .withSockJS();
  29. }
  30. @Bean
  31. public WebSocketHandler testWebSocketHandler() {
  32. return new TestWebsocketHandler();
  33. }
  34. @Bean
  35. public TestHandshakeInterceptor testHandshakeInterceptor() {
  36. return new TestHandshakeInterceptor();
  37. }
  38. }

5、创建Controller,控制页面跳转

WebsocketController

  1. package com.websocket;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.stereotype.Controller;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.ResponseBody;
  6. import org.springframework.web.servlet.ModelAndView;
  7. import org.springframework.web.socket.TextMessage;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10. import javax.servlet.http.HttpSession;
  11. /**
  12. * 〈〉
  13. *
  14. * @author xiaoyue
  15. * @create 2018/12/11 16:38
  16. * @since 1.0.0
  17. */
  18. @Controller
  19. public class WebsocketController {
  20. @Bean // 这个注解会从Spring容器拿出Bean
  21. public TestWebsocketHandler infoHandler() {
  22. return new TestWebsocketHandler();
  23. }
  24. @RequestMapping("/websocket/login")
  25. public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws Exception {
  26. String username = request.getParameter("username");
  27. System.out.println(username + "登录");
  28. HttpSession session = request.getSession(false);
  29. session.setAttribute("SESSION_USERNAME", username);
  30. // response.sendRedirect("/quicksand/jsp/websocket.jsp");
  31. return new ModelAndView("websocket");
  32. }
  33. @RequestMapping("/websocket/send")
  34. @ResponseBody
  35. public String send(HttpServletRequest request) {
  36. String username = request.getParameter("username");
  37. infoHandler().sendMessageToUser(username, new TextMessage("你好,测试!!!!"));
  38. return null;
  39. }
  40. }

6、测试页面

登录页面,index.jsp

  1. <%@ page language="java" contentType="text/html; charset=utf-8"
  2. pageEncoding="utf-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <body>
  6. <h2>Hello World!</h2>
  7. <body>
  8. <form action="/websocket/login.do">
  9. 登录名:<input type="text" name="username"/>
  10. <input type="submit" value="登录"/>
  11. </form>
  12. </body>
  13. </body>
  14. </html>

消息发送接收页面 websocket.jsp

  1. <%@ page language="java" contentType="text/html; charset=utf-8"
  2. pageEncoding="utf-8" %>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  7. <title>Java API for WebSocket (JSR-356)</title>
  8. </head>
  9. <body>
  10. <script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
  11. <script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
  12. <script type="text/javascript">
  13. var websocket = null;
  14. if ('WebSocket' in window) {
  15. //Websocket的连接
  16. websocket = new WebSocket("ws://localhost:8080/websocket/socketServer.do");//WebSocket对应的地址
  17. }
  18. else if ('MozWebSocket' in window) {
  19. //Websocket的连接
  20. websocket = new MozWebSocket("ws://localhost:8080/ws/websocket/socketServer.do");//SockJS对应的地址
  21. }
  22. else {
  23. //SockJS的连接
  24. websocket = new SockJS("http://localhost:8080/ws/sockjs/socketServer.do"); //SockJS对应的地址
  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(evt.data);
  35. }
  36. function onError() {
  37. }
  38. function onClose() {
  39. }
  40. function doSend() {
  41. if (websocket.readyState == websocket.OPEN) {
  42. var msg = document.getElementById("inputMsg").value;
  43. websocket.send(msg);//调用后台handleTextMessage方法
  44. alert("发送成功!");
  45. } else {
  46. alert("连接失败!");
  47. }
  48. }
  49. window.close = function () {
  50. websocket.onclose();
  51. }
  52. </script>
  53. 请输入:<textarea rows="3" cols="100" id="inputMsg" name="inputMsg"></textarea>
  54. <button onclick="doSend();">发送</button>
  55. </body>

注意事项

  1. 因为是基于Spring注解,所以要将以上websocket的三个文件添加到Spring的扫描路径中。
  2. 建议所有的servlet和filter都要加<async-supported>true</async-supported>,支持异步处理。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注