由于浏览器的历史遗留性问题,不是所有的浏览器都是支持WebSocket的,尤其是IE10以下,所以才出现了SockJS这样一个框架,它的原理也很简单,就是如果你的浏览器支持WebSocket那么他就使用webSocket协议通信,入股不支持就使用流传输或者轮询的方式,这样也保证了资源的最大利用率。
<!--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@EnableWebSocketpublic 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. */@Componentpublic 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/>这个出现中文乱码
刚开始时候,所有浏览器都出现中文乱码,后来解决在Chrome浏览不会出现中文乱码,但是在FF下会出现。原因有三个:第一,MessageConverter转换器没配置相应的<property name="supportedMediaTypes">属性,第二,bean和tag的先后顺序不对,第三,当使用<mvc:annotation-driven/>这个tag时候,请求处理器就会变成RequestMappingHandlerAdapter,跟进代码就会发现不是采用AnnotationMethodHandlerAdapter,所以配置时候要改成:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter统一解决办法是:要注意bean和tag的先后顺序<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><context:component-scan base-package="扫描Spring controller包路径" /><context:component-scan base-package="扫描websocket包路径"/><context:annotation-config /> //这个标注可以不加<mvc:annotation-driven/>//这个tag一定要放在上面代码最后,这是也是乱码根源之一问题6:启动时候出现这个Factory method 'webSocketHandlerMapping' threw exception; nested exception isjava.lang.IllegalStateException: No suitable default RequestUpgradeStrategy found
说明你的容器不支持websocket协议Tomcat7,0.26之后才支持websocket
Jboss as 7不支持websocket,需要要安装插件,可以直接升级到wildfly8支持websocket
新闻热点
疑难解答