webSocket介绍及项目实战【在线聊天系统】

news2024/10/7 14:24:07

文章目录

  • 一:消息推送常用方式介绍
    • 1.1 轮询:浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实时返回数据给浏览器
    • 1.2 长轮询:浏览器发出ajax请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才返回
    • 1.3 SSE(server-sent event):服务器发送事件
    • 1.4 websocket:是一种在基于TCP连接上进行全双工通信的协议
  • 二:websocket介绍
    • 2.1 什么是websocket
    • 2.2 websocket的原理
    • 2.3 websocket与http的关系
      • 2.3.1 相同点
      • 2.3.2 不同点
      • 2.3.3 联系
      • 2.3.4 总体过程
    • 2.4 说明
    • 2.5 原理解析
      • 2.5.1 请求数据
      • 2.5.2 响应数据
  • 三:websocket解决的问题
    • 3.1 http存在的问题
    • 3.2 long poll(长轮询)
    • 3.3 Ajax轮询
    • 3.4 websocket的改进
  • 四:websocket API
    • 4.1 客户端【浏览器】API
      • 4.1.1 websocket对象创建
      • 4.1.2 websocket对象相关事件
      • 4.1.3 websocket对象提供的方法
      • 4.1.4 代码演示
    • 4.2 服务端 API
      • 4.2.1 Endpoint说明
      • 4.2.2 定义Endpoint
      • 4.2.3 生命周期方法
      • 4.2.4 服务端如何接收客户端发送的数据
      • 4.2.5 服务端如何推送数据给客户端
  • 五:在线聊天室代码实现
    • 5.1 流程分析
    • 5.2 引入websocket依赖
    • 5.2 前端登录界面
    • 5.2 前端聊天界面
    • 5.3 发送消息实体类
      • 5.3.1 客户端 --> 服务端
      • 5.3.2 服务端 --> 客户端
    • 5.4 编写websocket配置类
    • 5.5 登录以及页面跳转
    • 5.6 后台核心代码

一:消息推送常用方式介绍

1.1 轮询:浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实时返回数据给浏览器

在这里插入图片描述

1.2 长轮询:浏览器发出ajax请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才返回

在这里插入图片描述

1.3 SSE(server-sent event):服务器发送事件

  • SSE在服务器和客户端之间打开一个单向通道
  • 服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息
  • 服务器有数据变更时将数据流式传输到客户端
    在这里插入图片描述

1.4 websocket:是一种在基于TCP连接上进行全双工通信的协议

二:websocket介绍

2.1 什么是websocket

  1. WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  2. 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  3. Websocket是一个持久化的协议

2.2 websocket的原理

  1. websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
  2. 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
  3. websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"

2.3 websocket与http的关系

在这里插入图片描述

2.3.1 相同点

  • 都是基于tcp的,都是可靠性传输协议
  • 都是应用层协议

2.3.2 不同点

  • WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息
  • HTTP是单向的
  • WebSocket是需要浏览器和服务器握手进行建立连接的
  • 而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接

2.3.3 联系

  • WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的

2.3.4 总体过程

  • 首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;
  • 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
  • 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

2.4 说明

全双工(Full Duplex):允许数据在两个方向上同时传输。
半双工(Half Duplex):允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上传输。
在这里插入图片描述

2.5 原理解析

在这里插入图片描述

2.5.1 请求数据

GET ws://localhost/chat HTTP/1.1
Host: localhost
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Extensions: permessage-deflate

2.5.2 响应数据

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Extensions: permessage-deflate

三:websocket解决的问题

3.1 http存在的问题

  • http是一种无状态协议,每当一次会话完成后,服务端都不知道下一次的客户端是谁,需要每次知道对方是谁,才进行相应的响应,因此本身对于实时通讯就是一种极大的障碍
  • http协议采用一次请求,一次响应,每次请求和响应就携带有大量的header头,对于实时通讯来说,解析请求头也是需要一定的时间,因此,效率也更低下
  • 最重要的是,需要客户端主动发,服务端被动发,也就是一次请求,一次响应,不能实现主动发送

