websocket session共享
单机运行
用户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 "%r" %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共享的更多相关文章
- [Spring] spring-session + JedisPool 实现 session 共享
1.至少导入四个jar包: jedis spring-session spring-data-redis commons-pool2 2.bean配置 <?xml version="1 ...
- Nginx+Tomcat搭建集群,Spring Session+Redis实现Session共享
小伙伴们好久不见!最近略忙,博客写的有点少,嗯,要加把劲.OK,今天给大家带来一个JavaWeb中常用的架构搭建,即Nginx+Tomcat搭建服务集群,然后通过Spring Session+Redi ...
- Nginx反向代理,负载均衡,redis session共享,keepalived高可用
相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tomcat服务器两台,由nginx进行反向代理和负载均衡,此 ...
- 分布式中使用Redis实现Session共享(二)
上一篇介绍了一些redis的安装及使用步骤,本篇开始将介绍redis的实际应用场景,先从最常见的session开始,刚好也重新学习一遍session的实现原理.在阅读之前假设你已经会使用nginx+i ...
- windows 环境下nginx + tomcat群 + redis 实现session共享
nginx作为负载均衡根据定义将不同的用户请求分发到不同的服务器,同时也解决了因单点部署服务器故障导致的整个应用不能访问的问题 在加入nginx之后,如果多个服务器中的一个或多个(不是全部)发生故障, ...
- shiro实现session共享
session共享:在多应用系统中,如果使用了负载均衡,用户的请求会被分发到不同的应用中,A应用中的session数据在B应用中是获取不到的,就会带来共享的问题. 假设:用户第一次访问,连接的A服务器 ...
- Redis安装及实现session共享
一.Redis介绍 1.redis是key-value的存储系统,属于非关系型数据库 2.特点:支持数据持久化,可以让数据在内存中保存到磁盘里(memcached:数据存在内存里,如果服务重启,数据会 ...
- Tomcat7基于Redis的Session共享实战二
目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无 ...
- Nginx+Tomcat+Redis实现负载均衡、资源分离、session共享
Nginx+Tomcat+Redis实现负载均衡.资源分离.session共享 CentOS安装Nginx http://centoscn.com/CentosServer/www/2013/0910 ...
随机推荐
- js 控制 class 类名(classList) 和 自定义属性(dataset)
classList 用法: const div = document.querySelector('div') div.classList.add('myclass') // 添加类名 div.cla ...
- Codeforces 101623E English Restaurant - 动态规划
题目传送门 传送门 题目大意 餐厅有$n$张桌子,第$i$张桌子可以容纳$c_i$个人,有$t$组客人,每组客人的人数等概率是$[1, g]$中的整数. 每来一组人数为$x$客人,餐厅如果能找到最小的 ...
- redhat7 配置使用centos的yum源
新安装了redhat7.安装后,登录系统,使用yum update 更新系统.提示: This system is not registered to Red Hat Subscription Man ...
- BZOJ1688|二进制枚举子集| 状态压缩DP
Disease Manangement 疾病管理 Description Alas! A set of D (1 <= D <= 15) diseases (numbered 1..D) ...
- 调用Bytom Chrome插件钱包开发Dapp
安装使用插件钱包 1. 打开Google浏览器的应用商店,搜索Bystore 下载链接:http://t.cn/E6cFFwb 2. 然后点击添加到Chrome,就可以添加到我们的: 3. 使用goo ...
- git\CentOS6.5中gitlab安装教程
一.Git 起源: Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本 ...
- 20175317 MyCP(课下作业,必做)
一.题目要求 编写MyCP.java 实现类似Linux下cp XXX1 XXX2的功能,要求MyCP支持两个参数: java MyCP -tx XXX1.txt XXX2.bin 用来把文本文件(内 ...
- 用node.js启动mock.js
Node.js Node 是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为与PHP.Python.Perl.Ruby 等服务端语言平起平坐的脚本语言.官网下载n ...
- 【A tour of go】练习题
练习:循环与函数 (1)题目 为了练习函数与循环,我们来实现一个平方根函数:用牛顿法实现平方根函数. 计算机通常使用循环来计算 x 的平方根.从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近 ...
- Django框架简介-路由系统
2.2 路由系统 Django 1.11版本 URLConf官方文档 URL配置(URLconf)就像Django 所支撑网站的目录.它的本质是URL与要为该URL调用的视图函数之间的映射表. 你就是 ...