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

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

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);
    }
}

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
WebSocket聊天项目是一种实时通信的应用程序,通过WebSocket协议实现客户端和服务器之间的双向通信。在项目中,客户端通过创建WebSocket对象来建立与服务器的连接,并通过发送和接收消息来实现聊天功能。 在项目中,通常会使用类似以下代码创建WebSocket对象: ```javascript websocket = new WebSocket("ws://127.0.0.1:3311/recruitment/websocketDemo/${session.username}"); ``` 其中,`ws://127.0.0.1:3311/recruitment/websocketDemo/${session.username}`是WebSocket的地址,包括本机地址、端口号、项目名和Websocket的处理器等信息。\[1\] 在实现WebSocket聊天项目时,可以参考一些资源,如Mozilla开发者网络提供的关于编写WebSocket客户端应用程序的文档\[2\],以及一些博客和教程\[2\] \[3\]。 具体实现时,用户通过账号密码登录后,通过WebSocket的onopen事件自动连接到聊天室。后台通过Map来记录用户连接情况。用户在聊天室中选择聊天对象,并在输入框中输入文字,然后点击发送按钮。JavaScript代码会响应按钮点击事件,将相关的JSON信息转换成字符串,并通过WebSocket的send()方法发送到后台服务器服务器接收到用户发送的信息后,解析并转发给另一个用户。另一个用户通过WebSocket的onmessage事件接收到消息,完成聊天功能的实现。\[3\] 总结起来,WebSocket聊天项目通过WebSocket协议实现了实时通信,用户可以在聊天室中发送和接收消息,实现了即时的聊天功能。 #### 引用[.reference_title] - *1* *2* *3* [WebSocket 实现聊天功能](https://blog.csdn.net/tuoniaoxs/article/details/116494440)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随意石光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值