springbootwebsocket集(stomp协议)连接时候传递参
最近在公司项⽬中接到个需求。就是后台跟前端浏览器要保持长连接,后台主动往前台推数据。
⽹上查了下,websocket stomp协议处理这个很简单。尤其是跟springboot 集成。
但是由于开始是单机玩的,很顺利。
但是后⾯部署到⽣产搞集的话,就会出问题了。
假如集两个节点,浏览器A与节点A建⽴连接,A节点发的消息浏览器A节点肯定能收到。但是B节点由于没有跟浏览器A建⽴连接。B节点发的消息浏览器就收不到了。
⽹上也查了好多,但是没有⼀个说的很清楚的,也很多都是理论层⾯的。
还有很多思路都是通过session获取信息的。但是这都不是我需要的。我需要的是从前台传递参数,连接的时候每个节点保存下。然后通过User获取。
话不多说,直接上代码。
<script type="text/javascript" src="${tPath}/scripts/sockjs.min.js"></script>
<script type="text/javascript" src="${tPath}/scripts/stomp.min.js"></script>
var WEB_SOCKET = {
topic : "",
url : "",
stompClient : null,
connect : function(url, topic, callback,userid) {
this.url = url;
var socket = new SockJS(url); //连接SockJS的endpoint名称为"endpointOyzc"
WEB_SOCKET.stompClient = Stomp.over(socket);//使⽤STMOP⼦协议的WebSocket客户端
WEB_t({userid:userid},function(frame){//连接WebSocket服务端
// console.log('Connected:' + frame);
//通过stompClient.subscribe订阅/topic/getResponse ⽬标(destination)发送的消息
WEB_SOCKET.stompClient.subscribe(topic, callback);
});
}
};
这是响应的前端代码。只需要引⼊两个js。调⽤new SockJS(url) 就代表跟服务器建⽴连接了。
@Configuration
//注解开启使⽤STOMP协议来传输基于代理(message broker)的消息,这时控制器⽀持使⽤@MessageMapping,就像使⽤@RequestMapping⼀样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Autowired
private GetHeaderParamInterceptor getHeaderParamInterceptor;
@Override
//注册STOMP协议的节点(endpoint),并映射指定的url
public void registerStompEndpoints(StompEndpointRegistry registry) {
//注册⼀个STOMP的endpoint,并指定使⽤SockJS协议
registry.addEndpoint("/endpointOyzc")
.setAllowedOrigins("*")
.withSockJS();
/* registry.addEndpoint("/endpointOyzc")
.
setAllowedOrigins("*")
.setHandshakeHandler(xlHandshakeHandler)
.withSockJS();*/
}
@Override
//配置消息代理(Message Broker)
public void configureMessageBroker(MessageBrokerRegistry registry) {
//点对点应配置⼀个/user消息代理,⼴播式应配置⼀个/topic消息代理
// 全局使⽤的消息前缀(客户端订阅路径上会体现出来)
//registry.setApplicationDestinationPrefixes("/app");
/
/点对点使⽤的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/
registry.setUserDestinationPrefix("/user");
}
/**
* 采⽤⾃定义,获取connect时候传递的参数
*
* @param registration
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(getHeaderParamInterceptor);
}
}
注:上⾯的endpointOyzc就是前端的url。后⾯注册端点,前台链接。
然后注意下configureClientInboundChannel这个⽅法,这个⽅法⾥⾯注⼊就是为了链接时候接收参数的。/**
* @author : hao
* @description : websocket建⽴链接的时候获取headeri⾥认证的参数。
* @time : 2019/7/3 20:42
*/
@Component
public class GetHeaderParamInterceptor extends ChannelInterceptorAdapter {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = Accessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.Command())) {
Object raw = Headers().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
Object name = ((Map) raw).get("userid");
if (name instanceof LinkedList) {
// 设置当前访问的认证⽤户
accessor.setUser(new JqxxPrincipal(((LinkedList) name).get(0).toString()));
}
}
}
return message;
}
}
/**
* @author : hao
* @description : ⾃定义的java.security.Principal
* @time : 2019/7/3 20:42
*/
public class JqxxPrincipal implements Principal {
private String loginName;
public JqxxPrincipal(String loginName) {
this.loginName = loginName;
}
@Override
public String getName() {
return loginName;
}
}
这样就存⼊的前台传的参数。
后台发消息的时候怎么发呢?
/**
* @author : hao
* @description : websocket发送代理,负责发送消息
* @time : 2019/7/4 11:01
*/
@Component
@Slf4j
public class WebsocketSendProxy<T> {
@Autowired
private SimpMessagingTemplate template;
@Autowired
private SimpUserRegistry userRegistry;
@Resource(name = "redisServiceImpl")
private RedisService redisService;
@Value("pic-name")
private String topicName;
public void sendMsg(RedisWebsocketMsg<T> redisWebsocketMsg) {
SimpUser simpUser = Receiver());
log.info("发送消息前获取接收⽅为{},根据Registry获取本节点上这个⽤户{}", Receiver(), simpUser);
if (simpUser != null && StringUtils.Name())) {
//2. 获取WebSocket客户端的订阅地址
WebSocketChannelEnum channelEnum = WebSocketChannelEnum.ChannelCode());
if (channelEnum != null) {
//3. 给WebSocket客户端发送消息
}
} else {
//给其他订阅了主题的节点发消息,因为本节点没有
}
}
}
可以发现上⾯代码利⽤了redis监听模型,也就是redis模型的消息队列
/**
* @author : hao
* @description : redis消息监听实现类,接收处理类
* @time : 2019/7/3 14:00
*/
@Component
@Slf4j
public class MessageReceiver {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Autowired
private SimpUserRegistry userRegistry;
/**
* 处理WebSocket消息
*/
前端websocket怎么用public void receiveMessage(RedisWebsocketMsg redisWebsocketMsg) {
log.info(MessageFormat.format("Received Message: {0}", redisWebsocketMsg));
//1. 取出⽤户名并判断是否连接到当前应⽤节点的WebSocket
SimpUser simpUser = Receiver());
if (simpUser != null && StringUtils.Name())) {
//2. 获取WebSocket客户端的订阅地址
WebSocketChannelEnum channelEnum = WebSocketChannelEnum.ChannelCode());
if (channelEnum != null) {
//3. 给WebSocket客户端发送消息
}
}
}
}
redis消息模型只贴部分代码就好了
/**
* 消息
*/
@Bean
MessageListenerAdapter messageListenerAdapter(MessageReceiver messageReceiver, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){  //消息接收者以及对应的默认处理⽅法
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(messageReceiver, "receiveMessage");
//消息的反序列化⽅式
messageListenerAdapter.setSerializer(jackson2JsonRedisSerializer);
return messageListenerAdapter;
}
/**
* message listener container
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
, MessageListenerAdapter messageListenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//添加消息
container.addMessageListener(messageListenerAdapter, new PatternTopic(topicName));
return container;
}
上⾯的思路⼤体如下:客户端简历链接时候,传过来userid保存起来。发消息的时候通过userRegistry获取,能获取到就证明是跟本节点建⽴的链接,直接⽤本节点发消息就好了。
如果不是就利⽤redis消息队列,把消息推出去。每个节点去判断获取看下是不是本节点的userid。这样就实现了集的部署。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。