欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring4+WebSocket+Socket+STOMP+Jetty构建示例

程序员文章站 2022-04-10 17:13:48
...

 

Spring 4引入了WebSocket API,浏览器和Web服务器可以根据WebSocket API通过WebSocket协议进行通信。通过这种方式,我们可以创建一个高度交互的UI和在线游戏,需要从服务器快速响应。SockJS在UI上提供类似WebSocket的对象。STOMP客户端用于通过WebSocket协议进行通信。我正在使用tomcat服务器来部署项目。下图为WebSocket通信的图表。

 Spring4+WebSocket+Socket+STOMP+Jetty构建示例
            
    
    博客分类: springwebsocketstomp springwebsocketstomp 

 

WebSocket协议

WebSocket是一种在浏览器和Web服务器之间进行通信的协议。WebSocket通过TCP协议工作。它在浏览器和Web服务器之间打开套接字连接并开始通信,它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。WebSocket可以轻松嵌入子协议,如STOMP。

 

STOMP协议

stomp是一个用于client之间进行异步消息传输的简单文本协议, 全称是Simple Text Oriented Messaging Protocol。STOMP使用连接,发送,订阅,断开等不同的命令进行通信。严格意义上来说STOMP并不是WebSocket的子协议,它是属于消息队列的一种协议,和AMQP/JMS一个层级。

 

SockJS

SockJS是一个java脚本库,为浏览器提供类似websocket的对象。SockJS提供跨浏览器兼容性,并支持STOMP协议与任何消息代理进行通信。SockJS的工作方式是我们需要提供URL来连接消息代理,然后让stomp客户端进行通信。

 

工程示例概览图

Spring4+WebSocket+Socket+STOMP+Jetty构建示例
            
    
    博客分类: springwebsocketstomp springwebsocketstomp 

 

所需的jar

<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.rongdu.framework</groupId>
	<artifactId>spring-websocket-stomp-demo</artifactId>
	<version>1.0</version>
	<packaging>war</packaging>
		
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>4.3.18.RELEASE</spring.version>
		<jettyVersion>9.4.9.v20180320</jettyVersion>
		<jackson.version>2.7.3</jackson.version>
	</properties>
	
	<dependencies>
		
		<dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
		 <dependency>
		    <groupId>com.fasterxml.jackson.core</groupId>
		    <artifactId>jackson-databind</artifactId>
		    <version>${jackson.version}</version>
		</dependency>

		<!-- WebSocket Tomcat插件 -->
		<!-- 
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-websocket</artifactId>
			<version>7.0.53</version>
		</dependency>
		 -->
		
		<!-- WebSocket Jetty插件 -->
		<dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-api</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-server</artifactId>
            <version>${jettyVersion}</version>
        </dependency>
        
		
		<dependency>
			<groupId>javax.websocket</groupId>
			<artifactId>javax.websocket-api</artifactId>
			<version>1.1</version>
		</dependency>
		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>${spring.version}</version>
        </dependency>
		<dependency>
            <groupId>org.java-websocket</groupId>
            <artifactId>Java-WebSocket</artifactId>
            <version>1.3.0</version>
        </dependency>
        				
		
        <!-- 页面 webjar -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
            <version>0.35</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>sockjs-client</artifactId>
            <version>1.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>stomp-websocket</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.1.0</version>
        </dependency>

		<!-- RabbitMQ -->
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>3.5.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-rabbit</artifactId>
			<version>1.4.5.RELEASE</version>
		</dependency>
		
		<!--slf4j--> 
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>2.7</version>
		</dependency>
		
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
					<encoding>utf-8</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

 

 

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee"
		 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
		 version="3.0">
	
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:/config/spring/*-beans.xml</param-value>
	</context-param>

	<!-- 编码过滤器 -->
	<filter>
		<filter-name>characterEncodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>			
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>characterEncodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
		<dispatcher>ASYNC</dispatcher>
	</filter-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>	
	
	<servlet>
		<servlet-name>springServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:/config/web/web-main.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>
	<servlet-mapping>
		<servlet-name>springServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
	
</web-app>

 

 

spring的mvc-servlet.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:task="http://www.springframework.org/schema/task"  
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:websocket="http://www.springframework.org/schema/websocket"
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.3.xsd
	http://www.springframework.org/schema/task  
    http://www.springframework.org/schema/task/spring-task-4.3.xsd
	http://www.springframework.org/schema/mvc
	http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
    http://www.springframework.org/schema/websocket
    http://www.springframework.org/schema/websocket/spring-websocket.xsd">

	<!-- 自动扫包 -->
	<context:component-scan base-package="com.huatech.controller"/>
    <!-- 提供请求分发、数据绑定、数据转换等操作 -->
	<mvc:annotation-driven/>
 	
 	<!-- 
 	application-destination-prefix 逗号分隔的前缀列表,用于匹配已处理消息的目标
	user-destination-prefix 用于标识用户目标的前缀。 不解析任何不以给定前缀开头的目的地。 默认值为:/user/
 	 -->
 	<websocket:message-broker application-destination-prefix="/app" user-destination-prefix="/queue">
        <websocket:stomp-endpoint path="/websocket-address" >
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:simple-broker prefix="/topic,/queue"/>
    </websocket:message-broker>
    
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		 <property name="prefix" value="/js/"/>		 
		 <property name="suffix" value=".jsp"/>
         <property name="contentType" value="text/html; charset=UTF-8"/>
	</bean>
 	 	
 	<!-- webjars 静态资源 -->
 	<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" >
 		<mvc:resource-chain resource-cache="false" auto-registration="false">
 			<mvc:resolvers>
 				<bean class="org.springframework.web.servlet.resource.WebJarsResourceResolver"/>
 				<bean class="org.springframework.web.servlet.resource.PathResourceResolver"/>
 			</mvc:resolvers>
 		</mvc:resource-chain>
 	</mvc:resources>
 	 	 	
 	<!-- 本地资源 -->
 	<mvc:resources mapping="/js/**" location="/js/"/>
 		
 	<!-- 默认首页 -->
	<mvc:view-controller path="/" view-name="index"/>
 	
 	<!-- scheduler配置 -->
 	<task:executor id="executor" pool-size="5" />      
    <task:scheduler id="scheduler" pool-size="5" />    
    <task:annotation-driven executor="executor" scheduler="scheduler" />
 	
 	
</beans>

 

添加控制器类

package com.huatech.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;

@Controller
public class TopicController {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(TopicController.class);
	
	@Autowired
	private SimpMessagingTemplate template;
	
    /*收到消息后广播推送*/
    @MessageMapping("/boardcast")
    @SendTo("/topic/boardcast")
    public String sendtousers(String s){
    	LOGGER.info(s);
        return "我是广播消息";
    }

    /*收到消息后精准投送到单个用户*/
    @MessageMapping("/precise")
    /*broadcast = false避免把消息推送到同一个帐号不同的session中*/
    @SendToUser(value = "/topic/precise",broadcast = false)
    public String sendtouser(String s){
    	LOGGER.info(s);
        return "我是精准投送消息";
    }


    /*下面2个是不用接收消息动态发送消息的方法*/
    /*Scheduled为定时任务,演示*/
    @Scheduled(fixedDelay = 1500)
    public void boardcast(){
        this.template.convertAndSend("/topic", "来自服务器广播消息");
    }

    @Scheduled(fixedDelay = 1500)
    public void precise(){
        this.template.convertAndSendToUser("abc", "/message","来自服务器精准投送消息");
    }

}

 