3.2 long poll(长轮询)

  • 对于以上情况就出现了http解决的第一个方法——长轮询
  • 基于http的特性,简单点说,就是客户端发起长轮询,如果服务端的数据没有发生变更,会 hold 住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回。返回后,客户端又会立即再次发起下一次长轮询
  • 优点是解决了http不能实时更新的弊端,因为这个时间很短,发起请求即处理请求返回响应,实现了“伪·长连接”
  • 张三取快递的例子,张三今天一定要取到快递,他就一直站在快递点,等待快递一到,立马取走
  • 推送延迟。服务端数据发生变更后,长轮询结束,立刻返回响应给客户端。
  • 服务端压力。长轮询的间隔期一般很长,例如 30s、60s,并且服务端 hold 住连接不会消耗太多服务端资源。

3.3 Ajax轮询

特点:

  • 基于http的特性,简单点说,就是规定每隔一段时间就由客户端发起一次请求,查询有没有新消息,如果有,就返回,如果没有等待相同的时间间隔再次询问
  • 优点是解决了http不能实时更新的弊端,因为这个时间很短,发起请求即处理请求返回响应,把这个过程放大n倍,本质上还是request = response
  • 举个形象的例子(假设张三今天有个快递快到了,但是张三忍耐不住,就每隔十分钟给快递员或者快递站打电话,询问快递到了没,每次快递员就说还没到,等到下午张三的快递到了,but,快递员不知道哪个电话是张三的,(可不是只有张三打电话,还有李四,王五),所以只能等张三打电话,才能通知他,你的快递到了)

案例分析:
从例子上来看有两个问题:

  • 假如说,张三打电话的时间间隔为10分钟,当他收到快递前最后一次打电话,快递员说没到,他刚挂掉电话,快递入库了(就是到了),那么等下一次时间到了,张三打电话知道快递到了,那么这样的通讯算不算实时通讯?很显然,不算,中间有十分钟的时间差,还不算给快递员打电话的等待时间(抽象的解释:每次request的请求时间间隔等同于十分钟,请求解析相当于等待)
  • 假如说张三所在的小区每天要收很多快递,每个人都采取主动给快递员打电话的方式,那么快递员需要以多快的速度接到,其他人打电话占线也是问题(抽象解释:请求过多,服务端响应也会变慢)

Ajax轮询存在的问题:

  • 推送延迟。
  • 服务端压力。配置一般不会发生变化,频繁的轮询会给服务端造成很大的压力。
  • 推送延迟和服务端压力无法中和。降低轮询的间隔,延迟降低,压力增加;增加轮询的间隔,压力降低,延迟增高

3.4 websocket的改进

一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端中断连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实现了“真·长链接”,实时性优势明显。
在这里插入图片描述
WebSocket有以下特点:

  • 是真正的全双工方式,建立连接后客户端与服务器端是完全平等的,可以互相主动请求。而HTTP长连接基于HTTP,是传统的客户端对服务器发起请求的模式。
  • HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据,这显然和原有的HTTP协议有区别所以它需要对服务器和客户端都进行升级才能实现(主流浏览器都已支持HTML5)

四:websocket API

4.1 客户端【浏览器】API

4.1.1 websocket对象创建

let  ws  =  new WebSocket(URL);

url说明:
格式:协议://ip地址/访问路径
协议:协议名称为 ws

4.1.2 websocket对象相关事件

在这里插入图片描述

4.1.3 websocket对象提供的方法

在这里插入图片描述

4.1.4 代码演示

在这里插入图片描述

4.2 服务端 API

4.2.1 Endpoint说明

  • Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范。
  • Java WebSocket应用由一系列的Endpoint组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口。

4.2.2 定义Endpoint

  • 第一种是编程式, 即继承类 javax.websocket.Endpoint并实现其方法。
  • 第二种是注解式, 即定义一个POJO, 并添加 @ServerEndpoint相关注解。

