spring4.0为websocket通信提供了支持,包括:
- 发送和接收消息的低层级api
- 发送和接收消息的高级api
- 用来发送消息的模板
- 支持sockJS,用来解决浏览器端、服务器以及代理不支持websocket的问题
我们首先会从如何使用spring的低层级websocket api开始
按照其最简单的形式,websocket只是两个应用端进行通信的通道。位于websocket一端的应用发送消息,另一端接收消息,因为是全双工,所以每一端即可发消息也可以收消息;
websocket最常见的应用场景是在浏览器与服务器的相互通信,通常由客户端的js开启一个到服务器的连接,服务器通过这个连接发送更新给客户端;相比轮询服务器查找更新方案,这种技术更加高效和自然。
接下来我们将采用Spring低层级的WebSocket API制作一个即时聊天工具
运行环境:jdk1.7+tomcat7 框架:spring+springmvc+mybatis
第一步:首先引入支持springsocket所需jar包
<springframework.version>4.1.7.RELEASE</springframework.version> <!-- springframework -->
<!-- springsocke begin-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${springframework.version}</version>
</dependency>
<!-- springsocke end-->
第二步:定义ChatHandler消息处理类
package com.platform_manager.t_bizlog.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
/**
* 消息处理类
* 扩展AbstractWebSocketHandler类定义ChatHandler处理类
* 用来在服务端处理通过websocket传送的文本消息
* 其中AbstractWebSocketHandler包含TextWebSocketHandler和BinaryWebSocketHandler两个子类分别用于处理文本和二进制消息
* @author 韩慧兵
* @version 2016年12月6日 下午5:33:23
* @since JDK 1.6
*/
public class ChatHandler extends AbstractWebSocketHandler{
private static final Logger logger=LoggerFactory.getLogger(ChatHandler.class);
/**
* 连接建立 ,可用于资源准备
* @param session
* @throws Exception
* @author 韩慧兵
* @version 2016年12月2日 上午10:25:21
* @since JDK 1.6
*/
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.info("连接建立成功!");
}
/**
* 重载 hanleTextMessage方法用于文本抵达后,在同一个连接上返回另外一条文本消息
* @param session
* @param message
* @throws Exception
* @author 韩慧兵
* @version 2016年12月2日 上午10:19:45
* @since JDK 1.6
*/
protected void handleTextMessage(WebSocketSession session,TextMessage message) throws Exception {
logger.info("回复消息:"+message.getPayload());
Thread.sleep(2000);//模拟延时
session.sendMessage(new TextMessage("你好,我是服务端,你发送的信息是:"+message.getPayload()));//发送信息
}
/**
* 连接关闭后 ,可用于资源销毁
* @param session
* @param status
* @throws Exception
* @author 韩慧兵
* @version 2016年12月2日 上午10:24:38
* @since JDK 1.6
*/
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
logger.info("连接关闭成功,状态为"+status);
}
}
第三步:定义聊天工具socket握手拦截器
package com.platform_manager.t_bizlog.controller;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
/**
* 握手拦截器
* @author 韩慧兵
* @version 2016年12月6日 下午5:44:54
* @since JDK 1.6
*/
public class ChatHandshakeInterceptor implements org.springframework.web.socket.server.HandshakeInterceptor {
private static final Logger logger=LoggerFactory.getLogger(ChatHandler.class);
/**
* 握手前拦截
* @param request
* @param serverHttpResponse
* @param webSocketHandler
* @param map
* @return
* @throws Exception
* @author 韩慧兵
* @version 2016年12月6日 下午5:49:24
* @since JDK 1.6
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
logger.info("开始握手!");
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
logger.info("结束握手!");
}
}
第四步:通过Java配置Spring,启用websocket并映射消息处理器
package com.platform_manager.t_bizlog.controller;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* 通过Java配置Spring,启用websocket并映射消息处理器
* @author 韩慧兵
* @version 2016年12月6日 下午5:45:08
* @since JDK 1.6
*/
@Configuration //配置类
@EnableWebMvc
@EnableWebSocket //声明支持websocket
public class ChatWebSocketConfig implements WebSocketConfigurer{
/**
* 注册消息处理器
* @param registry
* @author 韩慧兵
* @version 2016年12月2日 上午10:50:26
* @since JDK 1.6
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//将marcoHandler()映射至"/marco.do"
registry.addHandler(chatHandler(),"/chat.do").addInterceptors(new ChatHandshakeInterceptor());
//将marcoHandler()映射至"/sockjs/marco.do"
registry.addHandler(chatHandler(),"/sockjs/chat.do").addInterceptors(new ChatHandshakeInterceptor()).withSockJS();
}
/**
* 声明MarcoHandler bean
* @return
* @author 韩慧兵
* @version 2016年12月2日 上午10:49:38
* @since JDK 1.6
*/
@Bean
public ChatHandler chatHandler()
{
return new ChatHandler();
}
}
第五步:springmvc中添加对ChatWebSocketConfig类的自动扫描
<context:component-scan base-package="包位置">
第六步:制定客户端聊天窗体界面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@include file="/mastery/page/common/header.jsp"%>
<title>用户列表</title>
<script src="http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js"></script>
<script type="text/javascript">
$(function() {
var websocket;
//打开socket
if ('WebSocket' in window) {
//alert("WebSocket");
websocket = new WebSocket(
"ws://localhost:8080/mastery_platform_manager/chat.do");
} else if ('MozWebSocket' in window) {
alert("MozWebSocket");
websocket = new MozWebSocket("ws://echo");
} else {
alert("SockJS");
websocket = new SockJS(
"http://localhost:8080/mastery_platform_manager/sockjs/chat.do");
}
//处理连接开启事件
websocket.onopen = function(evnt) {
$("#tou").html("链接服务器成功!")
};
//处理信息
websocket.onmessage = function(evnt) {
$("#msg").html($("#msg").html() + "<br/>" + evnt.data);
};
websocket.onerror = function(evnt) {
};
//处理关闭事件
websocket.onclose = function(evnt) {
$("#tou").html("与服务器断开了链接!")
}
$('#send').bind('click', function() {
send();
});
function send() {
if (websocket != null) {
var message = document.getElementById('message').value;
websocket.send(message); //调用后台handleTextMessage方法
} else {
alert('未与服务器链接.');
}
}
});
</script>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">webSocket即时聊天工具 -【使用Spring低层级WebSocket
API版】</h3>
</div>
<div class="panel-body" id="msg">
服务器状态:
<div class="page-header" id="tou"></div>
聊天信息如下:
</div>
</div>
<p>
<div class="col-lg">
<div class="input-group">
<input type="text" class="form-control" placeholder="发送信息..."
id="message"> <span class="input-group-btn">
<button class="btn btn-default" type="button" id="send">发送</button>
</span>
</div>
</div>
<%@include file="/mastery/page/common/footer.jsp"%>
实现效果如下:
附:
笔者在编写示例中,遇到了很多问题,查看了各种偏方
1、jdk
What you’ll need
About 15 minutes
A favorite text editor or IDE
JDK 1.8 or later
Gradle 2.3+ or Maven 3.0+摘自spring官网:http://spring.io/guides/gs/messaging-stomp-websocket/
官网描述为1.8+,笔者亲测1.7.0可以使用,最开始笔者采用1.6测试,会遇到如下问题提示
信息: JSR 356 WebSocket (Java WebSocket 1.0) support is not available when running on Java 6. To suppress this message, run Tomcat on Java 7, remove the WebSocket JARs from $CATALINA_HOME/lib or add the WebSocketJARs to the tomcat.util.scan.DefaultJarScanner.jarsToSkip property in $CATALINA_BASE/conf/catalina.properties. Note that the deprecated Tomcat 7 WebSocket API will be available.
2、web.xml
网上有很多文章要求修改xml的申明,笔者并未修改,笔者web.xml声明如下
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
其他
1、ws:// 表明这是个基本的websocket连接,如果是安全websocket的话,协议的前缀将会是wss://
2、服务端关闭连接或浏览器转向其他页面都会关闭连接
3、防火墙代理通常会限制除http以外的流量,他们有可能不支持或还没有配置websocket通信
4、Sockjs是websocket技术的一种模拟,它的底层非常智能,sockjs会优先选用websocket,如果websocket不支持的话他会从以下方案中挑选最优的可行方案
XHR流、XDR流、iFrame事件源、iFrame HTML文件、XHR轮询、XDR轮询、iFrame XHR轮询、JSONP轮询
Websocket提供了浏览器和服务器之间的通信方式,当运行环境不支持webscoket的时候,socket提供了备选方案。但不管哪种场景,该篇文章所描述的通信方式在实际应用中都显得层级过低。下一篇,我们将在websocket之上使用stomp,为浏览器和服务器之间的通信添加恰当的消息语义