SpringBoot+WebSocket+Compartir sesión

prefacio

El protocolo WebSocket es un nuevo protocolo de red basado en TCP. Implementa comunicación full-duplex (full-duplex) entre el navegador y el servidor, lo que permite que el servidor envíe información de forma activa al cliente.

1. ¿Por qué necesita WebSocket?

HTTP se basa en solicitud-respuesta, es decir, la comunicación solo puede iniciarla el cliente y el servidor responde, sin estado ni conexión.

Sin estado: solo se procesa una solicitud por conexión y la conexión se desconecta una vez que finaliza la solicitud.

Sin conexión: no hay memoria para el procesamiento de transacciones y el servidor no sabe en qué estado se encuentra el cliente.

Para realizar la mensajería instantánea a través de HTTP, solo el sondeo de la página envía una solicitud al servidor y el servidor devuelve el resultado de la consulta. El sondeo es ineficiente y desperdicia recursos porque debe seguir conectándose o la conexión HTTP siempre está abierta.

La característica más importante de WebSocket es que el servidor puede enviar información de forma activa al cliente, y el cliente también puede enviar información de forma activa al servidor, que es un verdadero diálogo igualitario de dos vías.

Características de WebSocket

  • (1) Basado en el protocolo TCP, la implementación del lado del servidor es relativamente fácil.

  • (2) Tiene buena compatibilidad con el protocolo HTTP. Los puertos predeterminados también son 80 y 443, y la fase de negociación utiliza el protocolo HTTP, por lo que no es fácil protegerse durante la negociación y puede pasar a través de varios
    servidores proxy HTTP.

  • (3) El formato de datos es relativamente ligero, la sobrecarga de rendimiento es pequeña y la comunicación es eficiente.

  • (4) Se pueden enviar texto o datos binarios.

  • (5) No hay restricción del mismo origen y el cliente puede comunicarse con cualquier servidor.

  • (6) El identificador de protocolo es ws (o wss si está encriptado) y la URL del servidor es la URL.

2. SpringBoot integra WebSocket

1. dependencia experta

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

2.WebSocketConfig

Habilitar la compatibilidad con WebSocket

@Configuration
public class WebSocketConfig {
    
    
    /**
     * 自动注册使用@ServerEndpoint注解声明的websocket endpoint
     * 2022/2/14
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
    
    
        return new ServerEndpointExporter();
    }
}

3.ServidorWebSocket

Debido a que WebSocket es similar a la forma del servidor del cliente (usando el protocolo ws), entonces el WebSocketServer aquí es en realidad equivalente a un controlador del protocolo ws

Simplemente habilite @ServerEndpoint(“/websocket/{userId}”) y @Component directamente, y luego implemente @OnOpen para abrir la conexión, @onClose para cerrar la conexión, @onMessage para recibir mensajes y otros métodos.

La versión del clúster (múltiples nodos ws) también debe procesarse con la ayuda de mysql o redis, etc., y se puede modificar el método sendMessage correspondiente.

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
 
 
@ServerEndpoint("/imserver/{userId}")
@Component
public class WebSocketServer {
    
    
 
    static Log log=LogFactory.get(WebSocketServer.class);
    /**静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。*/
    private static int onlineCount = 0;
    /**concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。*/
    private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**与某个客户端的连接会话,需要通过它来给客户端发送数据*/
    private Session session;
    /**接收userId*/
    private String userId="";
 
    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session,@PathParam("userId") String userId) {
    
    
        this.session = session;
        this.userId=userId;
        if(webSocketMap.containsKey(userId)){
    
    
            webSocketMap.remove(userId);
            webSocketMap.put(userId,this);
            //加入set中
        }else{
    
    
            webSocketMap.put(userId,this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }
 
        log.info("用户连接:"+userId+",当前在线人数为:" + getOnlineCount());
 
        try {
    
    
            sendMessage("连接成功");
        } catch (IOException e) {
    
    
            log.error("用户:"+userId+",网络异常!!!!!!");
        }
    }
 
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
    
    
        if(webSocketMap.containsKey(userId)){
    
    
            webSocketMap.remove(userId);
            //从set中删除
            subOnlineCount();
        }
        log.info("用户退出:"+userId+",当前在线人数为:" + getOnlineCount());
    }
 
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
    
    
        log.info("用户消息:"+userId+",报文:"+message);
        //可以群发消息
        //消息保存到数据库、redis
        if(StringUtils.isNotBlank(message)){
    
    
            try {
    
    
                //解析发送的报文
                JSONObject jsonObject = JSON.parseObject(message);
                //追加发送人(防止串改)
                jsonObject.put("fromUserId",this.userId);
                String toUserId=jsonObject.getString("toUserId");
                //传送给对应toUserId用户的websocket
                if(StringUtils.isNotBlank(toUserId)&&webSocketMap.containsKey(toUserId)){
    
    
                    webSocketMap.get(toUserId).sendMessage(jsonObject.toJSONString());
                }else{
    
    
                    log.error("请求的userId:"+toUserId+"不在该服务器上");
                    //否则不在这个服务器上,发送到mysql或者redis
                }
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }
    }
 
    /**
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
    
    
        log.error("用户错误:"+this.userId+",原因:"+error.getMessage());
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
    
    
        this.session.getBasicRemote().sendText(message);
    }
 
 
    /**
     * 发送自定义消息
     * */
    public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
    
    
        log.info("发送消息到:"+userId+",报文:"+message);
        if(StringUtils.isNotBlank(userId)&&webSocketMap.containsKey(userId)){
    
    
            webSocketMap.get(userId).sendMessage(message);
        }else{
    
    
            log.error("用户"+userId+",不在线!");
        }
    }
 
    public static synchronized int getOnlineCount() {
    
    
        return onlineCount;
    }
 
    public static synchronized void addOnlineCount() {
    
    
        WebSocketServer.onlineCount++;
    }
 
    public static synchronized void subOnlineCount() {
    
    
        WebSocketServer.onlineCount--;
    }
}

