Spring Boot之集成WebSocket实现前后端通信,这你必须得会!

boyanx1周前技术教程3

1. 前言
这将又会是干货满满的一期,全程无尿点不废话只抓重点教,具有非常好的学习效果,拿好小板凳准备就坐!希望学习的过程中大家认真听好好学,学习的途中有任何不清楚或疑问的地方皆可评论区留言或私信,bug菌将第一时间给予解惑,那么废话不多说,直接开整!Fighting!!

2. 环境说明

本地的开发环境:

开发工具:IDEA 2021.3 JDK版本: JDK 1.8 Spring Boot版本:2.3.1 RELEASE Maven版本:3.8.2

3. 集成WebSocket

3.1 添加WebSocket依赖

在你项目的pom.xml文件中添加以下包。

xml复制代码<!--集成WebSocket-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

<!--Thymeleaf 静态模板-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.51</version>
</dependency>

3.2 添加WebSocket配置类

我们需要对WebSocket进行配置,以开启对WebSocket的使用。

typescript复制代码@Configuration
public class WebSocketConfig {

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

3.3 创建服务端WebSocket的server端

如下我们来定义一个类,专门把WebSocket相关的启动类及相关方法封装好,这也是服务端对WebSocket的支持核心。

typescript复制代码@Component
@ServerEndpoint("/server/{uid}")
@Slf4j
public class WebSocketServer {

    /**
     * 用来记录当前在线连接数量,应该把它设计成线程安全的。
     */
    private static int onlineCount = 0;

    /**
     * concurrent包是线程安全的Set,用来存放每个客户端对应的WebSocket对象。
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据.
     */
    private Session session;