4.2.3 生命周期方法

在这里插入图片描述

4.2.4 服务端如何接收客户端发送的数据

编程式:通过添加 MessageHandler 消息处理器来接收消息
注解式:在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

4.2.5 服务端如何推送数据给客户端

  • 发送消息则由 RemoteEndpoint 完成, 其实例由 Session 维护。
  • 发送消息有2种方式发送消息
    通过session.getBasicRemote 获取同步消息发送的实例 , 然后调用其 sendXxx()方法发送消息
    通过session.getAsyncRemote 获取异步消息发送实例,然后调用其 sendXxx() 方法发送消息
    在这里插入图片描述

五:在线聊天室代码实现

5.1 流程分析

在这里插入图片描述

5.2 引入websocket依赖

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

5.2 前端登录界面

<!DOCTYPE html>
<html lang="en">

<head>
    <title>随意时光-登录</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta name="keywords"
          content="Transparent Sign In Form Responsive Widget,Login form widgets, Sign up Web forms , Login signup Responsive web form,Flat Pricing table,Flat Drop downs,Registration Forms,News letter Forms,Elements"/>
    <script type="application/x-javascript">
        addEventListener("load", function () {
            setTimeout(hideURLbar, 0);
        }, false);

        function hideURLbar() {
            window.scrollTo(0, 1);
        }
    </script>

    <script src="/js/jquery.min.js"></script>
    <link rel="icon" href="/img/chat.ico" type="image/x-icon"/>
    <link rel="stylesheet" href="/css/font-awesome.css"/> <!-- Font-Awesome-Icons-CSS -->
    <link rel="stylesheet" href="/css/login.css" type="text/css" media="all"/> <!-- Style-CSS -->
</head>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="/js/vue.js"></script>
<script src="/js/axios-0.18.0.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>

<body class="background">

<div class="header-w3l">
    <h1>WeChat</h1>
</div>

<div class="main-content-agile" id="app">
    <div class="sub-main-w3">
        <h5>账号登录</h5>
        <el-form id="loginForm" size="medium">
            <el-row>
                <el-col :span="24">
                    <el-form-item class="icon1" label="账号:" prop="account">
                        <el-input placeholder="用户名/手机号/邮箱" id="account" style="width: 80%;" v-model="user.account" type="text"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>

            <el-row>
                <el-col :span="24">
                    <el-form-item class="icon2" label="密码:" prop="password">
                        <el-input placeholder="密码" id="password" style="width: 80%;" v-model="user.password" type="password"></el-input>
                    </el-form-item>
                </el-col>
            </el-row>
        </el-form>
        <div class="clear"></div>
        <input type="button" id="btn1" @click="login" value="登录"/>
        <div class="icon1">
            <span id="err_msg" style="color: red; ">{{errMessage}}</span>
        </div>
    </div>
</div>
<div class="footer">
    <p style="color: #1E1E1E;font-weight: bold">随意石光科技有限公司 版权所有、盗版必究 </p>
</div>

</body>
<script>
    new Vue({
        el:"#app",
        data() {
            return {
                errMessage: "",
                user:{
                    account:"",
                    password:""
                },

            }
        },
        methods: {
            login() {
                let self = this;
                axios.post("/userLogin",this.user).then(res => {
                    //判断登陆是否成功
                    if(res.data.code === 200) {
                        location.href = "websocket/main.html";
                    } else {
                        self.$message.error(res.data.msg);
                    }
                });
            },

        }
    });
</script>
</html>

5.2 前端聊天界面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="format-detection" content="telephone=no">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta content="yes" name="apple-mobile-web-app-capable">
    <meta content="yes" name="apple-touch-fullscreen">
    <meta name="full-screen" content="yes">
    <meta content="default" name="apple-mobile-web-app-status-bar-style">
    <meta name="screen-orientation" content="portrait">
    <meta name="browsermode" content="application">
    <meta name="msapplication-tap-highlight" content="no">
    <meta name="x5-orientation" content="portrait">
    <meta name="x5-fullscreen" content="true">
    <meta name="x5-page-mode" content="app">
    <base target="_blank">
    <title>WeChat-聊天室</title>
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
    <link rel="stylesheet" href="/css/chat.css">
