springboot整合websocket实现登录挤退现象

陆某人      2022-05-10     481

关键词:

在项目期间遇到了同一个账号不能在不同的地方同时登录的情况,解决方法用到了websocket。

关于websocket的原理网上有很多,我这里就不写了,推荐博客:

https://www.cnblogs.com/myzhibie/p/4470065.html

websocket理清原理:https://zhuanlan.zhihu.com/p/95622141

这里我主要记录一下websocket来实现的登录挤退的功能

一:实现的思想

1.我的思路是这样的,在登录的时候要去后台验证账号密码的正确性,如果这个都不正确那就别说了。

2.当账号和密码正确时,在session里面存储一下该用户信息,后台返回给前端一个标准,表示账号和密码正确,然后前端通过js来建立websocket的连接

后台会接收这个连接,然后在这个连接中取出该连接服务器的session,通过session里面存储的用户id来判断静态变量websocket

list里面是否含有该用户id的websocket(毕竟用户id为唯一标识)。

3.如果含有,则说明该用户已经在登录的状态。所以通过后台的websocket对象来发送消息,告知前端js的websocket说用户已经登录了。

4.如果不含有,则说明该账号目前不处于登录状态,就存放到静态变量List<Websocket>里面。并发送消息到前台说明登录成功。

以上为最基本的思想。但是问题来了。如何实现?同时,如何在websocket获得该次连接服务器的HttpSession对象?

慢慢来解决。这里默认该用户账号密码正确,从js发送websocket的连接开始。

maven依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

 

 

 

二:实现

  1.js发送websocket的连接

  

