Spring MVC整合WebSocket通信

目录

============================================================================

1、使用 Spring 的低层级 WebSocket API

2、使用JSR356定义的WebSocket规范

3、

4、

============================================================================

转载:http://www.cnblogs.com/yangchongxing/p/8474256.html

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。
特点:事件驱动、异步,使用ws或者wss协议的客户端socket,能够实现真正意义上的推送功能
缺点:少部分浏览器不支持,浏览器支持的程度与方式有区别。

参考资料:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

构造函数

  1. WebSocket(url[, protocols]) 返回一个 WebSocket 对象

常量

名称 作用
WebSocket.CONNECTING 0 正尝试与服务器建立连接
WebSocket.OPEN 1 与服务器已经建立连接
WebSocket.CLOSING 2 正在关闭与服务器的连接
WebSocket.CLOSED 3 已经关闭了与服务器的连接

WebSocket实例的readyState属性对照判断状态

属性

binaryType 使用二进制的数据类型连接
bufferedAmount 只读 未发送至服务器的字节数
extensions 只读 服务器选择的扩展
onclose 用于指定连接关闭后的回调函数
onerror 用于指定连接失败后的回调函数
onmessage 用于指定当从服务器接受到信息时的回调函数
onopen 用于指定连接成功后的回调函数
protocol 只读 服务器选择的下属协议
readyState 只读 当前的链接状态
url 只读 WebSocket 的绝对路径

方法

WebSocket.close([code[, reason]]) 关闭当前链接

WebSocket.send(data) 向服务器发送数据

Java服务端:
JSR356定义了WebSocket的规范,JSR356 的 WebSocket 规范使用 javax.websocket.*的 API,可以将一个普通 Java 对象(POJO)使用 @ServerEndpoint 注释作为 WebSocket 服务器的端点。

1、使用 Spring 的低层级 WebSocket API

实现WebSocketHandler接口、该接口包含5个方法,用于处理不同的事件

  1. public interface WebSocketHandler {
  2. void afterConnectionEstablished(WebSocketSession session) throws Exception;
  3. void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
  4. void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
  5. void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
  6. boolean supportsPartialMessages();
  7. }

比实现接口更为简单的方式是扩展AbstractWebSocketHandler抽象类,下面是抽象类的代码

  1. public abstract class AbstractWebSocketHandler implements WebSocketHandler {
  2. @Override
  3. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  4. }
  5. @Override
  6. public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
  7. if (message instanceof TextMessage) {
  8. handleTextMessage(session, (TextMessage) message);
  9. }
  10. else if (message instanceof BinaryMessage) {
  11. handleBinaryMessage(session, (BinaryMessage) message);
  12. }
  13. else if (message instanceof PongMessage) {
  14. handlePongMessage(session, (PongMessage) message);
  15. }
  16. else {
  17. throw new IllegalStateException("Unexpected WebSocket message type: " + message);
  18. }
  19. }
  20. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  21. }
  22. protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
  23. }
  24. protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
  25. }
  26. @Override
  27. public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
  28. }
  29. @Override
  30. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
  31. }
  32. @Override
  33. public boolean supportsPartialMessages() {
  34. return false;
  35. }
  36. }

我们以文本消息为例直接继承TextWebSocketHandler类
TextWebSocketHandler继承自AbstractWebSocketHandler类用来处理文本消息。
BinaryWebSocketHandler继承自AbstractWebSocketHandler类用来处理二进制消息。

  1. public class CommoditySocket extends TextWebSocketHandler {
  2. @Override
  3. public void afterConnectionEstablished(WebSocketSession session) throws Exception {
  4. System.out.println("open...");
  5. }
  6. @Override
  7. public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
  8. System.out.println("Closed");
  9. }
  10. @Override
  11. protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
  12. super.handleTextMessage(session, message);
  13. System.out.println("收到消息:" + message.getPayload());
  14. }
  15. }

消息处理类完成了,来看配置文件

首先追加websocket命名空间

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xmlns:tx="http://www.springframework.org/schema/tx"
  6. xmlns:task="http://www.springframework.org/schema/task"
  7. xmlns:websocket="http://www.springframework.org/schema/websocket"
  8. xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd
  9. http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
  10. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  11. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
  12. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  13. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

再追加如下配置,注意path的路径这个是请求的地址,ws://域名:端口/项目名/socket/text

  1. <websocket:handlers>
  2. <websocket:mapping handler="textHandler" path="/socket/text"/>
  3. </websocket:handlers>
  4.  
  5. <bean id="textHandler" class="cn.ycx.web.socket.TextHandler"></bean>