</head>

<body>
<img style="width:100%;height:100%" src="/img/dlam.jpg">

<div class="abs cover contaniner" id="app">
    <div class="abs cover pnl">
        <div class="top pnl-head" style="padding: 20px ; color: white;">
            <div id="userName">
                用户:{{username}}
                <span style='float: right;color: green' v-if="isOnline">在线</span>
                <span style='float: right;color: red' v-else>离线</span>
            </div>
            <div id="chatMes" v-show="chatMes" style="text-align: center;color: #6fbdf3;font-family: 新宋体">
                正在和 <font face="楷体">{{toName}}</font> 聊天
            </div>
        </div>
        <!--聊天区开始-->
        <div class="abs cover pnl-body" id="pnlBody">
            <div class="abs cover pnl-left" id="initBackground" style="background-color: white; width: 100%">
                <div class="abs cover pnl-left" id="chatArea" v-show="isShowChat">
                    <div class="abs cover pnl-msgs scroll" id="show">
                        <div class="pnl-list" id="hists"><!-- 历史消息 --></div>
                        <div class="pnl-list" id="msgs" v-for="message in historyMessage">

                            <!-- 消息这展示区域 -->
                            <div class="msg guest" v-if="message.toName">
                                <div class="msg-right">
                                    <div class="msg-host headDefault"></div>
                                    <div class="msg-ball">{{message.message}}</div>
                                </div>
                            </div>
                            <div class="msg robot" v-else>
                                <div class="msg-left" worker="">
                                    <div class="msg-host photo"
                                         style="background-image: url(/img/avatar/Member002.jpg)"></div>
                                    <div class="msg-ball">{{message.message}}</div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="abs bottom pnl-text">
                        <div class="abs cover pnl-input">
                            <textarea class="scroll" id="context_text" @keyup.enter="submit" wrap="hard" placeholder="在此输入文字信息..."
                                      v-model="sendMessage.message"></textarea>
                            <div class="abs atcom-pnl scroll hide" id="atcomPnl">
                                <ul class="atcom" id="atcom"></ul>
                            </div>
                        </div>

                        <div class="abs br pnl-btn" id="submit" @click="submit"
                             style="background-color: rgb(32, 196, 202); color: rgb(255, 255, 255);">
                            发送
                        </div>
                        <div class="pnl-support" id="copyright"><a href="http://www.itcast.cn">随意石光,版本所有</a></div>
                    </div>
                </div>

                <!--聊天区 结束-->
                <div class="abs right pnl-right">
                    <div class="slider-container hide"></div>
                    <div class="pnl-right-content">
                        <div class="pnl-tabs">
                            <div class="tab-btn active" id="hot-tab">好友列表</div>
                        </div>
                        <div class="pnl-hot">
                            <ul class="rel-list unselect">
                                <li class="rel-item" v-for="friend in friendsList"><a @click='showChat(friend)'>{{friend}}</a>
                                </li>
                            </ul>
                        </div>
                    </div>

                    <div class="pnl-right-content">
                        <div class="pnl-tabs">
                            <div class="tab-btn active">系统广播</div>
                        </div>
                        <div class="pnl-hot">
                            <ul class="rel-list unselect" id="broadcastList">
                                <li class="rel-item" style="color: #9d9d9d;font-family: 宋体" v-for="name in systemMessages">您的好友
                                    {{name}} 已上线</li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script src="/js/vue.js"></script>
