http协议是无状态协议,即每次请求时都不知道前面发生的什么。并且请求只能由浏览器发起,服务器只能响应该请求,不能主动发送消息给浏览器。这种单向的协议显然在不少场景下是不适用的,好比消息推送,股票实时行情。在websocket以前,咱们一般使用Ajax轮询服务器或者使用长轮询,这两种方式都极大消耗了服务端和客户端的资源。而使用websocket,咱们只须要借用http协议进行握手,而后保持着一个websocket链接,知道客户端主动断开。相对于另外两种方式,websocket只发送了一次http请求,当服务器有数据时再向浏览器推送数据,减小了带宽的使用以及服务器CPU使用率。css
HTTP、WebSocket 等应用层协议,都是基于 TCP 协议来传输数据的。
对于 WebSocket 来讲,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。
Socket并非一种协议,而是方便咱们使用TCP/IP的一种封装,而 WebSocket 则不一样,它是一个完整的 应用层协议,包含一套标准的 API 。html
STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操做的链接格式,容许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议
许多公司都提供了基于STOMP的服务器与客户端,若spring4开始支持的spring-websocket服务端,基于浏览器的stomp.js客户端
STOMP定义了客户端和服务器之间以Frame进行同行,Frame的格式为:java
COMMAND
header1:value1
header2:value2
Body^@
COMMAND分为CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT这几种。
COMMAND以后下一行紧跟着的是头部的键值对,以后加入一条空行,空行以后为body,即传递的消息实体。jquery
传统HTPP请求响应:git
websocket请求响应:github
4.Spring配置stompweb
环境配置:Spring4.3.9+tomcat8+jackson2.8.2spring
tomacat要8才支持stomp,spring4.0+之后支持stomp,而跨域问题能够经过下面代码解决,可是在spring4.0.9却没有setAllowedOrigins("*")这个函数,因此我选择4.3.9版本registry.addEndpoint("/endpoint").setAllowedOrigins("*").withSockJS(); 编程
而jackson版本太低的话和spring4.3.9不兼容,jackson2.8.2版本没有问题,项目所需类库下载地址:https://download.csdn.net/download/fxkcsdn/10536671。json
若是没有jackson-core,jackson-databind,jackson-annotations这三个类,则浏览器链接会出现下面状况:
ok,终于要步入正题了: Spring配置stomp
下面开始咱们的编程之旅:
第一步:编写实体类:
import java.io.Serializable; public class JinNang implements Serializable{ private static final long serialVersionUID = 1L; private String jice;//计策 private String people;//计策实施者 public JinNang(String jice,String people){ this.jice=jice; this.people=people; } public String getJice() { return jice; } public void setJice(String jice) { this.jice = jice; } public String getPeople() { return people; } public void setPeople(String people) { this.people = people; } }
People.java
public class People { private String name; public People(){} public People(String name){ this.name=name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
这两个实体类要有无参构造函数,待会的消息转换器会使用java映射来将浏览器发送的json数据转换为实体类。
第二步:配置启用代理的web消息功能:
以下的程序展现了如何经过java配置启用基于代理的Web消息功能:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; 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.messaging.simp.SimpMessagingTemplate; @Configuration @ComponentScan @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic","/queue"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/endpoint").setAllowedOrigins("*").withSockJS(); } }
WebSocketStompConfig 使用了 @EnableWebSocketMessageBroker 注解。这代表这个配置类不只配置了 WebSocket ,还配置了基于代理的 STOMP 消息。它重载了 registerStompEndpoints() 方法,将 “/endpoint” 注册为 STOMP 端点。这个路径与以前发送和接收消息的目的地路径有所不一样。这是一个端点,客户端在订阅或发布消息到目的地路径前,要链接该端点。WebSocketStompConfig 还经过重载 configureMessageBroker() 方法配置了一个简单的消息代理。这个方法是可选的,若是不重载它的话,将会自动配置一个简单的内存消息代理,用它来处理以 “/topic” 为前缀的消息。可是在本例中,咱们重载了这个方法,因此消息代理将会处理前缀为 “/topic” 和 “/queue” 的消息。除此以外,发往应用程序的消息将会带有 “/app” 前缀。书上这个截图展现了配置中的消息流:
当消息到达时,目的地的前缀将会决定消息该如何处理。在图 18.2 中,应用程序的目的地以 “/app” 做为前缀,而代理的目的地以 “/topic” 和 “/queue” 做为前缀。以应用程序为目的地的消息将会直接路由到带有 @MessageMapping 注解的控制器方法中。而发送到代理上的消息,其中也包括 @MessageMapping 注解方法的返回值所造成的消息,将会路由到代理上,并最终发送到订阅
这些目的地的客户端.
第三步:构造控制器
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 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.stereotype.Controller; @Controller public class ShuguoController { // public SimpMessagingTemplate template; // // @Autowired // public ShuguoController(SimpMessagingTemplate template) { // this.template = template; // } @MessageMapping("/jinnang") @SendTo("/topic/jinnang") public JinNang getJinNang(People people) throws Exception { System.out.println("people:"+people.getName()); return new JinNang("火烧赤壁",people.getName()); } }
咱们在控制器方法添加@MessageMapping注解,使其处理STOMP消息,他与带有@RequestMapping注解的方法处理HTTP请求的方式很是相似。可是。与@RequestMapping不一样的是,@MessageMapping的功能没法经过@EnableWebMvc启用。Spring的Web消息功能基于消息代理构建,所以除了告诉Spring咱们想要处理的消息之外,还有其余的内容须要配置。咱们必需要配置一个消息代理和其余的一些消息目的地。
可是这个处理器方法与咱们以前看到的有一点区别。 getJinnang() 方法没有使用 @RequestMapping 注解,而是使用了 @MessageMapping 注解。这表示 getJinnang()方法可以处理指定目的地上到达的消息。在本例中,这个目的地也就是 “/app/jingnang” ( “/app” 前缀是隐含的,由于咱们将其配置为应用的目的地前缀)。由于 getJinnang()方法接收一个 Jinnang参数,因此 Spring 的某一个消息转换器会将 STOMP 消息的负载转换为 JinNang对象。
由于咱们如今处理的不是HTTP,因此没法使用Spring的HttpMessageConverter实现将负载转换为JinNang对象。Spring4.0提供了几个消息转换器,做为API的一部分。下图描述了这些消息转换器,在处理STOMP消息的时候可能会用到他们。
假设getJinnang()方法所处理消息的内容类型为“application/json”(这是一个安全的假设,由于JinNang不是byte[]和String),MappingJackson2MessageConverter会负责将JSON消息转换为JinNang对象。就像在HTTP中对应的MappingJackson2HttpMessageConverter同样。MappingJackson2MessageConverter会将其任务委托给底层的Jackson2 JSON处理器。默认状况下,Jackson会使用反射将JSON属性映射为Java对象的属性。
第四步:编写客户端代码:
<!DOCTYPE html> <html> <head> <title></title> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta name="format-detection" content="telephone=no"/> <meta name="format-detection" content="email=no"/> <meta http-equiv="Cache-Control" content="no-cache"/> <meta http-equiv="Pragma" content="no-cache"/> <meta http-equiv="Expires" content="0"/> <!--必须导入的三个脚本文件 --> <script src="http://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script src="http://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.js"></script> <script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script> </head> <body class="test"> <script> //定义stomp链接服务器的地址:只须要一次HTTP握手就能够进行链接。整个通信过程是创建在一次链接/状态中,也就避免了HTTP的非状态性 var url = 'http://localhost:8080/SpringTest/endpoint' var socket = new SockJS(url, undefined, {transports: ['websocket']}); //新建stomp客户端 var stompClient = Stomp.over(socket); //stomp请求与服务器创建链接 connet({},function(),function())第一个参数Map是请求的头信息,第二个参数是请求成功回调函数,第三个函数是请求失败回调函数 stompClient.connect({}, function(frame) { console.log("connected------------"); stompClient.subscribe("/topic/jinnang", handleJinNang); sendMessage(); }, function(error){ console.log(error.headers.message) } ); function handleJinNang(result){ var jinnang=JSON.parse(result.body); console.log("received:",jinnang); document.getElementById("display").value=jinnang.people+"实施"+jinnang.jice; } function sendMessage(){ var message=JSON.stringify({"name":"黄盖"}) stompClient.send("/app/jinnang",{},message); } </script> <input type="text" id="display"/> </body> </html>
send()方法传递的第二个参数是一个头信息的Map,他会包含在STOMP的帧中,不过在这个例子中,咱们没有提供任何参数,Map是空的。
运行截图: