【2023最新】SpringBoot整合WebSocket实现简易私聊聊天室

WebSocket是一种在单个TCP连接上进行全双工通讯的协议,常用于实现服务端主动向客户端推送数据的实时功能。相比HTTP的轮询或长轮询,WebSocket减少了开销,增强了实时性,但也存在网络限制和浏览器兼容性问题。文章介绍了WebSocket的基本概念、优缺点,以及使用WebSocket进行编码的步骤,包括配置类、服务器端服务编写和HTML交互页面的创建。并通过测试验证了系统的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、WebSocket是什么?

1.1 为什么会有WebSocket这项技术?

在比较早的时期,很多网站做一种实时推送的功能(服务端需要向客户端主动推送数据),所用的技术都是轮询/短轮询。轮询指的是客户端定期的向服务端发起HTTP请求来获取到服务端返回数据给客户端,WebSocket与轮询的区别如下:
在这里插入图片描述

1.2 简介

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 把客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

1.3 优缺点

优点:
  1. 减少开销:服务端能够主动向客户端推送数据,避免了不必要资源开销;
  2. 更强的实时性:因为协议是全双工的,所以服务器可以随时向客户端推送数据;相对HTTP请求需要等待客户端发起请求服务端才能够响应数据,延迟明显更少;
  3. 保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
  4. 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
缺点:
  1. 因为是长连接:所以受网络限制比较大,需要处理好重连机制,比如地铁这种网络比较差的环境容易断开,这个时候就需要重连了;
  2. 各个浏览器支持程度不一样;

二、编码步骤

1.引入依赖

代码如下(示例):

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

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.25</version>
        </dependency>

    </dependencies>

2.编写WebScoket配置类

代码如下(示例):

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

3.编写WebScoketServer服务

代码如下(示例):

@Component
@Slf4j
@ServerEndpoint("/webSocketServer/{myUserId}")
public class WebSocketServer {

    /**
     * 与客户端的连接会话,需要通过他来给客户端发消息
     */
    private Session session;

    /**
     * 当前用户ID
     */
    private String userId;

    /**
     *  concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     *  虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
     */
    private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>();

    /**
     *用来存在线连接用户信息
     */
    private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();

    /**
     * 连接成功方法
     * @param session 连接会话
     * @param userId 用户编号
     */
    @OnOpen
    public void onOpen(Session session , @PathParam("myUserId") String userId){
        try {
            this.session = session;
            this.userId = userId;
            webSockets.add(this);
            sessionPool.put(userId, session);
            log.info("【websocket消息】 用户:" + userId + " 加入连接...");
        } catch (Exception e) {
            log.error("---------------WebSocket连接异常---------------");
        }
    }

    /**
     * 关闭连接
     */
    @OnClose
    public void onClose(){
        try {
            webSockets.remove(this);
            sessionPool.remove(this.userId);
            log.info("【websocket消息】 用户:"+ this.userId + " 断开连接...");
        } catch (Exception e) {
            log.error("---------------WebSocket断开异常---------------");
        }
    }

    @OnMessage
    public void onMessage(@PathParam("myUserId") String userId, String body){
        try {
            //将Body解析
            JSONObject jsonObject = JSONObject.parseObject(body);
            //获取目标用户地址
            String targetUserId = jsonObject.getString("targetUserId");
            //获取需要发送的消息
            String message = jsonObject.getString("message");
            jsonObject.put("userId" , userId);
            if(userId.equals(targetUserId)){
                sendMoreMessage(new String[]{targetUserId} ,  JSONObject.toJSONString(jsonObject));
            }else{
                sendMoreMessage(new String[]{userId , targetUserId} ,  JSONObject.toJSONString(jsonObject));
            }
        } catch (Exception e) {
            log.error("---------------WebSocket消息异常---------------");
        }
    }


    /**
     * 此为广播消息
     * @param message
     */
    public void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:"+message);
        for(WebSocketServer webSocket : webSockets) {
            try {
                if(webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                log.error("---------------WebSocket消息广播异常---------------");
            }
        }
    }

    /**
     * 单点消息
     * @param userId
     * @param message
     */
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null&&session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:"+message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                log.error("---------------WebSocket单点消息发送异常---------------");
            }
        }
    }

    /**
     * 发送多人单点消息
     * @param userIds
     * @param message
     */
    public void sendMoreMessage(String[] userIds, String message) {
        for(String userId:userIds) {
            Session session = sessionPool.get(userId);
            if (session != null&&session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:"+message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    log.error("---------------WebSocket多人单点消息发送异常---------------");
                }
            }
        }
    }
}

项目结构
在这里插入图片描述

4.编写一个简单的html进行交互

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <style>
        #content {
            overflow: auto;
            width: 500px;
            height: 300px;
            background-color: white;
        }

        body {
            background-color: lightblue;
        }
    </style>
</head>
<body>
<div>
    <div id="content">
    </div>
    <p><label for="myUserId">用户名称:</label><input type="text" name="myUserId" id="myUserId"
                                                     placeholder="用户名称"><input type="submit" value="登录"
                                                                                   id="login"/></p>
    <p><label for="targetUserId">目标用户名称:</label><input type="text" name="targetUserId" id="targetUserId"
                                                             placeholder="目标用户名称"></p>
    <textarea name="message" id="message" cols="38" rows="10" placeholder="消息..."></textarea>
    <p><input type="submit" id="send" value="发送"/></p>
    </form>
</div>

</body>
</html>

<script>
    $(function () {
        var websocket = null;
        $("#login").on('click', function () {
            let myUserId = $("#myUserId").val();
            websocket = new WebSocket("ws://localhost:8080/webSocketServer/" + myUserId);
            // 连接成功后的回调函数
            socket();
        });

        function socket(){
            websocket.onopen = function (params) {
                console.log('客户端连接成功')
            };

            websocket.onmessage = function (e) {
                var data = JSON.parse(e.data);
                if(data.userId == $("#myUserId").val()){
                    $("#content").append(`<div style="width: 500px;height:40px;line-height: 30px;"><p style="float:right;margin:0;padding:0;">我:${data.message}</p></div>`);
                }else{
                    $("#content").append(`<div style="width: 500px;height:40px;line-height: 30px;"><p style="float:left;margin:0;padding:0;">${data.userId}${data.message}</p></div>`);
                }
            };

            websocket.onclose = function (evt) {
                console.log("关闭客户端连接");
            };

            websocket.onerror = function (evt) {
                console.log("连接失败了");
            };
        }
        
        $("#send").on('click', function () {
            websocket.send(`{"targetUserId": "${$("#targetUserId").val()}" , "message": "${$("#message").val()}"}`);
        });
    });
</script>

三、测试

1.准备网页

在网页中,输入用户名称点击登录进行登录(在F12中看到客户端连接成功就表示成功登陆了):
在这里插入图片描述

2.发送消息

在网页中输入目标用户名称,然后输入需要发送的消息,点击发送即可:
在这里插入图片描述
到这简易的私聊聊天室就完成啦!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值