天天看点

Spring4.3.3 WebSocket-STOMP协议集成 (2)-WebSocket-stomp子协议通讯小栗子

        前面说到,使用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 >
    <%-- <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" target="_blank" rel="external nofollow" >
    <!-- 可选的Bootstrap主题文件(一般不用引入) -->
    <link rel="stylesheet" href="${ctxStatic }/bootstrap/3.3.0/css/bootstrap-theme.min.css" target="_blank" rel="external nofollow" >
    <!-- 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>
           

       启动运行,效果如下:

       浏览器:

Spring4.3.3 WebSocket-STOMP协议集成 (2)-WebSocket-stomp子协议通讯小栗子

       服务器:

Spring4.3.3 WebSocket-STOMP协议集成 (2)-WebSocket-stomp子协议通讯小栗子

如果出现spring扫描不到@MessageMapping注解方法的情况,在AbstractWebSocketMessageBrokerConfigurer的实现方法上加上注解:@ComponentScan("controller所在的包名")