【Java Web开发学习】Spring MVC整合WebSocket通信
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
构造函数
- 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个方法,用于处理不同的事件
- public interface WebSocketHandler {
- void afterConnectionEstablished(WebSocketSession session) throws Exception;
- void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
- void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
- void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
- boolean supportsPartialMessages();
- }
比实现接口更为简单的方式是扩展AbstractWebSocketHandler抽象类,下面是抽象类的代码
- public abstract class AbstractWebSocketHandler implements WebSocketHandler {
- @Override
- public void afterConnectionEstablished(WebSocketSession session) throws Exception {
- }
- @Override
- public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
- if (message instanceof TextMessage) {
- handleTextMessage(session, (TextMessage) message);
- }
- else if (message instanceof BinaryMessage) {
- handleBinaryMessage(session, (BinaryMessage) message);
- }
- else if (message instanceof PongMessage) {
- handlePongMessage(session, (PongMessage) message);
- }
- else {
- throw new IllegalStateException("Unexpected WebSocket message type: " + message);
- }
- }
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- }
- protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
- }
- protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
- }
- @Override
- public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
- }
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
- }
- @Override
- public boolean supportsPartialMessages() {
- return false;
- }
- }
我们以文本消息为例直接继承TextWebSocketHandler类
TextWebSocketHandler继承自AbstractWebSocketHandler类用来处理文本消息。
BinaryWebSocketHandler继承自AbstractWebSocketHandler类用来处理二进制消息。
- public class CommoditySocket extends TextWebSocketHandler {
- @Override
- public void afterConnectionEstablished(WebSocketSession session) throws Exception {
- System.out.println("open...");
- }
- @Override
- public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
- System.out.println("Closed");
- }
- @Override
- protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
- super.handleTextMessage(session, message);
- System.out.println("收到消息:" + message.getPayload());
- }
- }
消息处理类完成了,来看配置文件
首先追加websocket命名空间
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:task="http://www.springframework.org/schema/task"
- xmlns:websocket="http://www.springframework.org/schema/websocket"
- xsi:schemaLocation="http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.3.xsd
- http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.3.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
再追加如下配置,注意path的路径这个是请求的地址,ws://域名:端口/项目名/socket/text
- <websocket:handlers>
- <websocket:mapping handler="textHandler" path="/socket/text"/>
- </websocket:handlers>
- <bean id="textHandler" class="cn.ycx.web.socket.TextHandler"></bean>
浏览器端使用标准的WebSocket
- var url = 'ws://' + window.location.host + '/ycxxml/socket/text'; //配置文件中配的path有关
- var socket = new WebSocket(url);
- socket.onopen = function() {
- console.log("open...");
- socket.send("start talk...")
- }
- socket.onmessage = function(e) {
- console.log("服务器发来:" + e.data);
- document.write("" + e.data + "<br/>");
- }
- socket.onclose = function() {
- console.log("close...");
- }
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信息,这个信息可以是文本格式,也可以是二进制格式。
服务器端代码:
- package cn.ycx.web.socket;
- import java.io.IOException;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentMap;
- import javax.websocket.CloseReason;
- import javax.websocket.OnClose;
- import javax.websocket.OnError;
- import javax.websocket.OnMessage;
- import javax.websocket.OnOpen;
- import javax.websocket.Session;
- import javax.websocket.server.PathParam;
- import javax.websocket.server.ServerEndpoint;
- import org.springframework.web.socket.server.standard.SpringConfigurator;
- import com.alibaba.fastjson.JSON;
- /**
- * 交易
- * @author 杨崇兴
- *
- */
- @ServerEndpoint(value="/websocket/commodity/{fromUserId}/{toUserId}", configurator = SpringConfigurator.class)
- public class WebSocketServer {
- // 已经建立链接的对象缓存起来
- private static ConcurrentMap<Integer, WebSocketServer> serverMap = new ConcurrentHashMap<Integer, WebSocketServer>();
- // 当前session
- private Session currentSession;
- @OnOpen
- public void onOpen(Session session, @PathParam("fromUserId") int fromUserId, @PathParam("toUserId") int toUserId) throws IOException {
- this.currentSession = session;
- serverMap.put(fromUserId, this);//建立链接时,缓存对象
- }
- @OnClose
- public void onClose(Session session, CloseReason reason) {
- System.out.println(reason.toString());
- if (serverMap.containsValue(this)) {
- Iterator<Integer> keys = serverMap.keySet().iterator();
- int userId = 0;
- while(keys.hasNext()) {
- userId = keys.next();
- if (serverMap.get(userId) == this) {
- serverMap.remove(userId, this);//关闭链接时,删除缓存对象
- }
- }
- }
- this.currentSession = null;
- try {
- session.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @OnMessage()
- @SuppressWarnings("unchecked")
- public void onMessage(String json) {
- HashMap<String, String> map = JSON.parseObject(json, HashMap.class);
- int fromUserId = Integer.parseInt(map.get("fromUserId"));
- int toUserId = Integer.parseInt(map.get("toUserId"));
- String content = map.get("content").toString();
- WebSocketServer server = serverMap.get(toUserId);//若存在则用户在线,否在用户不在线
- if (server != null && server.currentSession.isOpen()) {
- if (fromUserId != toUserId) {
- try {
- server.currentSession.getBasicRemote().sendText(content);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- @OnError
- public void onError(Throwable t) {
- t.printStackTrace();
- }
- }
注意:修改了原来的问题,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,如下例所示:
- @Bean
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
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
浏览器端:
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>socketjs</title>
- </head>
- <body>
- 发送者:<input id="fromUserId" value="2">
- 接收者:<input id="toUserId" value="3">
- <button type="button" onclick="openClick();">打开</button>
- <button type="button" onclick="closeClick();">关闭</button><br/>
- <input id="message" value="---"/>
- <button type="button" onclick="sendClick();">发送</button>
- <div id="content"></div>
- <script>
- var socket;
- var t;
- function openClick() {
- connection();
- }
- function closeClick() {
- if (socket) {
- socket.close();
- }
- }
- function sendClick() {
- var fromUserId = document.getElementById("fromUserId").value;
- var toUserId = document.getElementById("toUserId").value;
- var content = document.getElementById("message").value;
- var obj = {
- "fromUserId":fromUserId,
- "toUserId":toUserId,
- "content":content
- };
- document.getElementById("content").innerHTML = document.getElementById("content").innerHTML + '<br/>' + fromUserId + "说:" + content;
- socket.send(JSON.stringify(obj));
- console.log(fromUserId + "说:" + JSON.stringify(content));
- }
- var connection = function() {
- console.log("connection()");
- var fromUserId = document.getElementById("fromUserId");
- var toUserId = document.getElementById("toUserId");
- var url = 'ws://' + window.location.host + '/ycxcode/websocket/commodity/{' + fromUserId.value + '}/{' + toUserId.value + '}';
- socket = new WebSocket(url);
- socket.onopen = onopen;
- socket.onmessage = onmessage;
- socket.onclose = onclose;
- socket.onerror = onerror;
- }
- //重连
- var reconnection = function() {
- //与服务器已经建立连接
- if (socket && socket.readyState == 1) {
- clearTimeout(t);
- } else {
- //已经关闭了与服务器的连接
- if (socket.readyState == 3) {
- connection();
- }
- //0正尝试与服务器建立连接,2正在关闭与服务器的连接
- t = setTimeout(function() {
- reconnection();
- }, 1000);
- }
- }
- var onopen = function() {
- console.log("onopen()");
- }
- var onclose = function() {
- console.log("onclose()");
- reconnection();
- }
- var onmessage = function(e) {
- console.log("onmessage()");
- if (e.data === "") return;
- var toUserId = document.getElementById("toUserId").value;
- document.getElementById("content").innerHTML = document.getElementById("content").innerHTML + '<br/>' + toUserId + "说:" + e.data;
- console.log(toUserId + "说:" + e.data);
- }
- var onerror = function() {
- console.log("error...");
- reconnection();
- }
- </script>
- </body>
- </html>
【Java Web开发学习】Spring MVC整合WebSocket通信的更多相关文章
- 【Java Web开发学习】Spring4整合thymeleaf视图解析
[Java Web开发学习]Spring4整合thymeleaf视图解析 目录 1.简单介绍2.简单例子 转载:https://www.cnblogs.com/yangchongxing/p/9111 ...
- 【Java Web开发学习】Spring MVC 使用HTTP信息转换器
[Java Web开发学习]Spring MVC 使用HTTP信息转换器 转载:https://www.cnblogs.com/yangchongxing/p/10186429.html @Respo ...
- 【Java Web开发学习】Spring MVC添加自定义Servlet、Filter、Listener
[Java Web开发学习]Spring MVC添加自定义Servlet.Filter.Listener 转载:https://www.cnblogs.com/yangchongxing/p/9968 ...
- 【Java Web开发学习】Spring MVC 拦截器HandlerInterceptor
[Java Web开发学习]Spring MVC 拦截器HandlerInterceptor 转载:https://www.cnblogs.com/yangchongxing/p/9324119.ht ...
- 【Java Web开发学习】Spring MVC文件上传
[Java Web开发学习]Spring MVC文件上传 转载:https://www.cnblogs.com/yangchongxing/p/9290489.html 文件上传有两种实现方式,都比较 ...
- 【Java Web开发学习】Spring MVC异常统一处理
[Java Web开发学习]Spring MVC异常统一处理 文采有限,若有错误,欢迎留言指正. 转载:https://www.cnblogs.com/yangchongxing/p/9271900. ...
- 【Java Web开发学习】Spring JPA
[Java Web开发学习]Spring JPA 转载:https://www.cnblogs.com/yangchongxing/p/10082864.html 1.使用容器管理类型的JPA JND ...
- 【Java Web开发学习】Spring加载外部properties配置文件
[Java Web开发学习]Spring加载外部properties配置文件 转载:https://www.cnblogs.com/yangchongxing/p/9136505.html 1.声明属 ...
- 【Java Web开发学习】Spring环境profile
[Java Web开发学习]Spring 环境profile 转载:http://www.cnblogs.com/yangchongxing/p/8890702.html 开发.测试.生产环境往往是不 ...
随机推荐
- PHP的两种选择防止sql注入
1.使用PDO: $stmt = $pdo->prepare('SELECT * FROM user WHERE name = :name'); $stmt->execute(array( ...
- veu npm run dev指定host
通常,我们可以在vue项目中的config/index.js指定host,,如下(解host的注释) 但是,在接手的目前项目中,解注释host后,npm run dev并有变为 http://192. ...
- pwnable.kr第二天
3.bof 这题就是简单的数组越界覆盖,直接用gdb 调试出偏移就ok from pwn import * context.log_level='debug' payload='A'*52+p32(0 ...
- GitHub的高级搜索方式
平时在学完一个知识后,需要写些 demo来进行练手,这个时候 GitHub就是最好不过的资源库了,以下整理了一些关于在 github 上面找项目的一些小技巧. 一.单条件使用 项目名称 仓库名称包含 ...
- 插槽在父组件和子组件间的使用(vue3.0推荐)
子组件: 父组件: 插槽在父组件和子组件间的使用(vue3.0推荐):在外面加一个template模板
- 移动端App uni-app + mui 开发记录
前言 uni-app uni-app是DCloud推出的终极跨平台解决方案,是一个使用Vue.js开发所有前端应用的框架,官网:https://uniapp.dcloud.io/ mui 号称最接近原 ...
- Streams:深入理解Redis5.0新特性
概述 相较于Redis4.0,Redis5.0增加了很多新的特性,而streams是其中最重要的特性之一.streams是redis 的一种基本数据结构,它是一个新的强大的支持多播的可持久化的消息队列 ...
- 实战webpack系列01
01. 采坑webpack 一.webpack初章 // 一个常见的`webpack`配置文件 const webpack = require('webpack'); const HtmlWebpac ...
- MySQL Last_SQL_Errno: 1062----经典错误,主键冲突
一.基础信息 1. Centos7.4 2.MySQL 5.7.21 3.基于gtid的复制 二.异常描述 误把从节点当成主节点插入一条数据,同一条数据在主.从节点插入都进行了一次插入操作,导致主键冲 ...
- P1548 棋盘问题
题目描述 设有一个N \times MN×M方格的棋盘(1≤N≤100,1≤M≤100)(1≤N≤100,1≤M≤100) 求出该棋盘中包含有多少个正方形.多少个长方形(不包括正方形). 例如:当 N ...