SpringBoot + Vue integra WebSocket para lograr el envío de mensajes de front-end y back-end

Escenas

WebSocket

El protocolo HTTP es un protocolo de capa de aplicación sin estado, sin conexión y unidireccional. Utiliza un modelo de solicitud / respuesta. La solicitud de comunicación solo puede ser iniciada por el cliente y el servidor responde a la solicitud.

Este modelo de comunicación tiene un inconveniente: el protocolo HTTP no puede permitir que el servidor inicie activamente mensajes al cliente.

La característica de esta solicitud unidireccional está destinada a ser muy problemático para que el cliente sepa si el servidor tiene cambios de estado continuos. La mayoría de las aplicaciones web implementarán un sondeo largo a través de frecuentes solicitudes asincrónicas de JavaScript y XML (AJAX). El sondeo es ineficiente y desperdicia recursos (porque la conexión debe mantenerse conectada o la conexión HTTP siempre está abierta).

Así es como se inventó WebSocket. La conexión WebSocket permite la comunicación full-duplex entre el cliente y el servidor para que cualquiera de las partes pueda enviar datos al otro extremo a través de la conexión establecida. WebSocket solo necesita establecer una conexión una vez y puede permanecer conectado todo el tiempo. En comparación con el establecimiento continuo de la conexión en el modo de sondeo, obviamente la eficiencia mejora enormemente.

Si sigue la versión separada de front-end y back-end para enseñarle a configurar el entorno localmente y ejecutar el proyecto:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662

Después de configurar los proyectos anteriores y posteriores. Realice la integración de WebSocket de SpringBoot y Vue en segundo plano.

Nota:

Blog:
https://blog.csdn.net/badao_liumang_qizhi
Siga la
cuenta pública
Programadores dominantes Obtenga libros electrónicos relacionados con la programación, instructivos y descargas gratuitas.

lograr

Integración SpringBoot

Primero introduzca las dependencias en el archivo pom

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

Luego cree una nueva clase de configuración para WebSocket para habilitar la compatibilidad con WebSocket

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

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

Luego cree una nueva clase de entidad cliente WebSocketClient para almacenar la sesión y la Uri conectadas

import javax.websocket.Session;

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

    //连接的uri
    private String uri;

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }
}

Luego cree un nuevo WebSocketService para crear y procesar conexiones

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint(value = "/websocket/{userName}")
@Component
public class WebSocketService {