index.jsp示例页面

<%@ page language="java" pageEncoding="UTF-8"%>
<html>
  <head>
  	<meta charset="utf-8"/>
    <title>spring-websocket-stomp</title>
  </head>
  <body>
  <h1>spring-websocket-stomp</h1>
  <button id="btn-send">广播消息</button>
  <button id="btn-send2">精准投送消息</button>
  <button id="btn-send3">动态广播</button>
  <button id="btn-send4">动态精准投送</button>
  </body>
</html>
<script src="webjars/jquery/jquery.min.js"></script>
<script src="webjars/sockjs-client/sockjs.min.js"></script>
<script src="webjars/stomp-websocket/stomp.min.js"></script>
<script src="js/app.js"></script>

 

app.js

var stompClient = null;
var subscription = null;
$(function () {
   var ws = new SockJS("/websocket-address");
    stompClient = Stomp.over(ws);
    stompClient.connect({'client-id': 'my-client'},function () {
    });

    $("#btn-send").click(function () {
        if(subscription != null){subscription.unsubscribe();}
        subscription = stompClient.subscribe("/topic/boardcast", function(){});
        stompClient.send("/app/boardcast",{},"请求广播");
    });
    $("#btn-send2").click(function () {
        if(subscription != null){subscription.unsubscribe();}
        subscription = stompClient.subscribe("/queue/topic/precise", function(){});
        stompClient.send("/app/precise",{},"请求精准投送");
    });
    // 请求动态广播
    $("#btn-send3").click(function () {
        if(subscription != null){subscription.unsubscribe();}
        subscription = stompClient.subscribe("/topic", function(){});
    });
    // 请求精准投送
    $("#btn-send4").click(function () {
        if(subscription != null){subscription.unsubscribe();}
        subscription = stompClient.subscribe("/queue/abc/message", function(){});
    });
});

 

遇到的问题

1、spring引入webjars页面报404问题

参考:https://blog.csdn.net/chao_1990/article/details/53449494

 

2、Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: com.fasterxml.jackson.databind.ObjectMapper.canSerialize(Ljava/lang/Class;Ljava/util/concurrent/atomic/AtomicReference;)Z

答:jackson版本冲突

 

3、@MessageMapping注解配置后不起作用

答:websocket相关的配置需通过mvc进行初始化,而不是交给IOC

参考:

https://*.com/questions/42441837/messagemapping-does-not-work-with-spring-security-and-mvc

  • Spring4+WebSocket+Socket+STOMP+Jetty构建示例
            
    
    博客分类: springwebsocketstomp springwebsocketstomp 
  • 大小: 8.8 KB
  • Spring4+WebSocket+Socket+STOMP+Jetty构建示例
            
    
    博客分类: springwebsocketstomp springwebsocketstomp 
  • 大小: 14.2 KB