    /**
     * 接收客户端消息的uid
     */
    private String uid = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("uid") String uid) {
        this.session = session;
        this.uid = uid;
        if (webSocketMap.containsKey(uid)) {
            webSocketMap.remove(uid);
            //加入到set中
            webSocketMap.put(uid, this);
        } else {
            //加入set中
            webSocketMap.put(uid, this);
            //在线数加1
            addOnlineCount();
        }

        log.info("用户连接:" + uid + ",当前在线人数为:" + getOnlineCount());

        try {
            sendMsg("连接成功");
        } catch (IOException e) {
            log.error("用户:" + uid + ",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(uid)) {
            webSocketMap.remove(uid);
            //从set中删除
            subOnlineCount();
        }
        log.info("用户退出:" + uid + ",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户id:" + uid + ",接收到的报文:" + message);
        //可以群发消息
        //消息保存到数据库、redis
        if (!StringUtils.isEmpty(message)) {
            try {
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                //追加发送人(防止串改)
                jsonObject.put("fromUID", this.uid);
                String toUID = jsonObject.getString("toUID");
                //传送给对应的toUserId用户的WebSocket
                if (!StringUtils.isEmpty(toUID) && webSocketMap.containsKey(toUID)) {
                    webSocketMap.get(toUID).sendMsg(jsonObject.toJSONString());
                } else {
                    //若果不在这个服务器上,可以考虑发送到mysql或者redis
                    log.error("请求的UserId:" + toUID + "不在该服务器上");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 处理错误
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户错误:" + this.uid + ",原因:" + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    private void sendMsg(String msg) throws IOException {
        this.session.getBasicRemote().sendText(msg);
    }

    /**
     * 发送自定义消息
     */
    public static void sendInfo(String message, @PathParam("uid") String uid) throws IOException {
        log.info("发送消息到:" + uid + ",发送的报文:" + message);
        if (!StringUtils.isEmpty(uid) && webSocketMap.containsKey(uid)) {
            webSocketMap.get(uid).sendMsg(message);
        } else {
            log.error("用户" + uid + ",不在线!");
        }
    }

    private static synchronized int getOnlineCount() {
        return onlineCount;
    }

    private static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    private static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}

拓展:

给大家科普一下,以上所用到的几个注解。分别如下:



注解

作用

@ServerEndpoint

创建了一个服务器端的URI,客户端可以通过这个URL来连接到WebSocket服务器端

@OnOpen

打开连接触发事件

@OnMessage

收到消息触发事件

@OnClose

关闭连接触发事件

@OnError

处理错误

3.4 创建Controller接口

编写一个Controller,用于定义WebSocket通信的接口。

less复制代码@RestController
@RequestMapping("/websocket")
@Api(tags = "WebSocket模块", description = "WebSocket模块")
public class WebSocketController {

    @GetMapping("/page")
    @ApiOperation(value = "跳转WebSocket微聊天室页面", notes = "跳转WebSocket微聊天室页面")
    public ModelAndView page() {
        return new ModelAndView("webSocket");
    }

    @GetMapping("/push/{toUID}")
    @ApiOperation(value = "指定用户进行消息推送", notes = "指定用户进行消息推送")
    public ResultResponse<String> pushToClient(String message, @PathVariable String toUID) throws IOException {
        WebSocketServer.sendInfo(message, toUID);
        return new ResultResponse<>("send ok!");
    }

}

3.5 创建页面进行WebSocket测试连接及发送消息

以下我们一切从简,我们就写个html来实现WebSocket的连接及消息推送。

xml复制代码<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebSocket微聊天室</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
    var socket;

    //打开WebSocket
    function openSocket() {
        if (typeof(WebSocket) === "undefined") {
            console.log("您的浏览器不支持WebSocket");
        } else {
            console.log("您的浏览器支持WebSocket");
            //实现化WebSocket对象,指定要连接的服务器地址与端口,建立连接.
            var socketUrl = "http://localhost:8080/server/" + $("#uid").val();
            //将https与http协议替换为ws协议
            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
            console.log(socketUrl);
            if (socket != null) {
                socket.close();
                socket = null;
            }
            socket = new WebSocket(socketUrl);
            //打开事件
            socket.onopen = function () {
                console.log("WebSocket已打开");
                //socket.send("这是来自客户端的消息" + location.href + new Date());
            };
            //获得消息事件
            socket.onmessage = function (msg) {
                console.log(msg.data);
                //发现消息进入,开始处理前端触发逻辑
            };
            //关闭事件
            socket.onclose = function () {
                console.log("WebSocket已关闭");
            };
            //发生了错误事件
            socket.onerror = function () {
                console.log("WebSocket发生了错误");
            }
        }
    }

    //发送消息
    function sendMessage() {
        if (typeof(WebSocket) === "undefined") {
            console.log("您的浏览器不支持WebSocket");
        } else {
            console.log("您的浏览器支持WebSocket");
            console.log('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');
            socket.send('{"toUID":"' + $("#toUID").val() + '","Msg":"' + $("#msg").val() + '"}');
        }
    }
</script>
<body>
<p>【uid】:
<div><input id="uid" name="uid" type="text" value="5"></div>
<p>【toUID】:
<div><input id="toUID" name="toUID" type="text" value="10"></div>
<p>【Msg】:
<div><input id="msg" name="msg" type="text" value="hello WebSocket"></div>
<p>【第一步操作:】:
<div><button onclick="openSocket()">开启socket</button></div>
<p>【第二步操作:】:
<div><button onclick="sendMessage()">发送消息</button></div>
</body>

</html>

3.6 项目结构

接下来,给大家看下我的项目结构目录。

4. 页面测试

我们既可以通过访问
http://localhost:8080/websocket/page,也可以直接打开webSocket.html页面,两种方式随你哪一种。

接着,我们先来开启客户端A注册一个在线用户,uid=‘张三’。然后点击【开启socket】,我们可以看到控制台,是成功连接了服务端的WebSocket服务的。

你也可以检查服务端的控制台,也可以看到,[张三]用户已经登录在线了,且在线人数为1。

我们再开启客户端B也注册一个在线用户。uid=‘李四’,点击【开启socket】连接,查看控制台打印,是成功连接了服务端的WebSocket服务。

你也查看系统控制台,可以看到共2个在线用户,分别为[张三]和[李四]。

接下来我们用客户端A给客户端B进行信息推送,信息内容为:你好李四,我是张三!

控制台是成功获取到了客户端A用户的消息推送。

并且客户端B也是捕获到了来自客户端A推送的消息。

相反客户端B在收到客户端A的广播消息,于是进行消息回复。我们就通过客户端B进行推送消息,发送消息内容为:你好张三,我是李四!

接下来,我们来查看下客户端A的控制台,显然是成功接收到了来自客户端B推送的消息。

查看服务端控制台也是可以得知。

以上是客户端开启WebSocket连接后进行的单通道推送。

5. 服务端推送消息到指定客户端

接下来我们再来测试一下通过服务端向指定客户端进行消息推送。

我们如上是有实现一个发送消息的接口,我们只需要在swagger中将收件人toUID及sendMsg指定即可,具体如下:

查看控制台,我们可以看到,消息是被成功推送的。

接着我们来验证一下,客户端B是否有接收到来自服务器端的消息。

经测试,客户端A是没接收到消息的;而客户端B是完整接收到了来自服务器端的消息。

相反,我们只发送到客户端A,测试过程也是的。这里就不给大家具体演示了,我们直接看结果。

ok,以上就是WebSocket通讯的简单玩法,更多有趣的玩法还得靠自己去琢磨啦。

... ...

相关文章

前端架构师成长之路:避免将 JWT 存储在 localStorage 中

看到很多文章介绍将 JWT 存储在 localStorage 中,事实上,个人觉得建议最好不要。这就是将敏感信息存储在 localStorage 中,对于安全问题来说是个挑战,对于客户端的行为在安全问...

Flask 数据可视化

数据可视化是数据处理中的重要部分,前面我们了解了 Flask 的开发和部署,如何用 Flask 做数据可视化呢?今天我们来了解一下。Python 语言极富表达力,并且拥有众多的数据分析库和框架,是数据...

ThinkPHP 对接 DeepSeek 的教程(以 ThinkPHP 6.x 版本为例)

一、准备工作注册 DeepSeek 账号并获取 API Key查看 DeepSeek API 文档(假设接口地址为 https://api.deepseek.com/v1/chat/completio...

57.后端必备的前端技巧

文章目录前言1.jquery的两种按钮点击和发送请求:2.vue中的按钮定义和axios请求:总结前言现在都是前后端分离的开发,很多时候可能后端不会再写html,jquery了,但是一些场景之下后端还...

用Python+Eel写个Metro风格的web的GUI桌面程序

1 说明1.1 Eel是python的轻量级桌面GUI程序开发库。1.2 已介绍,暂时略。讲一个用Python写个Metro风格的桌面程序。1.3 资料来源,对代码进行注释。https://www.j...

57challenges-56个挑战-客户端设计(part4)

上题目:接昨天:https://www.toutiao.com/article/7125986007236936226/昨天解决了前端界面格式化的问题,今天完成把json格式转化为excel表格,并供...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。