    private static final Logger log = LoggerFactory.getLogger(WebSocketService.class);

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
    private static ConcurrentHashMap<String, WebSocketClient> webSocketMap = new ConcurrentHashMap<>();


    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private Session session;
    /**接收userName*/
    private String userName="";
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session, @PathParam("userName") String userName) {
        if(!webSocketMap.containsKey(userName))
        {
            addOnlineCount(); // 在线数 +1
        }
        this.session = session;
        this.userName= userName;
        WebSocketClient client = new WebSocketClient();
        client.setSession(session);
        client.setUri(session.getRequestURI().toString());
        webSocketMap.put(userName, client);

        log.info("----------------------------------------------------------------------------");
        log.info("用户连接:"+userName+",当前在线人数为:" + getOnlineCount());
        try {
            sendMessage("来自后台的反馈:连接成功");
        } catch (IOException e) {
            log.error("用户:"+userName+",网络异常!!!!!!");
        }
    }

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

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到用户消息:"+userName+",报文:"+message);
        //可以群发消息
        //消息保存到数据库、redis
        if(StringUtils.isNotBlank(message)){

        }
    }

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

    /**
     * 连接服务器成功后主动推送
     */
    public void sendMessage(String message) throws IOException {
        synchronized (session){
            this.session.getBasicRemote().sendText(message);
        }
    }

    /**
     * 向指定客户端发送消息
     * @param userName
     * @param message
     */
    public static void sendMessage(String userName,String message){
        try {
            WebSocketClient webSocketClient = webSocketMap.get(userName);
            if(webSocketClient!=null){
                webSocketClient.getSession().getBasicRemote().sendText(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
   

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

    public static synchronized void addOnlineCount() {
        WebSocketService.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketService.onlineCount--;
    }

    public static void setOnlineCount(int onlineCount) {
        WebSocketService.onlineCount = onlineCount;
    }


    public static ConcurrentHashMap<String, WebSocketClient> getWebSocketMap() {
        return webSocketMap;
    }

    public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketClient> webSocketMap) {
        WebSocketService.webSocketMap = webSocketMap;
    }

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

}

Tenga en cuenta que el WebSocketClient al que se hace referencia aquí es la clase de entidad creada anteriormente para almacenar información relacionada con la conexión.

然后 @ServerEndpoint (valor = "/ websocket / {userName}")

Aquí está la dirección de la conexión WebSocket, el siguiente {userName} se usa para recibir los parámetros pasados ​​por el front-end, usados ​​como un identificador diferente, y luego hacer un procesamiento diferente

Luego, el siguiente método anotado de OnOpen es el método que se llamará después de que la conexión se establezca correctamente, aquí está el proceso de conteo del número de usuarios en línea, puede hacerlo de acuerdo con el suyo

El negocio necesita ser resuelto.

Luego, OnClose anota el método llamado por la relación de conexión.

Luego, OnMessage anota el método de devolución de llamada cuando el cliente envía un mensaje, que procesa los datos de acuerdo con sus necesidades.

El método sendMessage utiliza la sesión actual para enviar un mensaje de retroalimentación al cliente cuando el cliente se conecta al servidor.

Luego, envíe un mensaje al cliente especificado mediante el método sendMessage, que pasa el identificador único del usuario y el contenido del mensaje.

Cree una nueva interfaz de controlador para probar el envío de mensajes

import com.ruoyi.web.websocket.WebSocketService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/websocket")
public class WebSocketController {


    @GetMapping("/pushone")
    public void pushone()
    {
        WebSocketService.sendMessage("badao","公众号:霸道的程序猿");
    }
}

Si se trata de un proyecto SpringBoot normal, el backend está bien. Entonces, debido a que se usa el marco creado por Ruoyi anteriormente, la URL de ws y la URL de la interfaz deben liberarse para la autenticación de autorización.

 

Integración del proyecto Vue

Cree un nuevo componente WebSocket, que se coloca en el directorio de componentes.

 

Luego declara algunas variables

        data() {
            return {
                // ws是否启动
                wsIsRun: false,
                // 定义ws对象
                webSocket: null,
                // ws请求链接(类似于ws后台地址)
                ws: '',
                // ws定时器
                wsTimer: null,
            }
        },

Ejecute el método de inicialización de la conexión websocket en la función montada

        async mounted() {
            this.wsIsRun = true
            this.wsInit()
        },

En la implementación del método de inicialización

           const wsuri = 'ws://你的后台ip:7777/websocket/badao'
                this.ws = wsuri
                if (!this.wsIsRun) return
                // 销毁ws
                this.wsDestroy()
                // 初始化ws
                this.webSocket = new WebSocket(this.ws)
                // ws连接建立时触发
                this.webSocket.addEventListener('open', this.wsOpenHanler)
                // ws服务端给客户端推送消息
                this.webSocket.addEventListener('message', this.wsMessageHanler)
                // ws通信发生错误时触发
                this.webSocket.addEventListener('error', this.wsErrorHanler)
                // ws关闭时触发
                this.webSocket.addEventListener('close', this.wsCloseHanler)

                // 检查ws连接状态,readyState值为0表示尚未连接,1表示建立连接,2正在关闭连接,3已经关闭或无法打开
                clearInterval(this.wsTimer)
                this.wsTimer = setInterval(() => {
                    if (this.webSocket.readyState === 1) {
                        clearInterval(this.wsTimer)
                    } else {
                        console.log('ws建立连接失败')
                        this.wsInit()
                    }
                }, 3000)
            },

Realice una conexión websocket y pase los parámetros de badao, y configure el método de procesamiento de devolución de llamada correspondiente.

Finalmente, se configura un temporizador de 3 segundos para verificar periódicamente el estado de la conexión del websocket.

Este componente también agrega un botón y establece su evento de clic para enviar mensajes al servidor

            sendDataToServer() {
                if (this.webSocket.readyState === 1) {
                    this.webSocket.send('来自前端的数据')
                } else {
                    throw Error('服务未连接')
                }
            },

Código completo del componente de WebSocket

<template>
  <el-button @click="sendDataToServer" >给后台发送消息</el-button>
</template>

<script>
    export default {
        name: "WebSocket",
        data() {
            return {
                // ws是否启动
                wsIsRun: false,
                // 定义ws对象
                webSocket: null,
                // ws请求链接(类似于ws后台地址)
                ws: '',
                // ws定时器
                wsTimer: null,
            }
        },
        async mounted() {
            this.wsIsRun = true
            this.wsInit()
        },
        methods: {
            sendDataToServer() {
                if (this.webSocket.readyState === 1) {
                    this.webSocket.send('来自前端的数据')
                } else {
                    throw Error('服务未连接')
                }
            },
            /**
             * 初始化ws
             */
            wsInit() {
                const wsuri = 'ws://10.229.36.158:7777/websocket/badao'
                this.ws = wsuri
                if (!this.wsIsRun) return
                // 销毁ws
                this.wsDestroy()
                // 初始化ws
                this.webSocket = new WebSocket(this.ws)
                // ws连接建立时触发
                this.webSocket.addEventListener('open', this.wsOpenHanler)
                // ws服务端给客户端推送消息
                this.webSocket.addEventListener('message', this.wsMessageHanler)
                // ws通信发生错误时触发
                this.webSocket.addEventListener('error', this.wsErrorHanler)
                // ws关闭时触发
                this.webSocket.addEventListener('close', this.wsCloseHanler)

                // 检查ws连接状态,readyState值为0表示尚未连接,1表示建立连接,2正在关闭连接,3已经关闭或无法打开
                clearInterval(this.wsTimer)
                this.wsTimer = setInterval(() => {
                    if (this.webSocket.readyState === 1) {
                        clearInterval(this.wsTimer)
                    } else {
                        console.log('ws建立连接失败')
                        this.wsInit()
                    }
                }, 3000)
            },
            wsOpenHanler(event) {
                console.log('ws建立连接成功')
            },
            wsMessageHanler(e) {
                console.log('wsMessageHanler')
                console.log(e)
                //const redata = JSON.parse(e.data)
                //console.log(redata)
            },
            /**
             * ws通信发生错误
             */
            wsErrorHanler(event) {
                console.log(event, '通信发生错误')
                this.wsInit()
            },
            /**
             * ws关闭
             */
            wsCloseHanler(event) {
                console.log(event, 'ws关闭')
                this.wsInit()
            },
            /**
             * 销毁ws
             */
            wsDestroy() {
                if (this.webSocket !== null) {
                    this.webSocket.removeEventListener('open', this.wsOpenHanler)
                    this.webSocket.removeEventListener('message', this.wsMessageHanler)
                    this.webSocket.removeEventListener('error', this.wsErrorHanler)
                    this.webSocket.removeEventListener('close', this.wsCloseHanler)
                    this.webSocket.close()
                    this.webSocket = null
                    clearInterval(this.wsTimer)
                }
            },
        }
    }
</script>

<style scoped>

</style>

Luego, haga referencia al componente en la página de inicio

<template>
  <div class="app-container home">
    <el-row :gutter="20">
      websocket推送
      <WebSocket></WebSocket>
    </el-row>
    <el-divider />
  </div>
</template>
import WebSocket from '@/components/WebSocket/WebSocket'
export default {
  name: "index",
   components: {
     WebSocket
  },

efecto

Primero ejecute el servicio SpringBoot en segundo plano, luego ejecute el proyecto de front-end e inicie sesión en la página de inicio, abra la salida de la consola

 

Puede ver que la conexión websocket se estableció correctamente y que hay una salida en segundo plano.

 

Luego, el fondo empuja el mensaje al front-end y llama a la interfaz para enviar el mensaje en segundo plano.

Entonces puede recibir el impulso desde el fondo en la parte frontal

 

Luego haga clic en el botón en la parte frontal para enviar un mensaje al fondo

El mensaje enviado se recibe en el método de devolución de llamada en segundo plano.

 

Supongo que te gusta

Origin blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/114392573
Recomendado
Clasificación