function onenSocket(){
   /*newsinfo2为项目的根目录,websocket为请求地址,后台通过注解接收*/
var socket = new WebSocket("ws://localhost:8080/newsinfo2/webSocket/"); if(typeof(socket) == undefined){ alert("您的浏览器不支持webSocket,请换一个浏览器..."); return ; }
    /*websocket接收消息的函数*/ socket.onmessage
= function(msg){ if(msg == "已登录"){ alert("您的账号已在另一个地方尝试登录,如果不是您知晓的情况,请及时修改密码..."); }else if(msg == "登录成功"){ location.href="../index/index.html"; }else if(msg == "修改密码"){ alert("您账号的密码已经被修改!如果不是你自己知晓的情况,请及时修改密码..."); location.href="../login/login.html"; } } //socket打开时的方法 socket.onopen = function(){ } //socket关闭时的方法 socket.onclose = function(){ } //socket出错时的方法 socket.onerror = function(){ } /*//在页面加载时自动断开链接,这样就不会异常断开链接,后台不会报错误 $(document).ready(function(){ socket.close(); });*/ }

该js发送请求后,后台接收如下:

  2.后台websocket的接收

@ServerEndpoint(value = "/webSocket/")
public class WebSocketServer {

WebSocketServer为自创的类。通过这个注解,这个类会有一些自带的方法:

  onopen():连接时需要调用的方法

  onError():出现错误时执行的方法

  onClose():关闭连接时调用的方法

该类中必须要自定义一个静态变量:

  

//用于存储webSocketServer

public static CopyOnWriteArraySet<WebSocketServer> webSocketServerSet = new CopyOnWriteArraySet<WebSocketServer>();

 这个框架就算是建立了,接下来是一些缝缝补补的工作。

要完整的将我的webSocketServer呈现,那还需要获得httpsession对象。获得对象的方法和思想请看下面的博客:

https://www.cnblogs.com/zhuxiaojie/p/6238826.html

我的WebSocket整体呈现:

package news.webSocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import news.bean.UserInfo;
import news.config.Configuretor;
import news.utils.LogerUtils;
import news.utils.StaticValue;

/**
 * webSocketServer类,用于处理登录挤退现象,
 * 思路:登录时,要判断该账号是否已创建一个webSocket对象存储起来了,根据这个判断的结果来进行下一步动作
 * @author 徐金仁
 */
@ServerEndpoint(value = "/webSocket/" , configurator = Configuretor.class)
public class WebSocketServer {
    //用于存储webSocketServer
    public static CopyOnWriteArraySet<WebSocketServer> webSocketServerSet = new CopyOnWriteArraySet<WebSocketServer>();
    private Session session;  //与某个客户端连接的会话,该session是属于WebSocket的session,不属于HttpSession
    private String sid; //用户的编号
    private Logger log = LogerUtils.getLogger(this.getClass());
    @Autowired
    private HttpSession httpSession_;
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((session == null) ? 0 : session.hashCode());
        result = prime * result + ((httpSession_ == null) ? 0 : httpSession_.hashCode());
        result = prime * result + ((sid == null) ? 0 : sid.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        WebSocketServer other = (WebSocketServer) obj;
        if (session == null) {
            if (other.session != null)
                return false;
        } else if (!session.equals(other.session))
            return false;
        if (httpSession_ == null) {
            if (other.httpSession_ != null)
                return false;
        } else if (!httpSession_.equals(other.httpSession_))
            return false;
        if (sid == null) {
            if (other.sid != null)
                return false;
        } else if (!sid.equals(other.sid))
            return false;
        return true;
    }

    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketServerSet() {
        return webSocketServerSet;
    }

    public static void setWebSocketServerSet(CopyOnWriteArraySet<WebSocketServer> webSocketServerSet) {
        WebSocketServer.webSocketServerSet = webSocketServerSet;
    }

    public String getSid() {
        return sid;
    }

    public void setSid(String sid) {
        this.sid = sid;
    }

    public HttpSession gethttpSession_() {
        return httpSession_;
    }

    public void sethttpSession_(HttpSession httpSession_) {
        this.httpSession_ = httpSession_;
    }

    public void setSession(Session session) {
        this.session = session;
    }
    
    /**
     * 获取HttpSession
     * @return
     */
    public HttpSession getHttpSession(){
        return this.httpSession_;
    }
    
    /**
     * 获取session
     * @return
     */
    public Session getSession(){
        return this.session;
    }
    
    /**
     * 连接的时候需要调用的方法
     * @throws IOException 
     */
    @OnOpen
    public void onOpen(Session session,  EndpointConfig config) throws IOException{
        HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        this.session = session;
        this.httpSession_ = httpSession;
        System.out.println("链接中..." + httpSession_.getId());
        //StaticValue为自定义的存放key值的类,里面都是一些常量
        Object obj =  (this.httpSession_.getAttribute(StaticValue.CURRENT_USER));
        if(obj != null){ //说明还链接中
            this.sid = String.valueOf(((UserInfo)obj).getUid());
            log.info(this.sid + "正在链接中...");
            if(!webSocketServerSet.contains(this)){
                webSocketServerSet.add(this); //将连接到的添加进入set里面
            }
        }else{ //说明不链接了
            //等会在写
        }
        /*this.sendMessage("连接成功!");*/
    }
    /**
     * 发送消息的方法
     * @param string
     * @throws IOException 
     */
    public void sendMessage(String msg) throws IOException  {
        this.session.getBasicRemote().sendText(msg);
    }
    
    /**
     * 出现错误的方法
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session , Throwable error){
        log.error( this.sid + "websocket出错断开链接");
    }
    /**
     * 当连接断开时,调用的方法
     */
    @OnClose
    public void onClose(){
        webSocketServerSet.remove(this);
    }
    
    /**
     * 根据sid查询webSocket
     * @param sid
     * @return
     */
    public static WebSocketServer getWebSocket(String sid){
        for(WebSocketServer w : webSocketServerSet){
            if(sid.equals(w.sid)){
                return w;
            }
        }
        return null;
    }
}

controller层:

@RequestMapping("login")
    public UserInfo login(String uname, String upwd, HttpSession session) throws IOException{
        int result = 0;
        UserInfo userInfo = new UserInfo();
        userInfo.setUname(uname);
        userInfo.setUpwd(upwd);
        UserInfo us = null;
        us = loginService.login(userInfo);
        if(us == null){
            us = new UserInfo();
            us.setUid(-1); //表示账号或密码不对
        }else{//如果查寻到账号和密码都没有错误,则要判断是否已经被登录了,
            WebSocketServer wws = WebSocketServer.getWebSocket(String.valueOf(us.getUid()));
            if(wws != null){ //如果有
                wws.sendMessage("已登录");
                us.setUid(-2); //表示已登录
            }else{//表示暂时没有人登录,您是第一个,要将信息存储一下
                session.setAttribute("userInfo", us);
                session.setAttribute(StaticValue.CURRENT_USER, us);
                System.out.println("session的id:" + session.getId());
            }
        }
        System.out.println(us);
        return us;

 

这里还有几个坑,一个是如果就是登陆成功后,页面一刷新,websocket就会出异常断开,这里没有什么好的办法,只有每次刷新或者跳转页面的之后,都要重新链接。

还有一个是localhost访问的情况和127.0.0.1访问的情况下是不同的。如果你在js中链接使用127.0.0.1,而项目运行后在浏览器地址上显示的是localhost的话,那么获得的HttpSession并不是同一个对象。这样的话会导致程序员的判断出现错误。解决的办法是同一使用127.0.0.1或者是localhost。至于为什么会出现这种不同,请查看下面:

localhost 127.0.0.1和本机ip三者的区别

localhost 
不联网 
不使用网卡,不受防火墙和网卡限制 
本机访问 

127.0.0.1 
不联网 
网卡传输,受防火墙和网卡限制 
本机访问 

本机IP 
联网 
网卡传输 ,受防火墙和网卡限制 
本机或外部访问

以上三者区别知识的来源:

https://blog.csdn.net/qq_35101027/article/details/80745664

 

springboot2系列教程(十六)|整合websocket实现广播

前言如题,今天介绍的是SpringBoot整合WebSocket实现广播消息。什么是WebSocket?WebSocket为浏览器和服务器提供了双工异步通信的功能,即浏览器可以向服务器发送信息,反之也成立。WebSocket是通过一个socket来实现双工异步通信能力... 查看详情

springboot整合websocket实现实时消息推送(代码片段)

0.开发环境JDK:1.8SpringBoot:2.1.1.RELEASE1.引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>< 查看详情

springboot2系列教程(十七)|整合websocket实现聊天室

...基础之上,为便于更好理解今天这一篇,推荐先阅读:「SpringBoot整合WebSocket实现广播消息」 查看详情

springboot整合websocket实现即时聊天功能

...期,公司需要新增即时聊天的业务,于是用websocket整合到Springboot完成业务的实现。一、我们来简单的介绍下websocket的交互原理:1.客户端先服务端发起websocket请求;2.服务端接收到请求之后,把请求响应返回给客户端;3.客户端... 查看详情

springboot整合websocket实现简单聊天室(代码片段)

项目结构:效果展示:实现步骤步骤一:添加依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency> 查看详情

springboot整合websocket实现实时消息推送(代码片段)

0.开发环境JDK:1.8SpringBoot:2.1.1.RELEASE1.引入依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>2.新建WebSocket配置类importorg.springframework.context.annotatio... 查看详情

springboot1.5.9整合websocket

一.WebSocket介绍  1.WebSocket是什么?    WebSocket是协议,是HTML5开始提供的基于TCP(传输层)的一种新的网络协议,    它实现了浏览器与服务器全双工(full-duplex)通信,即允许服务器主动发送消息给客户端    WebSocket使得客... 查看详情

springboot整合websocket实现客户端与服务端通信(代码片段)

定义?WebSocket是通过单个TCP连接提供全双工(双向通信)通信信道的计算机通信协议。此WebSocketAPI可在用户的浏览器和服务器之间进行双向通信。用户可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器。它可以让... 查看详情

springboot整合websocket简单聊天室(代码片段)

springboot整合websocket(一)简单聊天室springboot整合websocket(一)简单聊天室springboot整合websocket(二)上传文件(引导篇)springboot整合websocket(三)上传文件(终篇& 查看详情

springboot+websocket实现扫码登录功能

最近单位又有一个新Java项目。涉及到扫码登录。之前项目使用的是ajax轮询的方式。感觉太low了。所以这次用webSocket的方式进行实现好。废话不多说!咱们开始!!一、首先咱们需要一张表这表是干啥的呢?就是... 查看详情

springboot-websocket实现及原理

本文章包括websocket面试相关问题以及springboot如何整合webSocket。参考文档https://blog.csdn.net/prayallforyou/article/details/53737901、https://www.cnblogs.com/bianzy/p/5822426.html  webSocket是HTML5的一种新协议,它实现了服务端与客户端的全双工通信,... 查看详情

springboot整合微信小程序实现登录与增删改查

项目描述:在微信小程序中通过与Springboot操作数据库实现登录验证,其中我是用springboot整合mybatis-plus 和mysql操作数据库1.开发前准备1.1前置知识java基础SpringBoot简单基础知识1.2环境参数开发工具:IDEA基础环境:Maven+JDK8所用... 查看详情

springboot/angular整合keycloak实现单点登录

未完待续KeycloakKeycloak为现代应用和服务提供开源的认证和访问管理,即通常所说的认证和授权。Keycloak支持OpenID、OAuth2.0和SAML2.0协议;支持用户注册、用户管理、权限管理;支持代理OpenID、SAML2.0IDP,支持GitHub、LinkedIn等第三方登... 查看详情

javaspringboot整合websocket

【Java】SpringBoot整合WebSocketWebSocket简介WebSocket是一种协议,用于实现客户端和服务器之间的双向通信。它可以在单个TCP连接上提供全双工通信,避免了HTTP协议中的请求-响应模式,从而实现更高效的数据交换。WebSocket协议最初由HT... 查看详情

springboot整合websocket,使用stomp协议,前后端整合实战(代码片段)

... 一篇极简风,最最最基础的方式整合websocket:《SpringBoot整合WebSocket简单实战案例》地址: https://blog.csdn.net/qq_35387940/article/details/ 查看详情

springboot整合shiro实现登录认证以及授权

1.添加shiro的依赖<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.4.0</version></dependency>2. 查看详情

springboot+websocket实现扫码登录,这种方式太香了!!

相关阅读:一个90后员工猝死的全过程最近单位又有一个新Java项目。涉及到扫码登录。之前项目使用的是ajax轮询的方式。感觉太low了。所以这次用webSocket的方式进行实现好。废话不多说!咱们开始!!一、首先咱... 查看详情

websocket教程springboot+maven整合(目录)

...、课程技术选型和浏览器兼容讲解简介:简单介绍什么是springboot、socketjs、stompjs,及解决使用浏览器兼容问题3、websocket广播、单播、组播介绍和使用场景说明简介:主要讲解websocket的一些概念,如广播,单播等,他们的基本区... 查看详情