浏览器端使用标准的WebSocket

  1. var url = 'ws://' + window.location.host + '/ycxxml/socket/text'; //配置文件中配的path有关
  2. var socket = new WebSocket(url);
  3. socket.onopen = function() {
  4. console.log("open...");
  5. socket.send("start talk...")
  6. }
  7. socket.onmessage = function(e) {
  8. console.log("服务器发来:" + e.data);
  9. document.write("" + e.data + "<br/>");
  10. }
  11. socket.onclose = function() {
  12. console.log("close...");
  13. }

2、使用JSR356定义的WebSocket规范

@ServerEndpoint(value="/websocket/commodity/{userId}", configurator = SpringConfigurator.class)

特别注意:configurator = SpringConfigurator.class,若要进行对象注入此段代码必须加

表示将普通的Java对象注解为WebSocket服务端点,运行在ws://[Server端IP或域名]:[Server端口]/项目/websocket/commodity/{userId}的访问端点,客户端浏览器已经可以对WebSocket客户端API发起HTTP长连接了。
@OnOpen
在新连接建立时被调用。@PathParam可以传递url参数,满足业务需要。Session表明两个WebSocket端点对话连接的另一端,可以理解为类似HTTPSession的概念。
@OnClose
在连接被终止时调用。参数closeReason可封装更多细节,如为什么一个WebSocket连接关闭。
@OnMessage
注解的Java方法用于接收传入的WebSocket信息,这个信息可以是文本格式,也可以是二进制格式

