由于浏览器的历史遗留性问题,不是所有的浏览器都是支持WebSocket的,尤其是IE10以下,所以才出现了SockJS这样一个框架,它的原理也很简单,就是如果你的浏览器支持WebSocket那么他就使用webSocket协议通信,入股不支持就使用流传输或者轮询的方式,这样也保证了资源的最大利用率。
1、websocket相关pom文件配置
<!--websocket-->
<!-- Provided Websocket API, because tomcat has its own implementation -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--相关jar-->
<!-- Json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
2、WebSocketConfig----配置WebSocket访问的地址
package com.bms.web.notice.websocket.config;
import com.bms.web.notice.websocket.handler.SystemWebSocketHandler;
import com.bms.web.notice.websocket.interceptor.WebsocketHandshakeInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* Created by zhu_kai1 on 2017/2/23.
*/
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebsocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
// webSocket
private static final String WEBSOCKET_SERVER ="/webSocketServer";
private static final String ECHO ="/echo";
// 不支持webSocket的话用sockjs
private static final String SOCKJS ="/sockjs/webSocketServer";
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//支持websocket 的访问链接
registry.addHandler(systemWebSocketHandler(), WEBSOCKET_SERVER).addInterceptors(handshakeInterceptor());
registry.addHandler(systemWebSocketHandler(), ECHO).addInterceptors(handshakeInterceptor());
//不支持websocket的访问链接
registry.addHandler(systemWebSocketHandler(), SOCKJS).addInterceptors(handshakeInterceptor()).withSockJS();
}
@Bean
public WebSocketHandler systemWebSocketHandler(){
return new SystemWebSocketHandler();
}
@Bean
public HandshakeInterceptor handshakeInterceptor(){
return new WebsocketHandshakeInterceptor();
}
// Allow serving HTML files through the default Servlet
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
3、HandshakeInterceptor---握手拦截器
package com.bms.web.notice.websocket.interceptor;
import com.msk.sso.client.bean.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* Created by zhu_kai1 on 2017/2/23.
*/
public class WebsocketHandshakeInterceptor implements HandshakeInterceptor{
private static Logger logger = LoggerFactory.getLogger(WebsocketHandshakeInterceptor.class);
public WebsocketHandshakeInterceptor() {
}
// 初次握手访问前
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
if (request instanceof ServletServerHttpRequest) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
//使用userName区分WebSocketHandler,以便定向发送消息
User loginUser= (User) servletRequest.getSession().getAttribute("loginUser");
//存入数据,方便在hander中获取,这里只是在方便在webSocket中存储了数据,并不是在正常的httpSession中存储,想要在平时使用的session中获得这里的数据,需要使用session 来存储一下
if(null !=loginUser){
map.put("userName", loginUser.getUserLogin());
logger.info("当前的登陆者为:{}", loginUser.getUserLogin());
}else{
logger.error("没有获取session中的当前登陆者信息");
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
}
}
4、SystemWebSocketHandler----消息处理
package com.bms.web.notice.websocket.handler;
import com.bms.web.notice.bean.result.NoticeResult;
import com.bms.web.notice.service.CommonService;
import com.framework.base.rest.result.BaseRestPaginationResult;
import com.framework.core.utils.StringUtils;
import com.framework.exception.SystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by zhu_kai1 on 2017/2/23.
*/
@Component
public class SystemWebSocketHandler implements WebSocketHandler {
@Autowired
private CommonService commonService;
public static final String USERNAME = "userName";
private static Logger logger = LoggerFactory.getLogger(SystemWebSocketHandler.class);
protected final static List<WebSocketSession> sessions = Collections.synchronizedList(new ArrayList<WebSocketSession>());
public SystemWebSocketHandler() {
}
// 连接建立后处理
@Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
logger.info("webSocket连接已建立");
sessions.add(webSocketSession);
String userLogin = (String) webSocketSession.getAttributes().get(USERNAME);
BaseRestPaginationResult<NoticeResult> result = null;
if(StringUtils.isNotEmpty(userLogin)){
result = commonService.getNoticeInfo(userLogin);
}
if(null !=result){
sendMessageToAll(new TextMessage(StringUtils.toString(result.getTotal())));
}
}
// 接收客户端消息,并发送出去
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
logger.info("发送消息" + webSocketMessage.toString());
}
// 抛出异常时处理
@Override
public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
if (webSocketSession.isOpen()) {
webSocketSession.close();
}
sessions.remove(webSocketSession);
logger.info("webSocket异常处理" + throwable.getMessage());
throw new SystemException(throwable);
}
// 连接关闭后处理
@Override
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
logger.info("webSocket连接已关闭......" + closeStatus.getReason());
sessions.remove(webSocketSession);
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
*
* @param message
*/
public void sendMessageToAll(TextMessage message) {
for (WebSocketSession session : sessions) {
try {
if (session.isOpen()) {
session.sendMessage(message);
}
} catch (IOException e) {
throw new SystemException(e.getMessage());
}
}
}
}
5、在对应的spring-mvc.xml配置文件中需要扫描websocketConfig
<context:component-scan base-package="com.bms.web" use-default-filters="false">
<context:include-filter type="regex" expression="com\.bms\.web\.notice\.websocket\..*"/>
</context:component-scan>
6、jsp需要引用对应的sockjs
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
7、js调用方法
webSocket: function () {
var host = window.location.host;
var websocket;
var url = null;
if ('WebSocket' in window) {
url = "ws://" + host + Main.contextPath + "/webSocketServer";
websocket = new WebSocket(url);
} else if ('MozWebSocket' in window) {
url = "ws://" + host + Main.contextPath + "/echo";
websocket = new MozWebSocket(url);
} else {
url = Main.contextPath + "/sockjs/webSocketServer";
websocket = new SockJS(url);//建立连接
}
websocket.onopen = function (event) {
};
websocket.onmessage = function (event) {
$("span#noticeTotal").text(event.data);
$("span#notice").text("共" + event.data + "条通知");
};
websocket.onerror = function (event) {
};
websocket.onclose = function (event) {
$.alertMessage.error("与webSocket服务器断开了连接");
}
}
ok,到此大致的websocket已搭建好,但是各位可能会遇到以下问题,请找到对应的错误解决,以下解决方法也是从网上查询到的。
问题1:统计了一下大家遇到第一个问题就是连接websocket时候报404错误
先检查连接websocket的url格式:ws://localhost:8080/web/webSocketServer,这个webSocketServer要匹配websocketConfig中的
registry.addHandler(systemWebSocketHandler(), "/webSocketServer").addInterceptors(handshakeInterceptor());
其次检查下Spring配置文件是否有加这个tag:<mvc:annotation-driven/>(加这个会出现中文乱码,下面会讲到),使用Spring websocket需要这个tag支持
当Spring配置文件有使用<context:component-scan/>扫描包,这个tag<context:annotation-config/>可以不去掉。
问题2:连接websocket时候报200,说明已经进入拦截器握手成功,但是没连接上websocket
如果websocket有配置自己定义的拦截器,先检查下拦截器beforeHandshake这个方法,这个方法有个参数Map<String, Object> attributes,不能给这个map的value设成null,否则进不到自己Handler下的这个方法afterConnectionEstablished,就会报200问题3:连接websocket时候,如果缺少配置会报415 Unsupported Media Type请求的格式不受请求页面的支持错误
当用户发送请求后,@Requestbody 注解会读取请求body中的数据,默认的请求转换器HttpMessageConverter通过获取请求头Header中的Content-Type来 确认请求头的数据格式,从而来为请求数据适配合适的转换器。例如contentType:applicatin/json,那么转换器会适配 MappingJacksonHttpMessageConverter。响应时候的时候同理,@Responsebody注解会启用 HttpMessageConverter,通过检测Header中Accept属性来适配的响应的转换器。
当在使用SpringMVC做服务器数据接收时,尤其是在做Ajax请求的时候,尤其要注意contentType属性,和accepte 属性的设置,在springmvc-config.xml中配置好相应的转换器。
添加相应转换器:
<bean id="stringHttpMessageConverter"
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter" />新增的
<ref bean="byteArrayHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter4JS" />
</list>
</property>
</bean>
问题4:网上例子都有说要在web.xml下的servlet和filter里面加上<async-supported>true</async-supported>
没有影响websocket是可以连接成功的(小编就是这种方式实现的),可以不用加,这个是用来支持异步的servlet3.x,建议不用加,除非有用到这个特性(小编没有试,这个可以自行对比下)
问题5:添加<mvc:annotation-driven/>这个出现中文乱码
class=" org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> //这是根本原因
<property name="messageConverters">
<list>
<ref bean="stringHttpMessageConverter" />
<ref bean="byteArrayHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter" />
<ref bean="jsonHttpMessageConverter4JS" />
</list>
</property>
</bean>
问题6:启动时候出现这个Factory method 'webSocketHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No suitable default RequestUpgradeStrategy found
说明你的容器不支持websocket协议Tomcat7,0.26之后才支持websocket
Jboss as 7不支持websocket,需要要安装插件,可以直接升级到wildfly8支持websocket