单机运行

用户a通过服务器进入房间room,用户b也通过房间进入room,用户之间是通过session来通话的,所以session直接存储在集合中就可以了。

因为session存储在一台服务器的集合中,所以每次发送消息的时候,直接发给房间内的所有人的session就可以了。

多机运行

假如有两台服务器,用户a通过服务器A进入了房间room,用户b通过服务器B也进入了房间room,由于session是用集合存储在各自的服务器中的,所以这种情况下,用户a发的消息只能通过服务器A,但是服务器A中是没有用户b的session的,所以当用户a发送消息的时候,用户b就收不到用户a发送的消息了。

实现共享session

利用redis的订阅发布实现

当用户a通过服务器A进入房间room,然后将用户存入redis的房间room中,用户b通过服务器B进入房间room,也将用户b存入redis的房间room中,并且都订阅房间room.将各自的session还是存储在各自的服务器中,

然后发消息的时候,发布消息到redis的room中,然后监听redis中的room房间,当有消息发送的时候,判断redis的房间中是否存在本地用户,如果存在则反推给当前服务器对应的用户。

在本机用两个tomcat配置不同的端口号模拟启动,访问测试成功,效果如下:

RedisConfig  这里没有配置JedisConnectionFactory,因为在application.yml中配置集群(windows集群配置参考这篇博客https://www.cnblogs.com/tommy-huang/p/6240083.html)时会自动注入连接工厂。

配置监听容器RedisMessageListenerContainer,用于监听redis发布的消息。

package com.test;/*

*/

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import java.util.HashSet;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<?,?> getRedisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory){
      RedisTemplate redisTemplate=  new RedisTemplate<>();
      redisTemplate.setConnectionFactory(redisConnectionFactory);
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    public RedisMessageListenerContainer initRedisContainer(@Autowired RedisConnectionFactory redisConnectionFactory){

        RedisMessageListenerContainer container=new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        return container;
    }
}

SocketRest

实现MessageListener接口,重写onMessage方法,接收redis消息并发送。
方法container.addMessageListener(socketRest,topic); socketRest为监听者,topic为监听的房间号。

每当第一个用户进入房间后,就为该房间增加一个监听者,用来监听该房间的消息。

package com.test;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.stereotype.Component;

@ServerEndpoint("/websocket/{roomId}/{username}")
@Component
public class SocketRest implements MessageListener{

    //接收redis中的消息
    @Override
    public void onMessage(Message msgs, byte[] pattern) {
        try {
            //消息内容
            byte[] body=msgs.getBody();
            System.out.println("body: "+body);
            //订阅房间
            String topic=new String(pattern);
            //获取存在的房间中的用户
        String result= new String(body,"utf-8");
            System.out.println("msg: "+result);
            JSONObject js= JSON.parseObject(result);
            String username=js.getString("username");
            String msg=js.getString("msg");
            Set<Object> SessionKeys= UserSessions.keySet();
            HashMap message=new HashMap();
            message.put("msg",msg);
            message.put("username",username);
           broadcast(topic,message,username);
        }catch (Exception e){
            log.info("onMessage exception");
        }
    }

}

application.yml

server:
  port: 8088

logging:
  config: classpath:log4j2.yml
spring:
  profiles:
       active: test
---
spring:
  profiles:  test

  datasource:
        url: jdbc:mysql://localhost:3306/test
        driver-class-name: com.mysql.jdbc.Driver
        data-password: 1234
        data-username: root
  redis:
    cluster:
      nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
    database: 0
    host: 127.0.0.1:7001
    port: 6379

  session:
  store-type: redis

WebsocketConfig  打包成war用外置tomcat部署时需要注释,不注释会和tomcat自带的websocket注入bean时冲突。

@Configuration
public class WebsocketConfig {

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

}

启动时写入房间

@Component
public class StartupRunner implements CommandLineRunner {

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public void run(String... strings) throws Exception{
        if(!redisUtil.sGet("room111").isEmpty()){
            System.out.println("清空数据");
            redisUtil.del("room111");
        }
        redisUtil.sSet("rooms","room111","room222");
       Set<Object> set=redisUtil.sGet("rooms");
       for (Object o:set){
           System.out.println("------>"+o.toString());
       }

    }
}

ApplicationContextRegister   解决websocket中不能注入bean.

package com.test;/*

*/

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextRegister implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

RedisUtil  发布方法

增加订阅发布方法, channel订阅房间号,message发送给该房间的消息。

public  void convertAndSend(String channel ,Object message){
    redisTemplate.convertAndSend(channel,message);
}

启动类Application

package com.test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@EnableAutoConfiguration
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
		return builder.sources(Application.class);
	}

	public static void main(String[] args) {
		 SpringApplication.run(Application.class, args);
	}

}

pom.xml

用tomcat部署时,如果不是默认的8080,则需要添加端口号,在server.xml中配置,增加端口以及设置编码。

<Connector port="8088" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="UTF-8" />

设置项目启动路径,docBase即项目名称

<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
     <Context path="" docBase="whatsup" debug="0" reloadable="true" />
        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

<!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

</Host>

log4j.yml

