前面说到,使用websocket通讯,现在说说应用上的通讯,stomp - streaming / simple text oriented protocol. 流/简单 文本协议。应用方面,一般采用该种协议,是websocket协议的一个子协议,了解一下既可。
stomp协议,配置时注意一个地方,stomp协议使用的中继器(路由)或者叫消息中介,默认在configureMessageBroker方法中,registry.enableSimpleBroker是使用内存中介,不依赖第三方组件,registry.enableStompBrokerRelay是使用第三方中间件,需要事先下载安装中间件,如activeMQ/RabbitMQ.本例子中使用内存中继。
具体代码实现:
1. WebSocketMessageBrokerConfigurer配置类,继承AbstractWebSocketMessageBrokerConfigurer重写下面方法。
package com.websocket.stomp;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketTransportRegistration;
/**
* stomp websocket的子协议,stomp: simple/streaming text oriented message protocol. 简单/流 文本消息协议,
* 选择使用内存中级,还是使用activeMQ等中间件服务器
* @author tomZ
* @date 2016年11月3日
* @desc TODO
*/
@Configuration
@EnableWebSocketMessageBroker
public class MyWebSocketMessageBrokerConfig extends AbstractWebSocketMessageBrokerConfigurer {
/**
* 连接的端点,客户端建立连接时需要连接这里配置的端点
*/
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
//为java stomp client提供链接
registry.addEndpoint("/client")
.setAllowedOrigins("*")
.setHandshakeHandler(new MyHandshakeHandler())
.addInterceptors(new MyHandshakeInterceptor());
//为js客户端提供链接
registry.addEndpoint("/hello")
.setAllowedOrigins("*")
.setHandshakeHandler(new MyHandshakeHandler())
.addInterceptors(new MyHandshakeInterceptor())
.withSockJS();
}
/**
* applicationDestinationPrefixes应用前缀,所有请求的消息将会路由到@MessageMapping的controller上,
* enableStompBrokerRelay是代理前缀,而返回的消息将会路由到代理上,所有订阅该代理的将收到响应的消息。
*
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.setUserDestinationPrefix("/user");
registry.enableSimpleBroker("/topic", "/queue")
// registry.enableStompBrokerRelay("/topic", "/queue")
//下面这配置为默认配置,如有变动修改配置启用就可以了
// .setRelayHost("127.0.0.1") //activeMq服务器地址
// .setRelayPort(61613)//activemq 服务器服务端口
// .setClientLogin("guest") //登陆账户
// .setClientPasscode("guest") //
;
}
/**
* 消息传输参数配置
*/
@Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
// super.configureWebSocketTransport(registration);
registration.setMessageSizeLimit(8192).setSendBufferSizeLimit(8192).setSendTimeLimit(10000);
}
/**
* 输入通道参数设置
*/
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
// super.configureClientInboundChannel(registration);
//线程信息
registration.taskExecutor().corePoolSize(4).maxPoolSize(8).keepAliveSeconds(60);
}
/**
* 输出通道参数配置
*/
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
// super.configureClientOutboundChannel(registration);
//线程信息
registration.taskExecutor().corePoolSize(4).maxPoolSize(8);
}
@Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
// return super.configureMessageConverters(messageConverters);
return true;
}
}
Handler类:处理器类
package com.websocket.stomp;
import java.security.Principal;
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.DefaultHandshakeHandler;
/**
* stomp 处理器
* @author tomZ
* @date 2016年11月4日
* @desc TODO
*/
public class MyHandshakeHandler extends DefaultHandshakeHandler {
///该方法可以重写用来为用户 添加标识 返回principal
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// TODO Auto-generated method stub
return super.determineUser(request, wsHandler, attributes);
}
}
Interceptor类。拦截器
package com.websocket.stomp;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
* stomp握手拦截器
* @author tomZ
* @date 2016年11月4日
* @desc TODO
*/
public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final Logger logger = LoggerFactory.getLogger( MyHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
logger.info("===============before handshake=============");
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
logger.info("===============after handshake=============");
super.afterHandshake(request, response, wsHandler, ex);
}
}
2. 最后在spring-context.xml (application-context.xml)中扫描上面类所在的包即可。
3. controller, @MessageMapping用法和@RequestMapping相似,@SendTo是将消息发到指定的映射路由uri上去,@Subscribe是订阅uri注解,此外还有@SendToUser/@DestinationVariable等注解,可以查一下。
package com.tom.jeesite.web.stomp;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
*
* @author tom
* @version 2016-06-12
*/
@Controller
public class MyStompTestController {
/**
* @MessageMapping 是app路由地址,客户端请求将交由其处理,@SendTo是返回消息路由到指定地址,订阅该地址的将接收到消息
* @param incoming
* @return
*/
@MessageMapping("/hi")
@SendTo("/topic/hi")
public String handleHi(String incoming) {
System.out.println("receive message : " + incoming);
return "hello, " + incoming;
}
/**
* 订阅,当有客户端订阅该内容,会有一次性响应
* @return
*/
@SubscribeMapping("/subscribeme")
public String subscribeThing() {
System.out.println("subscribe message called.");
return "thank you subscribe my channel";
}
}
4. Jsp页面:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var="ctxStatic" value="${pageContext.request.contextPath}/static"/>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<%
String path = request.getContextPath();
String basePath = request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head lang="en">
<%-- <script src="${ctxStatic }/websocket/sockjs-0.3.min.js"></script> --%>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="${ctxStatic }/bootstrap/3.3.0/css/bootstrap.min.css">
<!-- 可选的Bootstrap主题文件(一般不用引入) -->
<link rel="stylesheet" href="${ctxStatic }/bootstrap/3.3.0/css/bootstrap-theme.min.css">
<!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
<script src="${ctxStatic}/jquery/jquery-1.9.1.min.js" type="text/javascript"></script>
<!--<script type="text/javascript" src="js/jquery-1.7.2.js"></script>-->
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="${ctxStatic }/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<%-- <script src="${ctxStatic }/stomp/stomp.mini.js"></script> --%>
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<title>stomp测试</title>
<script type="text/javascript">
$(document).ready(function() {
var sock = new SockJS("${ctx}/hello");
var stomp = Stomp.over(sock);
stomp.connect('guest', 'guest', function(frame) {
console.log('***** Connected *****');
$('#msg').append("<b>***** Connected *****</b><br/>");
//一次性订阅,只返回一次
stomp.subscribe("/app/subscribeme", handleOneTime);
//订阅代理,代理发消息将会接收到
stomp.subscribe("/topic/hi", handleMsg);
}, function(error) {
console.log('error:'+error);
});
function handleOneTime(message) {
//alert('123');
console.log('Received: ', message);
$('#msg').append("<b>handleOneTime - Received: " +
message.body + "</b><br/>");
}
function handleMsg(message) {
console.log('Received: ', message);
$('#msg').append("<b>Received: " +
message.body + "</b><br/>");
// if (JSON.parse(message.body).message === 'Polo!') {
// setTimeout(function(){sayMarco()}, 2000);
// }
}
function handleErrors(message) {
console.log('RECEIVED ERROR: ', message);
$('#msg').append("<b>GOT AN ERROR!!!: " +
JSON.parse(message.body).message + "</b><br/>");
}
function send() {
console.log('Sending msg!');
//发送
stomp.send("/app/hi", {},
JSON.stringify({ 'message': $('#message').val()}));
// stomp.send("/topic/marco", {},
// JSON.stringify({ 'message': 'Marco!' }));
$('#msg').append("<b>Send: " + $('#message').val() + "!</b><br/>");
}
$('#stop').click(function() {
sock.close();
$('#msg').append("<b>Connection closed!</b><br/>");
});
$('#send').bind('click', function() {
send();
});
});
</script>
</head>
<body>
<div class="page-header" id="tou">
webSocket-stomp测试程序
<button class="btn btn-default" type="button" id="stop" >关闭连接</button>
</div>
<div class="well" id="msg">
</div>
<div class="col-lg">
<div class="input-group">
<input type="text" class="form-control" placeholder="发送信息..." id="message">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="send" >发送</button>
</span>
</div><!-- /input-group -->
</div><!-- /.col-lg-6 -->
</body>
</html>
启动运行,效果如下:
浏览器:
服务器:
如果出现spring扫描不到@MessageMapping注解方法的情况,在AbstractWebSocketMessageBrokerConfigurer的实现方法上加上注解:@ComponentScan("controller所在的包名")