本帖最后由 鱼丸儿 于 2018-3-30 15:32 编辑
WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。 WebSocket通信协议于2011年被IETF定为标准RFC 6455,WebSocketAPI被W3C定为标准。 在WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。 本文将说明在IntellijIDEA下使用Gradle构建SpringMVC+WebSocket实现消息推送.为了更好的阅读体验,请点击 阅读原文 导入依赖在build.gradle中导入denpendies [Java] 纯文本查看 复制代码 // ------------------------ Spring SpringMVC start -------------------------[/align][align=left]compile group: 'org.springframework', name: 'spring-webmvc', version: '4.2.4.RELEASE'
compile group: 'org.springframework', name: 'spring-context-support', version: '4.2.4.RELEASE'
// ------------------------ Spring SpringMVC end -------------------------
// ------------------------ WebSocket start -------------------------
compile group: 'org.springframework', name: 'spring-websocket', version: '4.2.4.RELEASE'
compile group: 'org.springframework', name: 'spring-messaging', version: '4.2.4.RELEASE'
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.1.0'
// ------------------------ WebSocket end -------------------------
compile group: 'log4j', name: 'log4j', version: '1.2.17'
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
此处需要注意的是Spring从4.0+版本开始支持WebSocket,而servlet-api需要为3.0+版本 [XML] 纯文本查看 复制代码 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
[url=http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd]http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd[/url]"
version="3.0">
<!-- 初始化spring 容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring/applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置前端控制器 -->
<servlet>
<servlet-name>index-dispather</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:config/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>index-dispather</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 解决post乱码问题 -->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
此处需要给servlet及filter添加异步<async-supported>true</async-supported>. [XML] 纯文本查看 复制代码 <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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
[url=http://www.springframework.org/schema/beans/spring-beans.xsd]http://www.springframework.org/schema/beans/spring-beans.xsd[/url]
[url=http://www.springframework.org/schema/context]http://www.springframework.org/schema/context[/url]
[url=http://www.springframework.org/schema/context/spring-context.xsd]http://www.springframework.org/schema/context/spring-context.xsd[/url]
[url=http://www.springframework.org/schema/mvc]http://www.springframework.org/schema/mvc[/url]
[url=http://www.springframework.org/schema/mvc/spring-mvc.xsd]http://www.springframework.org/schema/mvc/spring-mvc.xsd[/url]">
<!-- 自动扫描控制器,webSocket -->
<context:component-scan base-package="com.lhalcyon.king.controller,com.lhalcyon.king.socket"/>
<!-- 视图渲染 -->
<bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 控制器映射器和控制器适配器 -->
<mvc:annotation-driven>
</mvc:annotation-driven>
<!-- 静态资源映射器 -->
<mvc:resources mapping="/statics/**" location="/WEB-INF/statics/" />
</beans>
此处需配置扫描controller和websocket所在包 还有一个applicationContext-websocket.xml配置文件我们在代码实现中说明 代码实现握手拦截器HandshakeInterceptor 拦截器说明 An interceptorto copy information from the HTTP session to the “handshake
attributes” map to made available via WebSocketSession.getAttributes()
Copies a subset or all HTTP session attributes and/or the HTTP session id 拦截器主要用于用户登录标识的记录,便于后面获取指定用户的会话标识并向指定用户发送消息, 这里我们继承HttpSessionHandshakeInterceptor [Java] 纯文本查看 复制代码 [/align][align=left] public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private Logger logger = Logger.getLogger(HandshakeInterceptor.class);
// 握手前
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
logger.info("++ HandshakeInterceptor: beforeHandshake ++"+attributes);
return super.beforeHandshake(request, response, wsHandler, attributes);
}
// 握手后
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
logger.info("++ HandshakeInterceptor: afterHandshake ++");
super.afterHandshake(request, response, wsHandler, ex);
}
}
beforeHandshake(..)
Invoked before the handshake is processed.
afterHandshake(..)
Invoked after the handshake is done. The response status and headers indicate the results of the handshake, i.e. whether it was successful or not.
两个方法调用时机均为字面所述,握手前后分别调用.主要是在握手前后去做一些事,比如将需要的数据设置到attributes里,之后在WebSocketHandler的session中获取这些数据. 处理类WebSocketHandler [Java] 纯文本查看 复制代码 public class MyWebSocketHandler implements WebSocketHandler {
private static final Logger log = Logger.getLogger(MyWebSocketHandler.class);
// 保存所有的用户session
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("connect websocket success.......");
users.add(session);
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
Gson gson = new Gson();
// 将消息JSON格式通过Gson转换成Map
// message.getPayload().toString() 获取消息具体内容
Map<String, Object> msg = gson.fromJson(message.getPayload().toString(),
new TypeToken<Map<String, Object>>() {}.getType());
log.info("handleMessage......."+message.getPayload()+"..........."+msg);
String content = message.getPayload().toString();
// 处理消息 msgContent消息内容
TextMessage textMessage = new TextMessage(content, true);
// 调用方法(发送消息给所有人)
sendMsgToAllUsers(textMessage);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.warn("handleTransportError");
users.remove(session);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
log.info("connect websocket closed.......");
users.remove(session);
}
// 给所有用户发送 信息
public void sendMsgToAllUsers(WebSocketMessage<?> message) throws Exception{
for (WebSocketSession user : users) {
user.sendMessage(message);
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
以下对主要方法进行说明: afterConnectionEstablished(..) 连接建立后调用,常用于记录用户的连接标识,便于后面信息发送. handleTextMessage(..) 对消息进行处理. handleTransportError(..) 连接异常处理.需要关闭出错会话连接 afterConnectionClosed(..) 连接关闭处理 此处我们对消息的处理很简单,即在接受消息后发送给所有连接的用户,类似一个匿名群聊室. 注册完成了WebSocket处理类,还需要对其进行注册生效.这里有两种方式,择其一即可.
创建配置类,并通过注解注册[Java] 纯文本查看 复制代码 @Configuration
@EnableWebMvc
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//前台 可以使用websocket环境
registry.addHandler(myWebSocketHandler(),"/websocket").addInterceptors(new HandshakeInterceptor()).setAllowedOrigins("*");
//前台 不可以使用websocket环境,则使用sockjs进行模拟连接
registry.addHandler(myWebSocketHandler(), "/sockjs/websocket").addInterceptors(new HandshakeInterceptor())
.withSockJS();
}
// websocket 处理类
@Bean
public WebSocketHandler myWebSocketHandler(){
return new MyWebSocketHandler();
}
}
同时还需要配置上文提到过的Spring扫描配置类. [XML] 纯文本查看 复制代码 !-- 自动扫描控制器,webSocket -->
<context:component-scan base-package="com.lhalcyon.king.controller,com.lhalcyon.king.socket"/>
通过xml配置注册applicationContext-websocket.xml [XML] 纯文本查看 复制代码 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
[url=http://www.springframework.org/schema/beans]http://www.springframework.org/schema/beans[/url]
[url=http://www.springframework.org/schema/beans/spring-beans-4.0.xsd]http://www.springframework.org/schema/beans/spring-beans-4.0.xsd[/url]
[url=http://www.springframework.org/schema/websocket]http://www.springframework.org/schema/websocket[/url]
[url=http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd]http://www.springframework.org/s ... g-websocket-4.0.xsd[/url]">
<!-- websocket处理类 -->
<bean id="myHandler" class="com.lhalcyon.king.socket.MyWebSocketHandler"/>
<!-- 握手接口/拦截器 -->
<bean id="myInterceptor" class="com.lhalcyon.king.socket.HandshakeInterceptor"/>
<websocket:handlers>
<websocket:mapping path="/websocket" handler="myHandler"/>
<websocket:handshake-interceptors>
<ref bean="myInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
<!-- 注册 sockJS -->
<websocket:handlers>
<websocket:mapping path="/websocket" handler="myHandler"/>
<websocket:handshake-interceptors>
<ref bean="myInterceptor"/>
</websocket:handshake-interceptors>
<websocket:sockjs />
</websocket:handlers>
</beans>
以上服务端代码实现基本完成,接下来对客户端测试页面做一个简单的实现. 客户端页面index.jsp [HTML] 纯文本查看 复制代码 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE HTML>
<html>
<head>
<title>首页</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
<!-- 引入 JQuery -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<!-- 引入 sockJS -->
<script type="text/javascript" src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js" ></script>
<script type="text/javascript">
$(function() {
var websocket;
// 首先判断是否 支持 WebSocket
var url = 'ws://' + window.location.host + '/word-king/websocket';
if('WebSocket' in window) {
websocket = new WebSocket(url);
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket(url);
} else {
url = "http://"+ window.location.host +"/word-king/sockjs/websocket";
websocket = new SockJS(url);
}
// 打开时
websocket.onopen = function(evnt) {
console.log(" websocket.onopen ");
};
// 处理消息时
websocket.onmessage = function(evnt) {
$("#msg").append("<p>(<font color='red'>" + evnt.data + "</font>)</p>");
console.log(" websocket.onmessage ");
};
websocket.onerror = function(evnt) {
alert("onerror");
console.log(" websocket.onerror ");
};
websocket.onclose = function(evnt) {
console.log(" websocket.onclose ");
alert("onclose");
};
// 点击了发送消息按钮的响应事件
$("#TXBTN").click(function(){
// 获取消息内容
var text = $("#tx").val();
// 判断
if(text == null || text == ""){
alert(" content can not empty!!");
return false;
}
var msg = {
msgContent: text,
postsId: 1
};
// 发送消息
websocket.send(JSON.stringify(msg));
});
});
</script>
</head>
<body>
<!-- 最外边框 -->
<div style="margin: 20px auto; border: 1px solid blue; width: 300px; height: 500px;">
<!-- 消息展示框 -->
<div id="msg" style="width: 100%; height: 70%; border: 1px solid yellow;overflow: auto;"></div>
<!-- 消息编辑框 -->
<textarea id="tx" style="width: 100%; height: 20%;"></textarea>
<!-- 消息发送按钮 -->
<button id="TXBTN" style="width: 100%; height: 8%;">发送数据</button>
</div>
</body>
</html>
需要注意的是此处引入JQuery时,如果是本地的文件,可能存在无效的情况,需要去设置静态资源映射路径,可自行 本文采用的是引入在线地址 1
2 | <!-- 引入 JQuery -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.5/jquery.min.js"></script> |
地址写入后需Download下来 代码不高亮警告后即能生效.
|