<script src="/js/axios-0.18.0.js"></script>
<script>
    let ws;
    new Vue({
        el: "#app",
        data() {
            return {
                isShowChat: false,
                chatMes: false,
                isOnline: true,
                username:"",
                toName: "",
                sendMessage: {
                    toName: "",
                    message: ""
                },
                inputMessage: "",
                historyMessage: [],
                friendsList: [],
                systemMessages : []
            }
        },
        created() {
            this.init();
        },
        methods: {
            async init() {
                await axios.get("/websocket/user/getUsername").then(res => {
                    this.username = res.data;
                });
                //创建webSocket对象
                //ws = new WebSocket("ws://47.97.50.125:8989/chat");
                ws = new WebSocket("ws://127.0.0.1:8989/chat");
                //给ws绑定事件
                ws.onopen = this.onopen;
                //接收到服务端推送的消息后触发
                ws.onmessage = this.onMessage;
                ws.onclose = this.onClose;
            },
            showChat(name) {
                this.toName = name;
                //清除聊天区的数据
                let history = sessionStorage.getItem(this.toName);
                if (!history) {
                    this.historyMessage = [];
                } else {
                    this.historyMessage = JSON.parse(history);
                }
                //展示聊天对话框
                this.isShowChat = true;
                //显示“正在和谁聊天”
                this.chatMes = true;
            },
            submit() {
                this.sendMessage.toName = this.toName;
                this.historyMessage.push(JSON.parse(JSON.stringify(this.sendMessage)));
                sessionStorage.setItem(this.toName, JSON.stringify(this.historyMessage));
                ws.send(JSON.stringify(this.sendMessage));
                this.sendMessage.message = "";
            },
            onOpen() {
                 this.isOnline = true;
            },
            onClose() {
                this.isOnline = false;
            },
            onMessage(evt) {
                //获取服务端推送过来的消息
                var dataStr = evt.data;
                //将dataStr 转换为json对象
                var res = JSON.parse(dataStr);

                //判断是否是系统消息
                if(res.system) {
                    //系统消息  好友列表展示
                    let names = res.message;
                    this.friendsList = [];
                    this.systemMessages = [];
                    for (let i = 0; i < names.length; i++) {
                        if(names[i] != this.username) {
                            this.friendsList.push(names[i]);
                            this.systemMessages.push(names[i]);
                        }
                    }
                }else {
                    //非系统消息
                    let history = sessionStorage.getItem(res.fromName);
                    if (!history) {
                        this.historyMessage = [res];
                    } else {
                        this.historyMessage.push(res);
                    }
                    sessionStorage.setItem(res.fromName, JSON.stringify(this.historyMessage));
                }
            }
        }
    });


</script>
</body>
</html>

5.3 发送消息实体类

5.3.1 客户端 --> 服务端

{“toName”:“张三”,“message”:“你好”}


    import lombok.Data;

/**
 * @version v1.0
 * @ClassName: ClientToServerMessage
 * @Description: 客户端 ->服务端  {"toName":"张三","message":"hello world"}
 * @Author: ikun
 */
@Data
public class ClientToServerMessage {

    /**
     * 发送给谁
     */
    private String toName;

    /**
     * 发送的内容
     */
    private String message;
}

5.3.2 服务端 --> 客户端

  • 系统消息格式:{“system”:true,“fromName”:null,“message”:[“李四”,“王五”]}
  • 推送给某一个用户的消息格式:{“system”:false,“fromName”:“张三”,“message”:“你好”}
import lombok.Data;

/**
 * @version v1.0
 * @ClassName: ServerToClientMessage
 * @Description: 服务端 -> 浏览器
 */
@Data
public class ServerToClientMessage {

    /**
     * 是否系统消息
     */
    private boolean isSystem;

    /**
     * 需要发送给那个人,如果是系统消息,可以为空
     */
    private String fromName;

    /**
     * 消息的内容
     * 如果是系统消息是数组
     */
    private Object message;
}

5.4 编写websocket配置类

  • 扫描添加有@ServerEndpoint注解的 Bean
/**
 * @author ikun
 */
