项目里用到了websocket 但是后台是tomcat集群 有时候就会发现前台没收到推送消息。发现websocket是用一个静态变量存的前后台连接。
百度发现解决方案有好多 选了个简单的 使用redis 发布订阅方式实现 就是后台收到需要推送的消息时就往redis发布一下。然后所有的tomcat都订阅他 就可以实现 集群不漏发的问题了。
一下是一些实现的代码和逻辑。
websocket代码、
package com.asdc.jbp.hisLogin.websocket;
import java.io.IOException;
import java.util.UUID;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ContextLoader;
@Component("OutpatientDocWebSocket")
@ServerEndpoint("/outpatientDoc/{userId}")
public class OutpatientDocWebSocket {
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
//private static CopyOnWriteArraySet<OutpatientDocWebSocket> outpatientDocWebSocket = new CopyOnWriteArraySet<OutpatientDocWebSocket>();
private static ConcurrentHashMap<String, OutpatientDocWebSocket> outpatientDocWebSocket = new ConcurrentHashMap<String, OutpatientDocWebSocket>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//当前发消息的人员id
private String userId = "";
//保证当前连接是唯一的
private String uniqueUuid = "";
@Autowired
private OutpatientRedisDao outpatientRedisDao;
//=(OutpatientRedisDao) ContextLoader.getCurrentWebApplicationContext().getBean("OutpatientRedisDao")
private Logger log=LoggerFactory.getLogger(getClass());
/**
* 连接建立成功调用的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam(value = "userId") String paramUserId,Session session) {
this.session = session;
userId=paramUserId;
uniqueUuid=UUID.randomUUID().toString();
outpatientDocWebSocket.put(paramUserId, this); // 加入set中
OutpatientRedisDao outpatientRedisDaoOnOpen=(OutpatientRedisDao) ContextLoader.getCurrentWebApplicationContext().getBean("OutpatientRedisDao");
//广播建立连接 删除其他服务器连接
outpatientRedisDaoOnOpen.sendMessageByOpen(OutpatientRedisDao.outpatientSocketOpenChannel, userId+"|"+uniqueUuid);
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(userId!=""){
outpatientDocWebSocket.remove(userId); // 从map中删除
}
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
//System.out.println("来自客户端的消息:" + message);
//接受到的只能是websocket heart请求 不需理会
//System.out.println(message);
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
//System.out.println("发生错误");
log.error("websocket发生错误:",error);
//error.printStackTrace();
}
/**
*
* Description : 向前台发送消息 <br>
* Create Time: 2018年4月26日 <br>
* Create by : xxx<br>
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
*
* Description : 后台业务调用方法 <br>
* Create Time: 2018年5月7日 <br>
* Create by : xxx<br>
*
* @param regMsg
*/
public void SendRegisterMessage(String regMsg) {
outpatientRedisDao.sendMessage(OutpatientRedisDao.outpatientChannel, regMsg);
// 群发消息,向所有连接这个websocket服务器的客户端发送消息.
}
/**
*
* Description : 接受redis回调的推送方法 <br>
* Create Time: 2018年5月7日 <br>
* Create by : xxx<br>
*
* @param callBackMsg
*/
public void sendMessageHandle(String callBackMsg){
for (OutpatientDocWebSocket item : outpatientDocWebSocket.values()) {
try {
item.sendMessage(callBackMsg);
} catch (IOException e) {
log.error("websocket发送出错:",e);
e.printStackTrace();
continue;
}
}
}
/**
*
* Description : 建立连接时删除当前已存在的连接---保持连接唯一 <br>
* Create Time: 2018年5月7日 <br>
* Create by : xxx<br>
*
* @param callBackMsg
*/
public void onOpenRemoveHandle(String callBackMsg){
String[] msgs=callBackMsg.split("\\|");
//0用户id 1 唯一标识uuid
for (OutpatientDocWebSocket item : outpatientDocWebSocket.values()) {
if(msgs[0].equals(item.userId)&&!msgs[1].equals(item.uniqueUuid)){
outpatientDocWebSocket.remove(item.userId);
}
}
}
}
监听redis订阅
package com.asdc.jbp.hisLogin.redisUtil;
import org.springframework.beans.factory.annotation.Autowired;
/**
*
* ClassName : OutpatientDocMessageDelegateListenerImpl <br>
* Description : 接受订阅默认代理-----由 MessageListenerAdapter delegate <br>
* Create Time : 2018年4月26日 <br>
* Create by : xxx<br>
*
*/
public class OutpatientDocMessageDelegateListenerImpl{
@Autowired
private OutpatientDocWebSocket outpatientDocWebSocket;
/**
*
* Description : 此方法里实现订阅后调用方法 <br>
* Create Time: 2018年4月26日 <br>
* Create by : xxx<br>
*
* @param message
*/
public void handleMessage(String message){
outpatientDocWebSocket.sendMessageHandle(message);
}
/**
*
* Description : 连接时回调方法 <br>
* Create Time: 2018年5月9日 <br>
* Create by : xxx<br>
*
* @param message
*/
public void handleMessageByOpen(String message){
outpatientDocWebSocket.onOpenRemoveHandle(message);
}
}
往redis发布消息
public interface OutpatientRedisDao {
public static final String outpatientChannel ="outpatientDoc_Channel";
public static final String outpatientSocketOpenChannel="outpatientSocketOpen_Channel";
/**
*
* Description : 挂号发送医嘱列表信息 <br>
* Create Time: 2018年4月26日 <br>
* Create by : xxx <br>
*
* @param channel
* @param message
*/
public void sendMessage(String channel,String message);
public void sendMessageByOpen(String channel,String message);
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component("OutpatientRedisDao")
public class OutpatientRedisDaoImpl implements OutpatientRedisDao {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void sendMessage(String channel, String message) {
redisTemplate.convertAndSend(channel, message);
}
@Override
public void sendMessageByOpen(String channel, String message) {
redisTemplate.convertAndSend(channel, message);
}
}
spring-redis配置
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean
class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean
class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
</bean>
<!-- 挂号医嘱列表 订阅发布 -->
<bean id="outpatientMessageDelegateListener" class="com.OutpatientDocMessageDelegateListenerImpl" />
<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="outpatientMessageDelegateListener" />
<property name="serializer" ref="serializer" />
</bean>
<!-- org.springframework.data.redis.serializer.StringRedisSerializer 会有乱码 -->
<bean id="serializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
<redis:listener-container connection-factory="jedisConnectionFactory">
<!-- the method attribute can be skipped as the default method name is "handleMessage" -->
<redis:listener ref="outpatientMessageDelegateListener" serializer="serializer" method="handleMessage" topic="outpatientDoc_Channel" />
<redis:listener ref="outpatientMessageDelegateListener" serializer="serializer" method="handleMessageByOpen" topic="outpatientSocketOpen_Channel" />
</redis:listener-container>
代码还不是很完善 基本思路就这样。水平有限啊。。。。。