Appenders:
    Console:  #输出到控制台
      name: CONSOLE #Appender命名
      target: SYSTEM_OUT
      PatternLayout:
        pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
    RollingFile: # 输出到文件,超过256MB归档
      - name: ROLLING_FILE
        ignoreExceptions: false
        fileName: springboot.log
        filePattern: "/springboot/logs/$${date:yyyy-MM}/springboot -%d{yyyy-MM-dd}-%i.log.gz"
        PatternLayout:
          pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS}:%4p %t (%F:%L) - %m%n"
        Policies:
          SizeBasedTriggeringPolicy:
            size: "256 MB"
        DefaultRolloverStrategy:
          max: 1000
Loggers:
    Root:
      level: info
      AppenderRef:
        - ref: CONSOLE
    Logger: #单独设置某些包的输出级别
      - name: app.com.kenho.mapper #复数加上-
        additivity: false #去除重复的log
        level: trace
        AppenderRef:
          - ref: CONSOLE #复数加上-
          - ref: ROLLING_FILE #复数加上-

websocket session共享的更多相关文章

  1. [Spring] spring-session + JedisPool 实现 session 共享

    1.至少导入四个jar包: jedis spring-session spring-data-redis commons-pool2 2.bean配置 <?xml version="1 ...

  2. Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享

    小伙伴们好久不见!最近略忙,博客写的有点少,嗯,要加把劲.OK,今天给大家带来一个JavaWeb中常用的架构搭建,即Nginx+Tomcat搭建服务集群,然后通过Spring Session+Redi ...

  3. Nginx反向代理,负载均衡,redis session共享,keepalived高可用

    相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tomcat服务器两台,由nginx进行反向代理和负载均衡,此 ...

  4. 分布式中使用Redis实现Session共享(二)

    上一篇介绍了一些redis的安装及使用步骤,本篇开始将介绍redis的实际应用场景,先从最常见的session开始,刚好也重新学习一遍session的实现原理.在阅读之前假设你已经会使用nginx+i ...

  5. windows 环境下nginx + tomcat群 + redis 实现session共享

    nginx作为负载均衡根据定义将不同的用户请求分发到不同的服务器,同时也解决了因单点部署服务器故障导致的整个应用不能访问的问题 在加入nginx之后,如果多个服务器中的一个或多个(不是全部)发生故障, ...

  6. shiro实现session共享

    session共享:在多应用系统中,如果使用了负载均衡,用户的请求会被分发到不同的应用中,A应用中的session数据在B应用中是获取不到的,就会带来共享的问题. 假设:用户第一次访问,连接的A服务器 ...

  7. Redis安装及实现session共享

    一.Redis介绍 1.redis是key-value的存储系统,属于非关系型数据库 2.特点:支持数据持久化,可以让数据在内存中保存到磁盘里(memcached:数据存在内存里,如果服务重启,数据会 ...

  8. Tomcat7基于Redis的Session共享实战二

    目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无 ...

  9. Nginx+Tomcat+Redis实现负载均衡、资源分离、session共享

    Nginx+Tomcat+Redis实现负载均衡.资源分离.session共享 CentOS安装Nginx http://centoscn.com/CentosServer/www/2013/0910 ...

随机推荐

  1. Session &cookie introduction,usage

    Cookie 1)什么是Cookie?      服务器为了识别用户身份而临时存放在浏览器端的少量数据.     2)工作原理         浏览器访问服务器时,服务器将一些数据以set-cooki ...

  2. HDFS配置参数及优化之实战经验(Linux hdfs)

    HDFS优化之实战经验 Linux系统优化 一.禁止文件系统记录时间 Linux文件系统会记录文件创建.修改和访问操作的时间信息,这在读写操作频繁的应用中将带来不小的性能损失.在挂载文件系统时设置no ...

  3. 关于C++中ios::sync_with_stdio(false)

    粘贴自:https://blog.csdn.net/weixin_44015865/article/details/84974373 在C++中的输入和输出有两种方式,一种是scanf和printf, ...

  4. numpy小结

    <python数据科学>笔记  在线版地址:https://github.com/jakevdp/PythonDataScienceHandbook 1.常用np简写 import num ...

  5. 使用卷积神经网络CNN完成验证码识别

    gen_sample_by_captcha.py 生成验证码图片 # -*- coding: UTF-8 -*- """ 使用captcha lib生成验证码(前提:pi ...

  6. 支持向量机(Support Vector Machine):对偶

    前言 学SVM看到对偶问题的时候很难受,因为看不懂,数学知识真的太重要了.后来在B站看到某up主的精彩推导,故总结如下. SVM基本型 由之前最大化间隔的计算可得SVM的基本型为: $\underse ...

  7. TYVJ P1039 【忠诚2】

    题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人的挑拨,财主还是对管家产生了 ...

  8. http压测工具wrk使用

    安装 wrk支持大多数类UNIX系统,不支持windows.需要操作系统支持LuaJIT和OpenSSL,不过不用担心,大多数类Unix系统都支持.安装wrk非常简单,只要从github上下载wrk源 ...

  9. SSH实现ajax

    (1)首先要引入需要pom文件 <!-- https://mvnrepository.com/artifact/org.apache.struts/struts2-json-plugin --& ...

  10. Vivado SDK ,调用math.h函数的时候出现 undefined reference to `xxx' ,解决方案

    在Vivado SDK进行软件设计的时候,如调用math.h函数的时候出现 undefined reference to `sqrt' ,原因有以下情况: 1.没有添加需调用的头文件 解决方案:添加对 ...