@Configuration
public class WebSocketConfig {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
  • 获取 HttpSession 对象
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

/**
 * @version v1.0
 * @ClassName: GetHttpSessionConfig
 * @Description: TODO(一句话描述该类的功能)
 */
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取HttpSession对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        //将httpSession对象保存起来
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}

5.5 登录以及页面跳转

import com.sysg.websocket.entity.Result;
import com.sysg.websocket.entity.User;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;


@RestController
@RequestMapping("/websocket/user")
public class WebsocketUserController {

    @RequestMapping("/main.html")
    public String getMainHtml(){
        return "websocket/main";

    }

    /**
     * 登录
     * @param user 提交的用户数据,包含用户名和密码
     * @param session
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody User user, HttpSession session) {
        Result result = new Result();
        if(user != null && "123".equals(user.getPassword())) {
            result.setFlag(true);
            //将数据存储到session对象中
            session.setAttribute("user",user.getUsername());
        } else {
            result.setFlag(false);
            result.setMessage("登陆失败");
        }
        return result;
    }

    /**
     * 获取用户名
     * @param session
     * @return
     */
    @GetMapping("/getUsername")
    public String getUsername(HttpSession session) {

        return (String) session.getAttribute("user");
    }
}

5.6 后台核心代码

import com.alibaba.fastjson.JSON;
import com.sysg.websocket.config.GetHttpSessionConfig;
import com.sysg.websocket.utils.MessageUtils;
import com.sysg.websocket.ws.pojo.ClientToServerMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @version v1.0
 * @ClassName: ChatEndpoint
 * @Description: TODO(一句话描述该类的功能)
 */
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)
@Component
@Slf4j
public class ChatEndpoint {
    /**
     *用来存储每一个客户端对象对应的ChatEndpoint对象
     */
    private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>();
    /**
     * 声明session对象,通过该对象可以发送消息给指定用户
     */
    //private Session session;

    /**
     * httpSession存储了用户名
     */
    private HttpSession httpSession;

    /**
     * 建立websocket连接后,被调用
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        //1,将session进行保存
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        String user = (String) this.httpSession.getAttribute("user");
        onlineUsers.put(user,session);
        //2,广播消息。需要将登陆的所有的用户推送给所有的用户
        String message = MessageUtils.getMessage(true,null, getFriends());
        broadcastAllUsers(message);
    }

    /**
     * 获取当前在线用户
     * @return
     */
    public Set getFriends() {
        Set<String> set = onlineUsers.keySet();
        return set;
    }

