WebSocket
本文源代码下载: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>