websocket与redismq实现集群消息聊天

1.application.properties

server.port=8081

#thymeleaf配置
#是否启用模板缓存。
spring.thymeleaf.cache=false
#是否为Web框架启用Thymeleaf视图解析。
spring.thymeleaf.enabled=true
#在SpringEL表达式中启用SpringEL编译器。
spring.thymeleaf.enable-spring-el-compiler=true
#模板文件编码。
spring.thymeleaf.encoding=UTF-8
#要应用于模板的模板模式。另请参见Thymeleaf的TemplateMode枚举。
spring.thymeleaf.mode=HTML5
#在构建URL时添加前缀以查看名称的前缀。
spring.thymeleaf.prefix=classpath:/templates/
#Content-Type写入HTTP响应的值。
spring.thymeleaf.servlet.content-type=text/html
#在构建URL时附加到视图名称的后缀。
spring.thymeleaf.suffix=.html ##单服务器
spring.redis.host=192.168.159.129
##单端口
spring.redis.port=6379
## 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=300
## Redis数据库索引(默认为0)
spring.redis.database=0
## 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
## 连接池中的最大空闲连接
spring.redis.pool.max-idle=100
## 连接池中的最小空闲连接
spring.redis.pool.min-idle=20
## 连接超时时间(毫秒)
spring.redis.timeout=60000

2.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.szw.learn</groupId>
<artifactId>websocket_redis_mq_01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>websocket_redis_mq_01</name> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.16.RELEASE</version>
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.test.skip>true</maven.test.skip>
<skipTests>true</skipTests>
<thymeleaf.version>3.0.7.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.2</thymeleaf-layout-dialect.version>
<start-class>com.szw.learn.WsMqApplication</start-class>
</properties> <dependencies>
<!-- 使用web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <!-- 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> <!-- redis id与1.5之前的变了 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> <!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies> <repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories> <build>
<plugins>
<!-- 要将源码放上去,需要加入这个插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>

3.SpringUtils.java

package com.szw.learn.util;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Repository; @Repository
public final class SpringUtils implements BeanFactoryPostProcessor { private static ConfigurableListableBeanFactory beanFactory; // Spring应用上下文环境 @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtils.beanFactory = beanFactory;
} public static ConfigurableListableBeanFactory getBeanFactory() {
return beanFactory;
} /**
* 获取对象
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws org.springframework.beans.BeansException
*
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) getBeanFactory().getBean(name);
} /**
* 获取类型为requiredType的对象
*
* @param clz
* @return
* @throws org.springframework.beans.BeansException
*
*/
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = (T) getBeanFactory().getBean(clz);
return result;
} /**
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name) {
return getBeanFactory().containsBean(name);
} /**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
*
* @param name
* @return boolean
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return getBeanFactory().isSingleton(name);
} /**
* @param name
* @return Class 注册对象的类型
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
return getBeanFactory().getType(name);
} /**
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
*
* @param name
* @return
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return getBeanFactory().getAliases(name);
} }

4.redis

发布service:

package com.szw.learn.redismq;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component; /**
* @author 七脉 描述:发布service
*/
@Component
public class PublishService {
@Autowired
StringRedisTemplate redisTemplate; /**
* @author 七脉 描述:发布方法
* @param channel 消息发布订阅 主题
* @param message 消息信息
*/
public void publish(String channel, Object message) {
// 该方法封装的 connection.publish(rawChannel, rawMessage);
redisTemplate.convertAndSend(channel, message);
}
}

订阅监听类:

package com.szw.learn.redismq;

import java.io.IOException;

