美文网首页
Spring Boot 集成 WebSocket,实现前后端即时

Spring Boot 集成 WebSocket,实现前后端即时

作者: 陈二狗想吃肉 | 来源:发表于2021-05-24 09:15 被阅读0次

    一、什么是websocket?

    WebSocket协议是基于TCP的一种新的网络协议。它实现了客户端与服务器全双工通信,学过计算机网络都知道,既然是全双工,就说明了服务器可以主动发送信息给客户端 。这与我们的推送技术或者是多人在线聊天的功能不谋而合。

    为什么不使用HTTP 协议呢?这是因为HTTP是单工通信,通信只能由客户端发起,客户端请求一下,服务器处理一下,这就太麻烦了。于是websocket应运而生。

    下面我们就直接开始使用Springboot开始整合。以下案例都在我自己的电脑上测试成功,你可以根据自己的功能进行修改即可。我的项目结构如下:

    二、使用步骤

    1.添加依赖

    Maven依赖:

    org.springframework.boot

    spring-boot-starter-websocket

    2.启用Springboot对WebSocket的支持

    启用WebSocket的支持也是很简单,几句代码搞定:

    importorg.springframework.context.annotation.Bean;

    importorg.springframework.context.annotation.Configuration;

    importorg.springframework.web.socket.server.standard.ServerEndpointExporter;

    /**

    * @ Auther: 马超伟

    * @ Date: 2020/06/16/14:35

    * @ Description: 开启WebSocket支持

    */

    @Configuration

    publicclassWebSocketConfig{

    @Bean

    publicServerEndpointExporterserverEndpointExporter(){

    returnnewServerEndpointExporter();

    }

    }

    3.核心配置:WebSocketServer

    因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

    @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端

    新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便传递之间对userId进行推送消息。

    下面是具体业务代码:

    packagecc.mrbird.febs.external.webScoket;

    importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

    importcom.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;

    importlombok.extern.slf4j.Slf4j;

    importorg.springframework.stereotype.Component;

    importorg.springframework.stereotype.Service;

    importjavax.websocket.*;

    importjavax.websocket.server.PathParam;

    importjavax.websocket.server.ServerEndpoint;

    importjava.io.IOException;

    importjava.time.LocalDateTime;

    importjava.util.List;

    importjava.util.concurrent.CopyOnWriteArraySet;

    /**

    * Created with IntelliJ IDEA.

    * @ Auther: 马超伟

    * @ Date: 2020/06/16/14:35

    * @ Description:

    * @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,

    * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端

    */

    @Component

    @Slf4j

    @Service

    @ServerEndpoint("/api/websocket/{sid}")

    publicclassWebSocketServer{

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。

    privatestaticintonlineCount =0;

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。

    privatestaticCopyOnWriteArraySet webSocketSet =newCopyOnWriteArraySet();

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

    privateSession session;

    //接收sid

    privateString sid ="";

    /**

    * 连接建立成功调用的方法

    */

    @OnOpen

    publicvoidonOpen(Session session, @PathParam("sid")String sid){

    this.session = session;

    webSocketSet.add(this);//加入set中

    this.sid = sid;

    addOnlineCount();//在线数加1

    try{

    sendMessage("conn_success");

    log.info("有新窗口开始监听:"+ sid +",当前在线人数为:"+ getOnlineCount());

    }catch(IOException e) {

    log.error("websocket IO Exception");

    }

    }

    /**

    * 连接关闭调用的方法

    */

    @OnClose

    publicvoidonClose(){

    webSocketSet.remove(this);//从set中删除

    subOnlineCount();//在线数减1

    //断开连接情况下,更新主板占用情况为释放

    log.info("释放的sid为:"+sid);

    //这里写你 释放的时候,要处理的业务

    log.info("有一连接关闭!当前在线人数为"+ getOnlineCount());

    }

    /**

    * 收到客户端消息后调用的方法

    * @ Param message 客户端发送过来的消息

    */

    @OnMessage

    publicvoidonMessage(String message, Session session){

    log.info("收到来自窗口"+ sid +"的信息:"+ message);

    //群发消息

    for(WebSocketServer item : webSocketSet) {

    try{

    item.sendMessage(message);

    }catch(IOException e) {

    e.printStackTrace();

    }

    }

    }

    /**

    * @ Param session

    * @ Param error

    */

    @OnError

    publicvoidonError(Session session, Throwable error){

    log.error("发生错误");

    error.printStackTrace();

    }

    /**

    * 实现服务器主动推送

    */

    publicvoidsendMessage(String message)throwsIOException{

    this.session.getBasicRemote().sendText(message);

    }

    /**

    * 群发自定义消息

    */

    publicstaticvoidsendInfo(String message, @PathParam("sid")String sid)throwsIOException{

    log.info("推送消息到窗口"+ sid +",推送内容:"+ message);

    for(WebSocketServer item : webSocketSet) {

    try{

    //这里可以设定只推送给这个sid的,为null则全部推送

    if(sid ==null) {

    //                    item.sendMessage(message);

    }elseif(item.sid.equals(sid)) {

    item.sendMessage(message);

    }

    }catch(IOException e) {

    continue;

    }

    }

    }

    publicstaticsynchronizedintgetOnlineCount(){

    returnonlineCount;

    }

    publicstaticsynchronizedvoidaddOnlineCount(){

    WebSocketServer.onlineCount++;

    }

    publicstaticsynchronizedvoidsubOnlineCount(){

    WebSocketServer.onlineCount--;

    }

    publicstaticCopyOnWriteArraySetgetWebSocketSet(){

    returnwebSocketSet;

    }

    }

    4.测试Controller

    importorg.springframework.stereotype.Controller;

    importorg.springframework.web.bind.annotation.GetMapping;

    importorg.springframework.web.bind.annotation.PathVariable;

    importorg.springframework.web.bind.annotation.RequestMapping;

    importorg.springframework.web.bind.annotation.ResponseBody;

    importorg.springframework.web.servlet.ModelAndView;

    importjava.io.IOException;

    importjava.util.HashMap;

    importjava.util.Map;

    /**

    * Created with IntelliJ IDEA.

    *

    * @ Auther: 马超伟

    * @ Date: 2020/06/16/14:38

    * @ Description:

    */

    @Controller("web_Scoket_system")

    @RequestMapping("/api/socket")

    publicclassSystemController{

    //页面请求

    @GetMapping("/index/{userId}")

    publicModelAndViewsocket(@PathVariable String userId){

    ModelAndView mav =newModelAndView("/socket1");

    mav.addObject("userId", userId);

    returnmav;

    }

    //推送数据接口

    @ResponseBody

    @RequestMapping("/socket/push/{cid}")

    publicMappushToWeb(@PathVariable String cid, String message){

    Map result =newHashMap<>();

    try{

    WebSocketServer.sendInfo(message, cid);

    result.put("code", cid);

    result.put("msg", message);

    }catch(IOException e) {

    e.printStackTrace();

    }

    returnresult;

    }

    }

    5.测试页面index.html

    Java后端WebSocket的Tomcat实现

    Welcome

    发送消息


    关闭WebSocket连接


    varwebsocket =null;

    //判断当前浏览器是否支持WebSocket

    if('WebSocket'inwindow) {

    //改成你的地址

    websocket =newWebSocket("ws://192.168.100.196:8082/api/websocket/100");

    }else{

    alert('当前浏览器 Not support websocket')

    }

    //连接发生错误的回调方法

    websocket.onerror =function(){

    setMessageInnerHTML("WebSocket连接发生错误");

    };

    //连接成功建立的回调方法

    websocket.onopen =function(){

    setMessageInnerHTML("WebSocket连接成功");

    }

    varU01data, Uidata, Usdata

    //接收到消息的回调方法

    websocket.onmessage =function(event){

    console.log(event);

    setMessageInnerHTML(event);

    setechart()

    }

    //连接关闭的回调方法

    websocket.onclose =function(){

    setMessageInnerHTML("WebSocket连接关闭");

    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。

    window.onbeforeunload =function(){

    closeWebSocket();

    }

    //将消息显示在网页上

    functionsetMessageInnerHTML(innerHTML){

    document.getElementById('message').innerHTML += innerHTML +'<br/>';

    }

    //关闭WebSocket连接

    functioncloseWebSocket(){

    websocket.close();

    }

    //发送消息

    functionsend(){

    varmessage =document.getElementById('text').value;

    websocket.send('{"msg":"'+ message +'"}');

    setMessageInnerHTML(message +"&#13;");

    }

    6.结果展示

    后台:如果有连接请求

    前台显示:

    总结

    这中间我遇到一个问题,就是说WebSocket启动的时候优先于spring容器,从而导致在WebSocketServer中调用业务Service会报空指针异常

    所以需要在WebSocketServer中将所需要用到的service给静态初始化一下:如图所示:

    还需要做如下配置:

    相关文章

      网友评论

          本文标题:Spring Boot 集成 WebSocket,实现前后端即时

          本文链接:https://www.haomeiwen.com/subject/yvotsltx.html