本文源代碼下載:spring_websocket
手冊文檔下載:websocket.doc
WebSocket_百度百科
Spring WebSocket簡單入門測試Demo(網頁及時聊天)
項目需要一個及時推送消息的功能,並且這個任務落在了我的頭上,早有實現此功能的心思,這不機會就來了,網上看了下頁面ajax定時請求這種方案的居多,通過同時介紹找到了websocket,百度百科了下非常滿意,並輕松在網上找到了相關文章。下面簡單介紹下實現,代碼就不在這里貼了,Spring WebSocket簡單入門測試Demo(網頁及時聊天)這篇博文給的很全,下面只對實現過程中遇到的一些問題簡單說明下
問題1:建立鏈接失敗
這個問題是由於頁面鏈接ip與地址欄中不一致所致,正確方式如下圖


如果頁面寫成127.0.0.1,地址欄為localhost會鏈接報錯,相反也會報錯
問題2:多頁面使用websocket如何區分?
這個問題困擾了好久,試過通過訪問鏈接、單獨建立websocket等方式,均不能很好的解決問題,最后還是通過萬能的“打debug”得到了解決思路,豁然開朗,原來如此簡單。解決思路如下:
1)在WebsocketConfigure中為每個頁面添加單獨的websocket管理
@Configuration @EnableWebSocket//開啟websocket public class WebSocketConfig implements WebSocketConfigurer { public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { //頁面1的websocket支持 registry.addHandler(new WebSocketHander(), "/echo").addInterceptors(new HandshakeInterceptor()); //支持websocket 的訪問鏈接 registry.addHandler(new WebSocketHander(), "/sockjs/echo").addInterceptors(new HandshakeInterceptor()).withSockJS(); //不支持websocket的訪問鏈接 //頁面2的websocket支持 registry.addHandler(new WebSocketHander(), "/echo1").addInterceptors(new HandshakeInterceptor()); //支持websocket 的訪問鏈接 registry.addHandler(new WebSocketHander(), "/sockjs/echo1").addInterceptors(new HandshakeInterceptor()).withSockJS(); //不支持websocket的訪問鏈接 } }
有原來的一個websocket變為兩個,只需與各自的頁面對應即可
2)攔截器標識修改(粗體字為修改之處)
public class HandshakeInterceptor implements org.springframework.web.socket.server.HandshakeInterceptor { //進入hander之前的攔截 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception { String[] uri = request.getURI().toString().split("/"); if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(true); //保證地址欄的請求和websocket的請求地址統一就能獲取到了 User user = (User) session.getAttribute("now_user"); if (session != null) { //使用userName區分WebSocketHandler,以便定向發送消息 map.put("websocket_index", uri[uri.length - 1] + "_" + user.getUserName()); } } return true; } public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { } }
3)消息發送方法代碼修改(黑體字為修改內容)
package com.controller.websocket; import org.apache.log4j.Logger; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Created by root on 16-10-26. */ public class WebSocketHander implements WebSocketHandler { private static Logger log = Logger.getLogger(WebSocketHander.class); private static int count = 0;//統計建立管道數 private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>(); private static final Map<String, String> map = new HashMap(); //初次鏈接成功執行 public void afterConnectionEstablished(WebSocketSession session) throws Exception { log.debug("鏈接成功......"); users.add(session); String key = (String) session.getAttributes().get("websocket_index"); if (key != null) { //未讀消息處理邏輯 count++; map.put(key, session.getId()); session.sendMessage(new TextMessage(count + "")); } } //接受消息處理消息 public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception { sendMessageToUsers(new TextMessage(webSocketMessage.getPayload() + "")); //sendMessageToUser("123", new TextMessage(webSocketMessage.getPayload() + "")); } public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception { if (webSocketSession.isOpen()) { webSocketSession.close(); } count--; log.debug("鏈接出錯,關閉鏈接......"); users.remove(webSocketSession); } //關閉或離開此頁面管道關閉 public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception { count--; log.debug("鏈接關閉......" + closeStatus.toString()); users.remove(webSocketSession); } public boolean supportsPartialMessages() { return false; } /** * 給所有在線用戶發送消息 * * @param message */public void sendMessageToUsers1(String index, TextMessage message) { ArrayList<WebSocketSession> u = users; Map<String, String> m = map; for (WebSocketSession user : users) { try { String[] str = user.getAttributes().get("websocket_index").toString().split("_"); if (user.isOpen() && str[0].equals(index)) { user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } } /** * 給某個用戶發送消息 * * @param userName * @param message */public void sendMessageToUser1(String index, TextMessage message) { ArrayList<WebSocketSession> u = users; Map<String, String> m = map; for (WebSocketSession user : users) { if (user.getId().equals(map.get(index))) { try { if (user.isOpen()) { user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } break; } } } public static Map<String, String> getMap() { return map; } }
4)后端發送消息控制層代碼修改
package com.controller.websocket; import com.entity.User; import org.apache.log4j.Logger; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.socket.TextMessage; import javax.servlet.http.HttpServletRequest; /** * websocket數據推送測試 Created by root on 16-10-27. */ @Controller @RequestMapping(value = "/websocket") public class WebsocketController { private static Logger log = Logger.getLogger(WebsocketController.class); @Bean public WebSocketHander webSocketHandler() { return new WebSocketHander(); } /** * 后台推送消息給指定用戶 * * @param request * @return */ @RequestMapping("/auditing") @ResponseBody public String auditing(HttpServletRequest request, String index) { User user = (User) request.getSession().getAttribute("now_user"); //webSocketHandler().sendMessageToUser1(index + "_" + user.getUserName(), new TextMessage(user.getUserName())); webSocketHandler().sendMessageToUsers1(index, new TextMessage(user.getUserName())); return "success"; } /** * 打開此頁面前端和后端正式建立管道,關閉或離開此頁面管道關閉 * * @return */ @RequestMapping(value = "/websocket") public String websocket() { return "websocket"; } @RequestMapping(value = "/websocket1") public String websocket1() { return "websocket1"; } }
到此問題二得到解決。
問題3:同一頁面,在同一瀏覽器的兩個標簽中打開,此時同一功能的管道打開了兩個,如何在新的標簽打開頁面建立管道的時候關閉之前的?
解決方案是在新標簽打開該頁面時,將之前頁面的管道關閉,代碼如下:
//初次鏈接成功執行 public void afterConnectionEstablished(WebSocketSession session) throws Exception { String key = (String) session.getAttributes().get("websocket_index"); users.add(session); log.debug(key + " 鏈接成功......"); if (key != null) { if (map.get(key) != null) { for (WebSocketSession sess : users) { if (sess.getId().equals(map.get(key))) sess.close(); break; } } //未讀消息處理邏輯 count++; map.put(key, session.getId()); session.sendMessage(new TextMessage(count + "")); } }
問題4:刷新頁面,之前管道未加載完的信息仍會在刷新后的頁面加載(要求刷新之后之前的請求停止)?
解決方案記錄每個管道的WebSocketSession的id,保存到要發送消息的方法中,每次發送消息時檢測用戶的當前的管道id與記錄的是否相等,相等則發送,否則停止該方法。
具體通過在WebSocketHander中定義map記錄每個用戶的管道信息實現。
private static final Map<String, String> map = new HashMap();
最后給出下所需依賴
<!--websocket-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${org.springframework-version}</version>
</dependency>