import javax.websocket.Session;

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.StringRedisTemplate; /**
* @author 七脉 描述:订阅监听类
*/
public class SubscribeListener implements MessageListener { private StringRedisTemplate stringRedisTemplate; private Session session; /**
* 订阅接收发布者的消息
*/
@Override
public void onMessage(Message message, byte[] pattern) {
String msg = new String(message.getBody());
System.out.println(new String(pattern) + "主题发布:" + msg);
if(null!=session){
try {
session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
} public StringRedisTemplate getStringRedisTemplate() {
return stringRedisTemplate;
} public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
} public Session getSession() {
return session;
} public void setSession(Session session) {
this.session = session;
} }

注册redis监听容器:

package com.szw.learn.redismq;

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.jedis.JedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer; @Configuration
public class RedisConfig {
@Autowired
private JedisConnectionFactory jedisConnectionFactory; /**
* @author 七脉 描述:需要手动注册RedisMessageListenerContainer加入IOC容器
* @return
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer() { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(jedisConnectionFactory); return container; }
}

5.websocket

websocket注册:

package com.szw.learn.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 {
/**
* <br>描 述: @Endpoint注解的websocket交给ServerEndpointExporter自动注册管理
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}

websocket端点:

package com.szw.learn.websocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet; 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 org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component; import com.szw.learn.redismq.PublishService;
import com.szw.learn.redismq.SubscribeListener;
import com.szw.learn.util.SpringUtils;
/**
*@ServerEndpoint(value="/websocket")value值必须以/开路
*备注:@ServerEndpoint注解类不支持使用@Autowire
*{topic}指:向哪个频道主题里发消息
*{myname}指:这个消息是谁的。真实环境里可以使用当前登录用户信息
*/
@Component
@ServerEndpoint(value="/websocket/{topic}/{myname}")
public class WebsocketEndpoint { /**
* 因为@ServerEndpoint不支持注入,所以使用SpringUtils获取IOC实例
*/
private StringRedisTemplate redisTampate = SpringUtils.getBean(StringRedisTemplate.class); private RedisMessageListenerContainer redisMessageListenerContainer = SpringUtils.getBean(RedisMessageListenerContainer.class); //存放该服务器该ws的所有连接。用处:比如向所有连接该ws的用户发送通知消息。
private static CopyOnWriteArraySet<WebsocketEndpoint> sessions = new CopyOnWriteArraySet<>(); private Session session; @OnOpen
public void onOpen(Session session,@PathParam("topic")String topic){
System.out.println("java websocket:打开连接");
this.session = session;
sessions.add(this);
SubscribeListener subscribeListener = new SubscribeListener();
subscribeListener.setSession(session);
subscribeListener.setStringRedisTemplate(redisTampate);
//设置订阅topic
redisMessageListenerContainer.addMessageListener(subscribeListener, new ChannelTopic(topic));
} @OnClose
public void onClose(Session session){
System.out.println("java websocket:关闭连接");
sessions.remove(this);
} @OnMessage
public void onMessage(Session session,String message,@PathParam("topic")String topic,@PathParam("myname")String myname) throws IOException{
message = myname+":"+message;
System.out.println("java websocket 收到消息=="+message);
PublishService publishService = SpringUtils.getBean(PublishService.class);
publishService.publish(topic, message);
} @OnError
public void onError(Session session,Throwable error){
System.out.println("java websocket 出现错误");
} public Session getSession() {
return session;
} public void setSession(Session session) {
this.session = session;
}
}

测试controller

package com.szw.learn.websocket;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView; @Controller
@RequestMapping("websocket")
public class WebsocketController { @Value("${server.port}")
private String port; public static final String INDEX = "websocket/index"; /**
* @author 七脉
* 描述:聊天页
* @param topic 发布订阅的频道主题
* @param myname 发布者的显示名称
* @return
*/
@RequestMapping("index/{topic}/{myname}")
public ModelAndView index(@PathVariable("topic")String topic,@PathVariable("myname")String myname){
ModelAndView mav = new ModelAndView(INDEX);
mav.addObject("port", port);
mav.addObject("topic",topic);
mav.addObject("myname",myname);
return mav;
}
}

6.启动类

package com.szw.learn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class WsMqApplication {
public static void main(String[] args) {
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(WsMqApplication.class, args);
}
}

7.测试页面

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"></meta>
<title>websocket集群</title>
</head>
<body>
本服务端口号:[[${port}]],使用redismq实现websocket集群<br/>
[[${topic}]] 频道 聊天中。。。<br/>
<input id="input_id" type="text" /><button onclick="sendMessage()">发送</button> <button onclick="closeWebsocket()">关闭</button>
<div id="message_id"></div>
</body>
<script type="text/javascript">
document.getElementById('input_id').focus();
var websocket = null;
//当前浏览前是否支持websocket
if("WebSocket" in window){
var url = "ws://127.0.0.1:[[${port}]]/websocket/[[${topic}]]/[[${myname}]]";
websocket = new WebSocket(url);
}else{
alert("浏览器不支持websocket");
} websocket.onopen = function(event){
setMessage("打开连接");
} websocket.onclose = function(event){
setMessage("关闭连接");
} websocket.onmessage = function(event){
setMessage(event.data);
} websocket.onerror = function(event){
setMessage("连接异常");
} //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function(){
closeWebsocket();
} //关闭websocket
function closeWebsocket(){
//3代表已经关闭
if(3!=websocket.readyState){
websocket.close();
}else{
alert("websocket之前已经关闭");
}
} //将消息显示在网页上
function setMessage(message){
document.getElementById('message_id').innerHTML += message + '<br/>';
} //发送消息
function sendMessage(){
//1代表正在连接
if(1==websocket.readyState){
var message = document.getElementById('input_id').value;
//setMessage(message);
websocket.send(message);
}else{
alert("websocket未连接");
}
document.getElementById('input_id').value="";
document.getElementById('input_id').focus();
}
</script>
</html>

8.测试

  启动两个服务,端口号分别8081、8082(可以+)

  模拟两个端口的地址:

    http://localhost:8081/websocket/index/like/董志峰

    http://localhost:8082/websocket/index/like/史振伟

