在我的《使用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 οnclick="x()">登出</button>
</div>
</body>
</html>