在我的《使用Session实现一个用户只能登录一次》的这篇文章后有个遗留的问题,怎么实现第二个
账户登录,第一个用户马上就可以收到信息。我当时的想法是做一个轮询。但是这样的做法会给后
台很大的压力。
现在可以很好的解决这个问题,就是使用websocket。
websocket是什么?websocket是一个损耗小,可跨域,全双工通信的互联网技术,也就是说可以
从服务端向客户端推送消息,这样就可以不用以前的轮询和长连接的方式。
轮询:就是在前端做一个方法一直访问后台,一直从后台取数据。
长连接:一个连接可以连续发送多个数据包,损耗大。
下面就开始使用Spring websocket。
一、引入jar包(Spring 版本为4.3.8.RELEASE)
<!--servlet-api--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency>
二、实现拦截器
import java.util.Map; import javax.servlet.http.HttpSession; 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; public class MyHandlerInterceptors implements HandshakeInterceptor { public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(false); if (session != null) { attributes.put("sessionId",session.getId()); } } return true; } public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }
在建立连接前拦截和建立连接后拦截,主要使用前者,因为我们可以给WebSocketSession绑定
一些属性,就是向Map<String, Object> attributes 里面put值。
三、实现处理器
import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.springframework.stereotype.Component; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; @Component public class MyHandler extends TextWebSocketHandler{ private static final List<WebSocketSession> users = new ArrayList<>(); //连接关闭后 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { users.remove(session); super.afterConnectionClosed(session, status); } //在连接建立 public void afterConnectionEstablished(WebSocketSession session) throws Exception { users.add(session); session.sendMessage(new TextMessage("连接成功!!!"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))); } //处理Text消息 protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { System.out.println(message); session.sendMessage(new TextMessage("消息已接受!会及时处理"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))); super.handleTextMessage(session, message); } //抛出异常时处理 public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { if(session.isOpen()){ session.close(); } users.remove(session); } //支持部分信息(将一个信息拆分多个进行发送) public boolean supportsPartialMessages() { return false; } //单发消息 public void sendMessageToUser(String httpSessionId, TextMessage message) { for (WebSocketSession user : users) { if (user.getAttributes().get("sessionId").equals(httpSessionId)) { try { if (user.isOpen()) { user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } break; } } } //服务端主动关闭连接 public void close(String httpSessionId, TextMessage message) { for (WebSocketSession user : users) { if (user.getAttributes().get("sessionId").equals(httpSessionId)) { try { if (user.isOpen()) { user.sendMessage(message); user.close(); } } catch (IOException e) { e.printStackTrace(); } break; } } } //群发消息(给所有的在线用户发送消息) public void sendMessageToUsers(TextMessage message) { for (WebSocketSession user : users) { try { if (user.isOpen()) { user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } } //给指定的多个用户发送消息 public void sendMessageToUsers(String[] httpSessionIds, TextMessage message) { for (int i = 0; i < httpSessionIds.length; i++) { String httpSessionId = httpSessionIds[i]; sendMessageToUser(httpSessionId, message); } } }
四、使用java配置Spring-WebSocket
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; 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 com.it.handle.MyHandler; import com.it.interceptors.MyHandlerInterceptors; @Configuration @EnableWebSocket public class WebScoketConfig implements WebSocketConfigurer { @Autowired MyHandler myHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler,"/test").addInterceptors(new MyHandlerInterceptors()); registry.addHandler(myHandler,"/sockjs").addInterceptors(new MyHandlerInterceptors()).withSockJS(); } }addHandler(handler,"url"),使用哪个处理器处理"url"过来的请求。addInterceptors(HandlerInt
erceptor),给这个请求绑定拦截器。setAllowedOrigins(String[ ])设置跨的域。withSockJS()
是该请求是通过JS的方式过来的,因为websocket 是html5技术,部分浏览器不支持,则需要通
过JS的方式。
五、使用websocket
因为MyHandler使用了@Component的注解则可以在你需要的地方把处理器注入进来即可,然后使用其方法。
如:
//登出
@GetMapping("/logout")
public @ResponseBody String logout(HttpSession session) {
myHandler.close(session.getId(), new TextMessage("登出成功"));
//销毁session
session.invalidate();
return "success";
}
六、前端的使用
1.创建连接
var ws = new WebSocket("ws://ip:port/项目名/url");addHandler(handler,url)。这两个要url相同。
2.发送消息
ws.send(data);
3.接收到消息触发
ws.onmessage = function(evt){
var received_msg = evt.data;//received_msg就是传过来的消息
}
4.连接断开触发
ws.onclose = function(){
}
5.连接打开触发
ws.onopen = function(){
}
6.连接错误触发
ws.onerror = function(evt){
}
7.客户端关闭连接
ws.close();
例子:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>主页</title> <base href="<%=basePath%>"> <style type="text/css"> *{ padding:0px; margin: 0px; } </style> <script type="text/javascript" src="./static/js/jquery.js"></script> <script type="text/javascript"> function x() { $.get("logout",function (data) { alert(data=='success'?'登出成功':'登出失败'); location.href='login'; }); } function WebSocketTest() { if ("WebSocket" in window) { alert("您的浏览器支持 WebSocket!"); // 打开一个 web socket var ws = new WebSocket("ws://127.0.0.1/SSMWebSocket/test"); //alert(ws); ws.onopen = function() { // Web Socket 已连接上,使用 send() 方法发送数据 ws.send("发送数据"); alert("数据发送中..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("数据已接收:"+received_msg); }; ws.onclose = function() { // 关闭 websocket alert("连接已关闭..."); }; ws.onerror = function(evt){ ws.close(); } } else { // 浏览器不支持 WebSocket alert("您的浏览器不支持 WebSocket!"); } } </script> </head> <body> <div style="width:100%;height:auto;text-align:center;"> <div id="sse"> <a href="javascript:WebSocketTest()">运行 WebSocket</a> </div> <button onclick="x()">登出</button> </div> </body> </html>