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

Posted 陆某人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot整合websocket实现登录挤退现象相关的知识,希望对你有一定的参考价值。

在项目期间遇到了同一个账号不能在不同的地方同时登录的情况,解决方法用到了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

 

以上是关于springboot整合websocket实现登录挤退现象的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot整合Websocket实现即时聊天功能

开发经验springboot整合websocket实现群聊

springboot整合websocket实现一对一消息推送和广播消息推送

SpringBoot 整合WebSocket 实现简单聊天室

springboot整合websocket实现客户端与服务端通信

SpringBoot2.x整合WebSoket