  如图

  

源码下载:https://pan.baidu.com/s/1VMQJgXe5vX7uwsyRV57gIw

websocket redis实现集群即时消息聊天的更多相关文章

  1. (七)整合 Redis集群 ,实现消息队列场景

    整合 Redis集群 ,实现消息队列场景 1.Redis集群简介 1.1 RedisCluster概念 2.SpringBoot整合Redis集群 2.1 核心依赖 2.2 核心配置 2.3 参数渲染 ...

  2. redis sentinel 集群监控 配置

    环境: ip  172.16.1.31 26379  redis sentinel ip  172.16.1.30 6379   主 1 ip  172.16.1.31 6380   从 1 ip   ...

  3. Redis Cluster搭建高可用Redis服务器集群

    一.Redis Cluster集群简介 Redis Cluster是Redis官方提供的分布式解决方案,在3.0版本后推出的,有效地解决了Redis分布式的需求,当一个节点挂了可以快速的切换到另一个节 ...

  4. 10.Redis分布式集群

    10.Redis分布式集群10.1 数据分布10.1.1 数据分布理论10.1.2 Redis数据分区10.1.3 集群功能限制10.2 搭建集群10.2.1 准备节点10.2.2 节点握手10.2. ...

  5. redis sentinel集群的搭建

    背景说明: 这里采用1主2从的redis集群,3个sentinel搭建高可用redis集群. 一,关于搭建redis-sentinel高可用之前,我们必须要了解redis主从搭建redis-senti ...

  6. Redis Cluster集群知识学习总结

    Redis集群解决方案有两个: 1)  Twemproxy: 这是Twitter推出的解决方案,简单的说就是上层加个代理负责分发,属于client端集群方案,目前很多应用者都在采用的解决方案.Twem ...

  7. Centos6 安装 Redis 和集群配置

    Redis安装 先确认gcc和tcl已经安装 sudo yum install gcc-c++ sudo yum install tcl 解压, 编译和安装 .tar.gz /usr/src/ cd ...

  8. Redis Cluster 集群使用(3)

    简介 Redis3.0版本之前,可以通过Redis Sentinel(哨兵)来实现高可用(HA),从3.0版本之后,官方推出了Redis Cluster,它的主要用途是实现数据分片(Data Shar ...

  9. 11.Redis 哨兵集群实现高可用

    作者:中华石杉 Redis 哨兵集群实现高可用 哨兵的介绍 sentinel,中文名是哨兵.哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能: 集群监控:负责监控 redis mast ...

随机推荐

  1. 机房收费系统(VB.NET)——超具体的报表制作过程

    之前做机房收费系统用的报表是Grid++Report,这次VB.NET重构中用到了VisualStudio自带的报表控件. 刚開始当然对这块功能非常不熟悉,只是探究了一段时间后还是把它做出来了. 以下 ...

  2. 73、ImageView 让图片填充满

    在xml里的ImageView中设置   android:scaleType="fitXY"属性,这个属性是让图片填充满, 当然你可以去查一下这个scaleType的其他属性,你要 ...

  3. jQuery中如何给动态添加的元素绑定事件

    jquery中绑定事件一般使用bind,或者click,但是这只能是对已经加载好的元素定义事件,那些后来添加插入的元素则需要另行绑定.在1.7版本以前使用live.但是在1.8版本以后推荐使用on.这 ...

  4. Kotlin——高级篇(二):高阶函数详解与标准的高阶函数使用

    在上面一个章节中,详细的讲解了Kotlin中关于Lambda表达式的语法以及运用,如果还您对其还不甚理解,请参见Kotlin--高级篇(一):Lambda表达式详解.在这篇文章中,多次提到了Kotli ...

  5. homebrew常用指令

    其它Homebrew指令: brew list   —列出已安装的软件 brew update   —更新Homebrew brew home  *—用浏览器打开 brew info   *—显示软件 ...

  6. 表单验证 靠name获取

    表单 靠name获取 <form class="add-form" name="form" action="#" method=&qu ...

  7. 160711、Java 多线程核心技术梳理

    本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock 的使用,定时器,单例模式,以及线程状态与线程组. java 多线程 基础知识 创建线程的两种方式:1 ...

  8. Oracle-Rman(物理备份)

    Rman(物理备份) Rman -recover manager Rman 备份的对象 数据文件 数据文件 (Data File) 控制文件 控制文件 (Control File) 参数文件 参数文件 ...

  9. Android 点击电话号码之间拨号

    点击电话号码之间拨打电话,可用通过下面的方式实现: 假设电话号码以TextView的方式显示 1.Intent方式 在TextView的响应事件中 : String phone = tvphone.g ...

  10. 如何基于Go搭建一个大数据平台

    如何基于Go搭建一个大数据平台 - Go中国 - CSDN博客 https://blog.csdn.net/ra681t58cjxsgckj31/article/details/78333775 01 ...