服务器端代码:

  1. package cn.ycx.web.socket;
  2.  
  3. import java.io.IOException;
  4. import java.util.HashMap;
  5. import java.util.Iterator;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. import java.util.concurrent.ConcurrentMap;
  8.  
  9. import javax.websocket.CloseReason;
  10. import javax.websocket.OnClose;
  11. import javax.websocket.OnError;
  12. import javax.websocket.OnMessage;
  13. import javax.websocket.OnOpen;
  14. import javax.websocket.Session;
  15. import javax.websocket.server.PathParam;
  16. import javax.websocket.server.ServerEndpoint;
  17.  
  18. import org.springframework.web.socket.server.standard.SpringConfigurator;
  19.  
  20. import com.alibaba.fastjson.JSON;
  21. /**
  22. * 交易
  23. * @author 杨崇兴
  24. *
  25. */
  26. @ServerEndpoint(value="/websocket/commodity/{fromUserId}/{toUserId}", configurator = SpringConfigurator.class)
  27. public class WebSocketServer {
  28. // 已经建立链接的对象缓存起来
  29. private static ConcurrentMap<Integer, WebSocketServer> serverMap = new ConcurrentHashMap<Integer, WebSocketServer>();
  30. // 当前session
  31. private Session currentSession;
  32. @OnOpen
  33. public void onOpen(Session session, @PathParam("fromUserId") int fromUserId, @PathParam("toUserId") int toUserId) throws IOException {
  34. this.currentSession = session;
  35. serverMap.put(fromUserId, this);//建立链接时,缓存对象
  36. }
  37. @OnClose
  38. public void onClose(Session session, CloseReason reason) {
  39. System.out.println(reason.toString());
  40. if (serverMap.containsValue(this)) {
  41. Iterator<Integer> keys = serverMap.keySet().iterator();
  42. int userId = 0;
  43. while(keys.hasNext()) {
  44. userId = keys.next();
  45. if (serverMap.get(userId) == this) {
  46. serverMap.remove(userId, this);//关闭链接时,删除缓存对象
  47. }
  48. }
  49. }
  50. this.currentSession = null;
  51. try {
  52. session.close();
  53. } catch (IOException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. @OnMessage()
  58. @SuppressWarnings("unchecked")
  59. public void onMessage(String json) {
  60. HashMap<String, String> map = JSON.parseObject(json, HashMap.class);
  61. int fromUserId = Integer.parseInt(map.get("fromUserId"));
  62. int toUserId = Integer.parseInt(map.get("toUserId"));
  63. String content = map.get("content").toString();
  64. WebSocketServer server = serverMap.get(toUserId);//若存在则用户在线,否在用户不在线
  65. if (server != null && server.currentSession.isOpen()) {
  66. if (fromUserId != toUserId) {
  67. try {
  68. server.currentSession.getBasicRemote().sendText(content);
  69. } catch (IOException e) {
  70. e.printStackTrace();
  71. }
  72. }
  73. }
  74. }
  75. @OnError
  76. public void onError(Throwable t) {
  77. t.printStackTrace();
  78. }
  79.  
  80. }

注意:修改了原来的问题,serverMap对象全局缓存了已经链接上的对象,通过这对象也能判断用户是否在线。

注意:使用spring boot是要定义ServerEndpointExporter

If you want to use @ServerEndpoint in a Spring Boot application that used an embedded container, you must declare a single ServerEndpointExporter @Bean, as shown in the following example:

如果想要在使用嵌入式容器的Spring Boot应用中使用@ServerEndpoint,则必须声明单个ServerEndpointExporter @Bean,如下例所示:

  1. @Bean
  2. public ServerEndpointExporter serverEndpointExporter() {
  3. return new ServerEndpointExporter();
  4. }

The bean shown in the preceding example registers any @ServerEndpoint annotated beans with the underlying WebSocket container. When deployed to a standalone servlet container, this role is performed by a servlet container initializer, and the ServerEndpointExporter bean is not required.

前面示例中所示任何在WebSocket容器中使用@ServerEndpoint注解标注的beans。当部署到独立的servlet容器时,此角色由servlet容器初始值设定项执行,并且不需要 ServerEndpointExporter bean

浏览器端:

  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.  
  8. <title>socketjs</title>
  9. </head>
  10. <body>
  11. 发送者:<input id="fromUserId" value="2">
  12. 接收者:<input id="toUserId" value="3">
  13. <button type="button" onclick="openClick();">打开</button>
  14. <button type="button" onclick="closeClick();">关闭</button><br/>
  15. <input id="message" value="---"/>
  16. <button type="button" onclick="sendClick();">发送</button>
  17. <div id="content"></div>
  18. <script>
  19. var socket;
  20. var t;
  21. function openClick() {
  22. connection();
  23. }
  24. function closeClick() {
  25. if (socket) {
  26. socket.close();
  27. }
  28. }
  29. function sendClick() {
  30. var fromUserId = document.getElementById("fromUserId").value;
  31. var toUserId = document.getElementById("toUserId").value;
  32. var content = document.getElementById("message").value;
  33. var obj = {
  34. "fromUserId":fromUserId,
  35. "toUserId":toUserId,
  36. "content":content
  37. };
  38. document.getElementById("content").innerHTML = document.getElementById("content").innerHTML + '<br/>' + fromUserId + "说:" + content;
  39. socket.send(JSON.stringify(obj));
  40. console.log(fromUserId + "说:" + JSON.stringify(content));
  41. }
  42.  
  43. var connection = function() {
  44. console.log("connection()");
  45. var fromUserId = document.getElementById("fromUserId");
  46. var toUserId = document.getElementById("toUserId");
  47. var url = 'ws://' + window.location.host + '/ycxcode/websocket/commodity/{' + fromUserId.value + '}/{' + toUserId.value + '}';
  48. socket = new WebSocket(url);
  49. socket.onopen = onopen;
  50. socket.onmessage = onmessage;
  51. socket.onclose = onclose;
  52. socket.onerror = onerror;
  53. }
  54. //重连
  55. var reconnection = function() {
  56. //与服务器已经建立连接
  57. if (socket && socket.readyState == 1) {
  58. clearTimeout(t);
  59. } else {
  60. //已经关闭了与服务器的连接
  61. if (socket.readyState == 3) {
  62. connection();
  63. }
  64. //0正尝试与服务器建立连接,2正在关闭与服务器的连接
  65. t = setTimeout(function() {
  66. reconnection();
  67. }, 1000);
  68. }
  69. }
  70. var onopen = function() {
  71. console.log("onopen()");
  72. }
  73. var onclose = function() {
  74. console.log("onclose()");
  75. reconnection();
  76. }
  77. var onmessage = function(e) {
  78. console.log("onmessage()");
  79. if (e.data === "") return;
  80. var toUserId = document.getElementById("toUserId").value;
  81. document.getElementById("content").innerHTML = document.getElementById("content").innerHTML + '<br/>' + toUserId + "说:" + e.data;
  82. console.log(toUserId + "说:" + e.data);
  83. }
  84. var onerror = function() {
  85. console.log("error...");
  86. reconnection();
  87. }
  88.  
  89. </script>
  90. </body>
  91. </html>

【Java Web开发学习】Spring MVC整合WebSocket通信的更多相关文章

  1. 【Java Web开发学习】Spring4整合thymeleaf视图解析

    [Java Web开发学习]Spring4整合thymeleaf视图解析 目录 1.简单介绍2.简单例子 转载:https://www.cnblogs.com/yangchongxing/p/9111 ...

  2. 【Java Web开发学习】Spring MVC 使用HTTP信息转换器

    [Java Web开发学习]Spring MVC 使用HTTP信息转换器 转载:https://www.cnblogs.com/yangchongxing/p/10186429.html @Respo ...

  3. 【Java Web开发学习】Spring MVC添加自定义Servlet、Filter、Listener

    [Java Web开发学习]Spring MVC添加自定义Servlet.Filter.Listener 转载:https://www.cnblogs.com/yangchongxing/p/9968 ...

  4. 【Java Web开发学习】Spring MVC 拦截器HandlerInterceptor

    [Java Web开发学习]Spring MVC 拦截器HandlerInterceptor 转载:https://www.cnblogs.com/yangchongxing/p/9324119.ht ...

  5. 【Java Web开发学习】Spring MVC文件上传

    [Java Web开发学习]Spring MVC文件上传 转载:https://www.cnblogs.com/yangchongxing/p/9290489.html 文件上传有两种实现方式,都比较 ...

  6. 【Java Web开发学习】Spring MVC异常统一处理

    [Java Web开发学习]Spring MVC异常统一处理 文采有限,若有错误,欢迎留言指正. 转载:https://www.cnblogs.com/yangchongxing/p/9271900. ...

  7. 【Java Web开发学习】Spring JPA

    [Java Web开发学习]Spring JPA 转载:https://www.cnblogs.com/yangchongxing/p/10082864.html 1.使用容器管理类型的JPA JND ...

  8. 【Java Web开发学习】Spring加载外部properties配置文件

    [Java Web开发学习]Spring加载外部properties配置文件 转载:https://www.cnblogs.com/yangchongxing/p/9136505.html 1.声明属 ...

  9. 【Java Web开发学习】Spring环境profile

    [Java Web开发学习]Spring 环境profile 转载:http://www.cnblogs.com/yangchongxing/p/8890702.html 开发.测试.生产环境往往是不 ...

随机推荐

  1. PHP的两种选择防止sql注入

    1.使用PDO: $stmt = $pdo->prepare('SELECT * FROM user WHERE name = :name'); $stmt->execute(array( ...

  2. veu npm run dev指定host

    通常,我们可以在vue项目中的config/index.js指定host,,如下(解host的注释) 但是,在接手的目前项目中,解注释host后,npm run dev并有变为 http://192. ...

  3. pwnable.kr第二天

    3.bof 这题就是简单的数组越界覆盖,直接用gdb 调试出偏移就ok from pwn import * context.log_level='debug' payload='A'*52+p32(0 ...

  4. GitHub的高级搜索方式

    平时在学完一个知识后,需要写些 demo来进行练手,这个时候 GitHub就是最好不过的资源库了,以下整理了一些关于在 github 上面找项目的一些小技巧. 一.单条件使用 项目名称 仓库名称包含 ...

  5. 插槽在父组件和子组件间的使用(vue3.0推荐)

    子组件: 父组件: 插槽在父组件和子组件间的使用(vue3.0推荐):在外面加一个template模板

  6. 移动端App uni-app + mui 开发记录

    前言 uni-app uni-app是DCloud推出的终极跨平台解决方案,是一个使用Vue.js开发所有前端应用的框架,官网:https://uniapp.dcloud.io/ mui 号称最接近原 ...

  7. Streams:深入理解Redis5.0新特性

    概述 相较于Redis4.0,Redis5.0增加了很多新的特性,而streams是其中最重要的特性之一.streams是redis 的一种基本数据结构,它是一个新的强大的支持多播的可持久化的消息队列 ...

  8. 实战webpack系列01

    01. 采坑webpack 一.webpack初章 // 一个常见的`webpack`配置文件 const webpack = require('webpack'); const HtmlWebpac ...

  9. MySQL Last_SQL_Errno: 1062----经典错误,主键冲突

    一.基础信息 1. Centos7.4 2.MySQL 5.7.21 3.基于gtid的复制 二.异常描述 误把从节点当成主节点插入一条数据,同一条数据在主.从节点插入都进行了一次插入操作,导致主键冲 ...

  10. P1548 棋盘问题

    题目描述 设有一个N \times MN×M方格的棋盘(1≤N≤100,1≤M≤100)(1≤N≤100,1≤M≤100) 求出该棋盘中包含有多少个正方形.多少个长方形(不包括正方形). 例如:当 N ...