4. Versión de grupo

Al usar websocket, se enfrentará al problema de compartir sesiones

Aquí implementamos el uso compartido de sesiones a través del modo de suscripción y escucha de Redis. Cuando se une una conexión, la conexión se envía a la cola y luego se envía al servicio que se suscribe a la cola, y el servicio que almacena la conexión realiza el procesamiento lógico.

La lógica empresarial de compartir sesión se muestra en la siguiente figura
inserte la descripción de la imagen aquí

4.1 Realización del modo de publicación y suscripción de Redis

prefacio

Redis puede implementar colas de mensajes a través del modo de publicación y suscripción y el mecanismo de sondeo.

Dado que no hay garantía de persistencia de mensajes y ACK, la función de publicación y suscripción de Redis no es confiable. Esto también conduce a sus escenarios de aplicación limitados, y se recomienda su uso en escenarios con requisitos bajos de tiempo real y confiabilidad.

1. Redis publicar y suscribirse

​​​​​​​1. Principio de implementación del modelo de publicación y suscripción de Redis

Se mantiene un diccionario pubsub_channels en el servidor, y todos los canales y las relaciones de suscripción se almacenan aquí. Las claves del diccionario son los nombres de los canales, y los valores son una lista de clientes suscritos al canal.

1> Cuando un nuevo cliente se suscribe a un canal, sucederá una de dos cosas:

(1) Si el canal ya existe, el nuevo cliente se agregará al final de la lista enlazada del canal correspondiente a pubsub_channels

(2) Si el canal no existe originalmente, se crea una clave para el canal y el cliente se convierte en el primer elemento de la lista enlazada

2> Cuando un cliente se da de baja de un canal:

La lista enlazada correspondiente a la clave de pubsub_channels eliminará el cliente

3> enviar información

El servidor recorrerá la lista enlazada de claves correspondientes en pubsub_channels y enviará información a cada cliente

El servidor también mantiene una lista vinculada pubsub_patterns, el atributo de patrón de la lista vinculada registra el patrón suscrito y el atributo de cliente registra el cliente del patrón suscrito

  1. Cuando un nuevo cliente se suscribe a un patrón, se realizarán los siguientes pasos:

(1) Cree un nodo de lista vinculada, el atributo de patrón registra el modo de suscripción y el cliente registra el cliente del modo de suscripción

(2) Agregue este nodo de lista enlazada a la lista enlazada pubsub_patterns

  1. Cuando un cliente se da de baja de un esquema:

El servidor atraviesa pubsob_patterns para encontrar el patrón correspondiente y también el nodo correspondiente al cliente cliente, y elimina el nodo modificado

  1. enviar mensaje

El servidor atraviesa pubsub_channels, encuentra patrones que coinciden con los canales y envía mensajes de mahjong a los clientes que se han suscrito a estos patrones.

2. Introducir dependencias
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
3. configuración redis
public class RedisConfig {
    
    
    //Key的过期时间
    private Duration timeToLive = Duration.ofDays(1);
 
    /**
     * redis模板,存储关键字是字符串,值jackson2JsonRedisSerializer是序列化后的值
     *
     * @param
     * @return org.springframework.data.redis.core.RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    
    
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        RedisSerializer redisSerializer = new StringRedisSerializer();
        //key
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        //value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
 
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
 
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
    
    
        return RedisCacheConfiguration.
                defaultCacheConfig().
                entryTtl(this.timeToLive).			//Key过期时间 1天
                serializeKeysWith(RedisSerializationContext.SerializationPair.
                fromSerializer(new StringRedisSerializer())).
                serializeValuesWith(RedisSerializationContext.SerializationPair.
                        fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

4. monitoreo redis
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
 
import java.util.concurrent.CountDownLatch;
 
 
@Configuration
public class RedisMessageListener {
    
    
    /**
     * 创建连接工厂
     *
     * @param connectionFactory
     * @param listenerAdapter
     * @return
     */
    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                                   MessageListenerAdapter listenerAdapter,
                                                   MessageListenerAdapter listenerAdapterWang,
                                                   MessageListenerAdapter listenerAdapterTest2) {
    
    
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //(不同的监听器可以收到同一个频道的信息)接受消息的频道
        container.addMessageListener(listenerAdapter, new PatternTopic("phone"));
 
        container.addMessageListener(listenerAdapterWang, new PatternTopic("phone"));
 
        container.addMessageListener(listenerAdapterTest2, new PatternTopic("phoneTest2"));
        return container;
    }
 
 
    /**
     * 绑定消息监听者和接收监听的方法
     *
     * @param receiver
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapter(ReceiverRedisMessage receiver) {
    
    
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }
 
    @Bean
    public MessageListenerAdapter listenerAdapterWang(ReceiverRedisMessage receiver) {
    
    
        return new MessageListenerAdapter(receiver, "receiveMessageWang");
    }
   /**
     * 绑定消息监听者和接收监听的方法
     *
     * @param receiver
     * @return
     */
    @Bean
    public MessageListenerAdapter listenerAdapterTest2(ReceiverRedisMessage receiver) {
    
    
        return new MessageListenerAdapter(receiver, "receiveMessage2");
    }
 
    /**
     * 注册订阅者
     *
     * @param latch
     * @return
     */
    @Bean
    ReceiverRedisMessage receiver(CountDownLatch latch) {
    
    
        return new ReceiverRedisMessage(latch);
    }
 
 
    /**
     * 计数器,用来控制线程
     *
     * @return
     */
    @Bean
    public CountDownLatch latch() {
    
    
        return new CountDownLatch(1);//指定了计数的次数 1
    }
}
5. Clase de recepción de mensajes Redis
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
 
import java.util.concurrent.CountDownLatch;
 
 
public class ReceiverRedisMessage {
    
    
    private CountDownLatch latch;
 