    /**
     * 广播消息给客户端
     * @param message
     */
    private void broadcastAllUsers(String message) {
        try {
            //遍历map集合
            Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();
            for (Map.Entry<String, Session> entry : entries) {
                //获取到所有用户对应的session对象
                Session session = entry.getValue();
                //发送消息
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            //记录日志
            log.error(e.getMessage());
        }
    }

    /**
     * 浏览器发送消息到服务端,该方法被调用
     *
     * 张三  -->  李四
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        try {
            //将消息推送给指定的用户
            ClientToServerMessage msg = JSON.parseObject(message, ClientToServerMessage.class);
            //获取 消息接收方的用户名
            String toName = msg.getToName();
            String mess = msg.getMessage();
            //获取消息接收方用户对象的session对象
            Session session = onlineUsers.get(toName);
            String user = (String) this.httpSession.getAttribute("user");
            String msg1 = MessageUtils.getMessage(false, user, mess);
            session.getBasicRemote().sendText(msg1);
        } catch (Exception e) {
            //记录日志
            log.error(e.getMessage());
        }
    }

    /**
     * 断开 websocket 连接时被调用
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        //1,从onlineUsers中剔除当前用户的session对象
        String user = (String) this.httpSession.getAttribute("user");
        onlineUsers.remove(user);
        //2,通知其他所有的用户,当前用户下线了
        String message = MessageUtils.getMessage(true,null,getFriends());
        broadcastAllUsers(message);
    }
}
import com.alibaba.fastjson.JSON;
import com.sysg.websocket.ws.pojo.ServerToClientMessage;

/**
 * @version v1.0
 * @ClassName: MessageUtils
 * @Description: 封装json格式消息的工具类
 */
public class MessageUtils {
    /**
     * 将数据转化为json格式的数据
     * @param isSystemMessage
     * @param fromName
     * @param message
     * @return
     */
    public static String getMessage(boolean isSystemMessage,String fromName, Object message) {

        ServerToClientMessage result = new ServerToClientMessage();
        result.setSystem(isSystemMessage);
        result.setMessage(message);
        if(fromName != null) {
            result.setFromName(fromName);
        }
        return JSON.toJSONString(result);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513955.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习之-Spring Cache缓存框架应用本地缓存

此文章用于个人学习记录&#xff0c;原文地址&#xff1a;https://zhuanlan.zhihu.com/p/452315531 如果想了解springCache与redis的交互请看其他文章 缓存是web项目不可或缺的一部分&#xff0c;通过缓存能够降低服务器数据库压力&#xff0c;提高服务器的稳定性及响应速度。…

Rust + 嵌入式:强力开发组合

Rust 的由来 Rust 编程语言的灵感诞生于一次意外。2006年&#xff0c;当 Graydon Hoare 回到位于温哥华的公寓时&#xff0c;发现电梯又因为软件崩溃出了故障。住在 21 楼的他无奈爬楼时&#xff0c;不禁心想&#xff0c;“我们搞计算机的&#xff0c;怎么连个能正常运行的电梯…

接口测试要会什么技能?测试老鸟总结,从接口到接口自动化全面盲扫...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Python自动化测试&…

YOLOv5改进:引入DenseNet思想打造密集连接模块,彻底提升目标检测性能

目录 一、密集连接模块的介绍1、密集连接的概念2、密集连接与残差连接的对比3、DenseNet的结构 二、 YOLOv5中引入密集连接模块的原因1、密集连接模块对于目标检测的优势2、密集连接模块对目标检测性能的影响 三、 YOLOv5中密集连接模块的具体实现1、使用DenseNet的基本单元Den…

怎样才能尽快从开发岗转到产品经理岗位?

越来越多的开发同学随着工作年限的增长都会产生类似的想法。 当然&#xff0c;背后的原因也是多种多样&#xff0c;像薪资上的限制、行业前景的担忧等等&#xff0c;很多同学则踌躇在原地不敢转产品岗位&#xff0c;而有的同学则是通过各种不同方式顺利转岗到产品经理岗位&…

树莓派从源码构建安装Git最新版

1、查看Git版本 首先我们通过SSH客户端连接树莓派,在树莓派中通过查看 Git 版本信息&#xff0c;我们只能看到最高版本显示为 2.30.2&#xff0c;并且通过apt安装也无法将Git更新到最新版。 git --version sudo apt upgrade git那么我们只能通过从源代码来构建安装Git了&…

单片机复习题第二章

1.在AT89S52单片机中&#xff0c;如果采用6MHz晶振&#xff0c;一个机器周期为2us。 时钟周期的振荡频率为fosc&#xff0c;则时钟周期T1/fosc。 一个机器周期包括12个时钟周期。 2.内部RAM中&#xff0c;位地址为40H&#xff0c;88H的位&#xff0c;该位所在字节的字节地址分别…

scrollIntoView 的使用

描述 将调用此方法的元素滚动到浏览器窗口的可见区域。 scrollIntoView 官方文档 用法 element.scrollIntoView() 用法同 element.scrollIntoView(true) element.scrollIntoView(alignToTop) true 表示 element 元素顶部与可见区域的顶部对齐&#xff0c;默认值false 表示 el…

基于html+css的图展示56

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Packet Tracer - 第 2 层 VLAN 安全

Packet Tracer - 第 2 层 VLAN 安全 目标 在 SW-1 和 SW-2 之间连接新的冗余链路。 启用中继&#xff0c;并在 SW-1 和 SW-2 之间的新中继链路上 配置安全。 创建新的管理 VLAN (VLAN 20) 并将管理 PC 连接到 该 VLAN。 实施 ACL 以阻…

Java学习-GUI编程-JFrame窗口

Java学习-GUI编程-JFrame窗口 public class TestJFrame {public void init(){JFrame jf new JFrame("这是一个JFream窗口");jf.setVisible(true);jf.setBounds(100,100,400,400);JLabel label new JLabel("这是一个label");label.setHorizontalAlignmen…

Linux之shell函数和正则表达式(八)

1、函数 1、函数概述 shell 中允许将一组命令集合成语句形成一段可用代码&#xff0c;这些代码块称为 shell 函数&#xff0c;给这段代码起个名字称为函数名&#xff0c;后续可以直接调用该段代码的功能 2、定义函数 函数名() {函数体&#xff08;一堆命令的集合&#xff0…

程序员的下一个风口

面对近一年的裁员潮&#xff0c;以及 GPT 出现带来的 AI 颠覆潮流&#xff0c;各种话题出现&#xff1a;「前端已死」、「后端已死」、「Copy/Paste 程序员将被 AI 取代」。程序员行业是否还有发展空间&#xff1f; 这一两年的就业机会是因为经济衰落周期内造成的&#xff0c;不…

pmp备考有哪些适合新手的学习资料?

在备考中&#xff0c;无论你是胸有成竹的学霸还是忐忑不安的学渣&#xff0c;强烈建议你是时候展现真正的技术了——临阵磨枪不快也光&#xff01;你准备好了吗&#xff1f;前方为大家准备了高能备考技巧和干货资料包&#xff0c;拿起热水杯&#xff0c;准备走起。 1.PMBOK知识…

怎样使用Web自动化测试减少手动劳动?以百度网站为例

从入门到精通&#xff01;企业级接口自动化测试实战&#xff0c;详细教学&#xff01;&#xff08;自学必备视频&#xff09; 目录 摘要 步骤1&#xff1a;安装和配置Selenium 步骤2&#xff1a;启动浏览器并访问百度网站 步骤3&#xff1a;关闭浏览器 总结 摘要 本指南将…

数据风险评估

通过数据风险评估定位敏感数据并分析其漏洞。根据文件的敏感度对文件进行分类&#xff0c;并确保为它们提供必要的保护。 发现、分析和保护敏感数据 查找所有个人数据根据敏感度对文件进行分类遵守 IT 法规分析文件和权限 查找所有个人数据 监控企业存储环境并接收有关敏感…

(CVE-2022-22965)Spring Framework 远程命令执行漏洞(vulfocus复现)

漏洞原理 该漏洞是SpringFramework数据绑定的一个漏洞&#xff0c;如果后台方法中接受的参数为非基础类型&#xff0c;Spring会根据前端传入的请求正文中的参数的key值来查询与其名称所对应的getter和setter方法&#xff0c;攻击者利用这一特性修改了Tomcat的一个用于日志记录…

java开发记录v2(引入jar,问题及解决)

引入本地jar包 在src同级目录下新建libs目录将jar包放入进去 在pom.xml中下加入以下代码 <dependencies><dependency><groupId>com.hikvision.ga</groupId><artifactId>artemis-http-client</artifactId><version>1.1.8</ver…

Acer非凡X14笔记本电脑蓝屏了怎么U盘重装系统?

Acer非凡X14笔记本电脑蓝屏了怎么U盘重装系统&#xff1f;有用户在使用Acer非凡X14笔记本电脑的时候&#xff0c;系统开机后出现了蓝屏的情况&#xff0c;导致自己的电脑无法正常的去进行使用。那么这个情况怎么去通过U盘重装系统的方法来恢复系统的使用呢&#xff1f;一起来看…

软考A计划-重点考点-专题十二(JAVA程序设计)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…