由于瀏覽器的歷史遺留性問題,不是所有的瀏覽器都是支持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
新聞熱點
疑難解答