    @Autowired
    public ReceiverRedisMessage(CountDownLatch latch) {
    
    
        this.latch = latch;
    }
 
 
    /**
     * 队列消息接收方法
     *
     * @param jsonMsg
     */
    public void receiveMessage(String jsonMsg) {
    
    
        log.info("[开始消费REDIS消息队列phone数据...]");
        try {
    
    
            System.out.println(jsonMsg);
            log.info("[消费REDIS消息队列phone数据成功.]");
        } catch (Exception e) {
    
    
            log.error("[消费REDIS消息队列phone数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }
 
 
    public void receiveMessageWang(String jsonMsg) {
    
    
        log.info("[开始消费REDIS消息队列phone数据...]");
        try {
    
    
            System.out.println(jsonMsg);
            log.info("[消费REDIS消息队列phone数据成功.]");
        } catch (Exception e) {
    
    
            log.error("[消费REDIS消息队列phone数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }
 
    /**
     * 队列消息接收方法
     *
     * @param jsonMsg
     */
    public void receiveMessage2(String jsonMsg) {
    
    
        log.info("[开始消费REDIS消息队列phoneTest2数据...]");
        try {
    
    
            System.out.println(jsonMsg);
            /**
             *  此处执行自己代码逻辑 操作数据库等
             */
 
            log.info("[消费REDIS消息队列phoneTest2数据成功.]");
        } catch (Exception e) {
    
    
            log.error("[消费REDIS消息队列phoneTest2数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }
 
}
6. Prueba de código
  public void redisTest() {
    
    
        //这个是测试同一个频道,不同的订阅者收到相同的信息,“phone”也就是topic也可以理解为频道
        redisTemplate.convertAndSend("phone", "223333");
        //这个phoneTest2是另外的一个频道
      //  redisTemplate.convertAndSend("phoneTest2", "34555665");
    } 

2. Mecanismo de sondeo Redis

1. Principio

Las claves de tipo de lista de Redis se pueden usar para implementar colas y admitir lecturas de bloqueo, lo que puede implementar una cola de prioridad de alto rendimiento.En Redis, el tipo de lista es una lista vinculada de cadenas ordenadas en orden de inserción. Como una lista enlazada normal en una estructura de datos, podemos agregar nuevos elementos a su cabeza (izquierda) y cola (derecha). Al insertar, si la clave no existe, Redis creará una nueva lista vinculada para la clave. Por el contrario, si se eliminan todos los elementos de la lista vinculada, la clave también se eliminará de la base de datos. El número máximo de elementos que puede contener una Lista es 4294967295. Desde la perspectiva de la eficiencia de la inserción y eliminación de elementos, si insertamos o eliminamos elementos en ambos extremos de la lista enlazada, esta será una operación muy eficiente Incluso si se han almacenado millones de registros en la lista enlazada, esta operación puede también debe hacerse en tiempo constante.

2. Productor
  public R saveUserTicket(String phoneNum) {
    
    
        redisTemplate.opsForList().leftPush("ticket:Data", phoneNum);
        return R.ok();
    }
3. Consumidores
 @Scheduled(fixedRate = 1)
    public synchronized void consumer() {
    
    
            String message = redisTemplate.opsForList().rightPop("ticket:Data", 5, TimeUnit.SECONDS);
            if (!StringUtils.isEmpty(message)){
    
    
           //数据库操作
            }
    }
4. Optimización

Como en el código anterior, si la cola está vacía en este momento, los consumidores seguirán extrayendo datos con frecuencia, lo que hará que la CPU se quede inactiva, lo que no solo ocupa los recursos de la CPU, sino que también ejerce presión sobre Redis. Así podemos dormir un rato cuando la cola está vacía, y luego tirar.

La implementación es la siguiente

     @Scheduled(fixedRate = 1)
    public synchronized void consumer() throws InterruptedException {
    
    
        long a = redisTemplate.opsForList().size("ticket:Data");
        if (a == 0) {
    
    
            TimeUnit.SECONDS.sleep(1);//等待时间
        }
            String message = redisTemplate.opsForList().rightPop("ticket:Data", 5, TimeUnit.SECONDS);
            if (!StringUtils.isEmpty(message)){
    
    
           //数据库操作
            }

Principio de implementación del modo de publicación y suscripción de Redis

prefacio

Los sistemas de publicación-suscripción se utilizan a menudo en nuestro trabajo diario. En la mayoría de los casos, utilizamos colas de mensajes. Las colas de mensajes de uso común incluyen Kafka, RocketMQ y RabbitMQ. Cada cola de mensajes tiene sus propias características. Muchas veces es posible que no necesitemos implementar el cola de mensajes correspondiente de forma independiente, pero simplemente utilícelo, y la cantidad de datos no será demasiado grande.En este caso, podemos usar el modelo Pub/Sub de Redis.

1. Redis publicar y suscribirse

La función de publicación y suscripción de Redis se compone principalmente de los comandos PUBLISH, SUBSCRIBE y PSUBSCRIBE. Uno o más clientes se suscriben a uno o más canales. Cuando otros clientes envían mensajes al canal, los clientes suscritos al canal recibirán los mensajes correspondientes. mensaje.

2. Comandos básicos del modo publicación-suscripción

El comando psubscribe se suscribe a uno o más canales que coinciden con el patrón dado. Cada patrón usa * como carácter coincidente, como si* coincidiera con todos los canales que comienzan con él (it.news, it.blog, it.tweets)

La sintaxis básica del comando Psubscribe es la siguiente:

PSUBSCRIBE patrón [patrón...]

El comando Redis Pubsub se usa para ver el estado del sistema de suscripción y publicación, que consta de varios subcomandos en diferentes formatos.

PUBSUB [argumento [argumento...]]

El comando Publicar se utiliza para enviar información al canal especificado.

La sintaxis básica del comando Publicar es la siguiente:

PUBLICAR mensaje de canal

El comando Punsubscribe se usa para cancelar la suscripción de todos los canales del patrón dado.

La sintaxis básica del comando Punsubscribe es la siguiente:

PUNSUBSCRIBE [patrón [patrón...]]

El comando Subscribe se utiliza para suscribirse a la información de un canal o canales determinados.

La sintaxis básica del comando Subscribe es la siguiente:

SUSCRÍBETE canal [canal...]

El comando Cancelar suscripción se utiliza para cancelar la suscripción de información de un canal o canales determinados.

La sintaxis básica del comando Cancelar suscripción es la siguiente:

CANCELAR SUSCRIPCIÓN canal [canal...]

3. Principio de implementación del modo de publicación-suscripción

Redis implementa funciones de publicación y suscripción a través de comandos como PUBLISH, SUBSCRIBE y PSUBSCRIBE.

  • Después de suscribirse a un canal a través del comando SUBSCRIBE, se mantiene un diccionario en el servidor redis.Las claves del diccionario son canales, y el valor del diccionario es una lista enlazada, que almacena todos los clientes que se suscriben a este canal. La clave del comando SUBSCRIBE es agregar el cliente a la lista de suscripción del canal especificado.
  • Envíe información a los suscriptores a través del comando PUBLISH, redis-server usará el canal dado como clave, buscará la lista vinculada de todos los clientes que se suscriben al canal en el diccionario de canales que mantiene y publicará la información para todos atravesando el lista enlazada de suscriptores
Estructura de almacenamiento subyacente de Pub/Sub

Suscríbete al canal

En la estructura subyacente de Redis, la relación de suscripción entre el cliente y el canal se guarda a través de una estructura de diccionario y lista enlazada, de la siguiente forma:

inserte la descripción de la imagen aquí

En la estructura subyacente de Redis, se define un diccionario pubsub_channels en la estructura del servidor Redis

struct redisServer {
    
    
 
//用于保存所有频道的订阅关系
 
dict *pubsub_channels
 
}

En este diccionario, la clave representa el nombre del canal y el valor es una lista enlazada, que almacena todos los clientes que se suscriben a este canal.

Entonces, cuando un cliente ejecuta la acción de suscribirse a un canal, el servidor asociará al cliente con el canal suscrito en el diccionario pubsub_channels.

Hay dos situaciones en este momento:

  • Este canal se suscribe por primera vez: la primera vez que se suscribe significa que la información del canal no existe en el diccionario, entonces el programa primero debe crear una clave correspondiente y asignar una lista enlazada vacía, y luego agregar el cliente correspondiente a la lista enlazada. En este punto, la lista enlazada tiene solo un elemento.

  • El canal ya ha sido suscrito por otros clientes: en este momento, solo agregue la información del cliente correspondiente al final de la lista vinculada.

Por ejemplo, si hay un nuevo cliente Cliente 08 para suscribirse al canal de ejecución, la imagen de arriba se convertirá en
inserte la descripción de la imagen aquí

Si el Cliente 08 quiere suscribirse a un nuevo canal new_sport, entonces se convertirá en

inserte la descripción de la imagen aquí

darse de baja

Lo anterior es la suscripción de un solo Canal, por el contrario, si un cliente desea cancelar la suscripción del Canal relacionado, no es más que encontrar la lista enlazada del Canal correspondiente y eliminar el cliente correspondiente. ya el ultimo, sera El Canal correspondiente tambien se elimina.

estructura de suscripción de esquema

La suscripción del canal de patrones es similar a la suscripción de un solo canal, pero el servidor guarda la relación de suscripción de todos los patrones en el atributo pubsub_patterns del estado del servidor.


struct redisServer{
    
    
 
//保存所有模式订阅关系
 
list *pubsub_patterns;
 
}

A diferencia de la suscripción a un solo canal, el atributo pubsub_patterns es una lista enlazada, no un diccionario. La estructura del nodo es la siguiente:


struct pubsubPattern{
    
    
 
//订阅模式的客户端
 
redisClient *client;
 
//被订阅的模式
 
robj *pattern;
 
} pubsubPattern;

De hecho, el atributo del cliente se usa para almacenar la información del cliente correspondiente, y el patrón se usa para almacenar el patrón coincidente correspondiente al cliente.

Entonces, la estructura correspondiente a la coincidencia de patrón Client-06 anterior se almacena de la siguiente manera
inserte la descripción de la imagen aquí

Hay un nodo en la lista enlazada pubsub_patterns, el cliente correspondiente es Client-06 y se ejecuta el patrón coincidente correspondiente*.

modelo de suscripción

Cuando un cliente se suscribe al Canal del patrón correspondiente a través del comando psubscribe, el servidor creará un nodo, establecerá el atributo de Cliente al cliente correspondiente y el atributo de patrón a la regla de patrón correspondiente, y luego lo agregará al final de la lista enlazada.

Crear un nuevo nodo;

Asigne valores a las propiedades del nodo;

Agregue el nodo al final de la lista vinculada;

modo de cancelación de suscripción

El comando para darse de baja de un modo es punsubscribe, y el cliente usa este comando para darse de baja de uno o más canales de modo. Después de recibir el comando, el servidor recorrerá la lista enlazada pubsub_patterns y eliminará los nodos coincidentes con atributos de cliente y patrón. Aquí, es necesario juzgar que el atributo del cliente y el atributo del patrón son válidos antes de eliminarlos.

Recorra todos los nodos y elimine nodos cuando coincidan el mismo atributo de cliente y el mismo atributo de patrón.

Hacer un anuncio

Cuando un cliente ejecuta el comando de mensaje de publicación de nombre de canal, el servidor encontrará todos los canales que coincidan con el nombre de canal de las dos estructuras de pubsub_channels y pubsub_patterns, y enviará el mensaje. En pubsub_channels, simplemente busque la clave del canal correspondiente y envíe un mensaje al cliente en la lista de valores correspondiente.

4.2 Envío de mensajes en cola

    @OnMessage
    public void onMessage(Session session, String message) throws IOException, ParseException {
    
    
        //业务逻辑处理
        //向所有用户广播
        redisUtil.convertSend("topic", message);
    } 

4.3 Recepción de mensajes en cola

/**
     * 队列消息接收方法
     * 2022/2/21
     */
    public void receiveMessage(String message) {
    
    
        log.info("[开始消费REDIS消息队列topic数据...]");
        try {
    
    
            //业务逻辑处理
            webSocketServer.sendMessageForProject(message);
            log.info("[消费REDIS消息队列topic数据成功.]");
        } catch (Exception e) {
    
    
            log.error("[消费REDIS消息队列topic数据失败,失败信息:{}]", e.getMessage());
        }
        latch.countDown();
    }
参考来源:「人苼若只茹初見」
原文链接: https://blog.csdn.net/printf88/article/details/123685995

Supongo que te gusta

Origin blog.csdn.net/qq_40421671/article/details/131449130
Recomendado
Clasificación