单机运行

用户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. 基于OpenCV做“三维重建”(0)-- OpenCV3.2+VIZ6.3.0在vs2012下的编译和使用

    一.问题提出         ViZ对于显示3维的效果图来说,非常有帮助:我在使用OpenCV进行双目测距的过程中,有一些参数希望能够通过可视化的方法显示出来,所以参考了这方面相关的资料.做了一些实验 ...

  2. 使用kubesql进行kubernetes资源查询

    kubesql kubesql(https://github.com/xuxinkun/kubesql)是我最近开发的一个使用sql查询kubernetes资源的工具.诸如node,pod等kuber ...

  3. Oracle错误——ORA-39002:操作无效、ORA-39070:无法打开日志文件、ORA-06512:在“SYS.UTL_FILE”,line

    错误 在使用数据泵impdp导入文件时,出现错误,无法导入数据 Next 问题原因 初步猜测,应该是Oracle用户权限出现问题,是对Directory目录无操作权限所致,经过一番修改和测试,发现使用 ...

  4. 详解Bootstrap实现基本布局的方法

    看到了一篇 20 分钟打造 Bootstrap 站点的文章,内容有点老,重新使用bootstrap教程实现一下,将涉及的内容也尽可能详细说明. 1. 创建基本的页面我们先创建一个基本的 HTML 模板 ...

  5. [Java]先有Class还是先有Object?

    先有鸡还是先有蛋 这个问题让我想到了一个古老的问题,先有鸡还是先有蛋. 回到本题 下面让我们详细看一下这个问题: 在Java的对象模型中: 所有的类都是Class类的实例,Object是类,那么Obj ...

  6. 使用win10的开始屏幕,在系统中设置简洁、快捷桌面

    前几天入手了一个本本,由于之前电脑使用的柠檬桌面软件和现在本本的分辨率不适应,意外发现win10自带的开始屏幕整理桌面也是很有意思,再加上触摸板的手势,瞬间觉得整个电脑都清洁许多.废话少说,开始上料. ...

  7. css浮动与清除浮动

    css浮动 首先,我们要知道,css中块级元素在页面中是独占一行的,自上而下排列,也就是我们所说的流,通常称为标准流. 以div为例,div是块级元素,如下: 可以清楚地看到,div是独占一行的,di ...

  8. js switch 字符串

    //字符串var day="dd";switch (day){case "dd": x="day it's dd"; break;case ...

  9. Reids 持久化AOF 重写实现原理

    AOF重写 AOF重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的.(auto-aof-rewrite-percentage和auto-a ...

  10. kafka知识点

    一.为什么需要消息系统 1.解耦: 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束. 2.冗余: 消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险. ...