WebSocket+SockJs+STMOP
来源:互联网 发布:域名反向解析作用 编辑:程序博客网 时间:2024/05/29 02:37
- 应用场景
- WebSocket
- 1 编写Handler类
- 2 拦截器的实现
- 3 WebSocketConfig配置
- 4 客户端配置
- 5 Bad Code
- 6 nginx配置
- SockJs
- 1 WebSocketConfig配置
- 客户端配置
- STOMP
- 1 WebSocketSockJsSTOMP三者关系
- 2 STOMP
- 3 WebSockConfig配置
- 4 Controller
- 5 客户端代码
- 6 获取用户信息定向推送
- 61 MyHandsHandler
- 62 注册MyHandsHandler
- 63 Controller
- 7 其他Annotation说明
- 71DestinationVariable
- WebSocket
应用场景
websocket 是 Html5 新增加特性之一,目的是浏览器与服务端建立全双工的通信方式,解决 http 请求-响应带来过多的资源消耗,同时对特殊场景应用提供了全新的实现方式,比如聊天、股票交易、游戏等对对实时性要求较高的行业领域。
1.WebSocket
WebSocket 是发送和接收消息的底层API,WebSocket 协议提供了通过一个套接字实现全双工通信的功能。也能够实现 web 浏览器和 server 间的异步通信,全双工意味着 server 与浏览器间可以发送和接收消息。需要注意的是必须考虑浏览器是否支持,浏览器的支持情况如下:
1.1 编写Handler类
方法一:实现 WebSocketHandler 接口,WebSocketHandler 接口如下
public interface WebSocketHandler { void afterConnectionEstablished(WebSocketSession session) throws Exception; void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception; void handleTransportError(WebSocketSession session, Throwable exception) throws Exception; void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception; boolean supportsPartialMessages();}
方法二:扩展 AbstractWebSocketHandler
@Servicepublic class ChatHandler extends AbstractWebSocketHandler { @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { session.sendMessage(new TextMessage("hello world.")); }}
该类中的方法我们都可以按需求重载
1.2 拦截器的实现
@Componentpublic class WebSocketHandshakeInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { if (request instanceof ServletServerHttpRequest) { attributes.put("username",userName); } return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { }}
- beforeHandshake()方法,在调用 handler 前调用。常用来注册用户信息,绑定 WebSocketSession,在 handler 里根据用户信息获取WebSocketSession发送消息
1.3 WebSocketConfig配置
@Configuration@EnableWebSocketpublic class WebSocketConfig implements WebSocketConfigurer{ @Autowired private ChatHandler chatHandler; @Autowired private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(chatHandler,"/chat") .addInterceptors(webSocketHandshakeInterceptor); } @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192*4); container.setMaxBinaryMessageBufferSize(8192*4); return container; }}
- 实现 WebSocketConfigurer 接口,重写 registerWebSocketHandlers 方法,这是一个核心实现方法,配置 websocket 入口,允许访问的域、注册 Handler、SockJs 支持和拦截器。
- registry.addHandler()注册和路由的功能,当客户端发起 websocket 连接,把 /path 交给对应的 handler 处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。
- addInterceptors,顾名思义就是为 handler 添加拦截器,可以在调用 handler 前后加入我们自己的逻辑代码。
- ServletServerContainerFactoryBean可以添加对WebSocket的一些配置
1.4 客户端配置
var wsServer = 'ws://127.0.0.1:8080/chat'; var websocket = new WebSocket(wsServer); websocket.onopen = function (evt) { onOpen(evt) }; websocket.onclose = function (evt) { onClose(evt) }; websocket.onmessage = function (evt) { onMessage(evt) }; websocket.onerror = function (evt) { onError(evt) }; function onOpen(evt) { console.log("Connected to WebSocket server."); } function onClose(evt) { console.log("Disconnected"); } function onMessage(evt) { console.log('Retrieved data from server: ' + evt.data); } function onError(evt) { console.log('Error occured: ' + evt.data); }websocket.send(“test”);//客户端向服务器发送消息
注意:
其中 wsServer = ‘ws://127.0.0.1:8080/chat’中的地址要根据自己的实际情况来定,一般形式为:ws://域名:端口/应用路径/WebSocket 配置的 path。“应用路径”是应用部署在 tomcat 中的文件夹路径,“WebSocket 配置的 path”是配置文件中这条配置项配置的路径。
后台输出:
- Connection established- getId:1- getLocalAddress:/127.0.0.1:8080- getUri:/chat
1.5 Bad Code
- 1006
nginx配置添加
proxy_connect_timeout 500s; proxy_read_timeout 500s; proxy_send_timeout 6s;
- 1009
内容长度超限,将MaxTextMessageBufferSize增大
1.6 nginx配置
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
2. SockJs
为了应对许多浏览器不支持WebSocket协议的问题,设计了备选SockJs
。
SockJS 是 WebSocket 技术的一种模拟。SockJS 会 尽可能对应 WebSocket API,但如果 WebSocket 技术不可用的话,就会选择另外的通信方式协议。
2.1 WebSocketConfig配置
@Configuration@EnableWebSocketpublic class WebSocketConfig implements WebSocketConfigurer{ @Autowired private ChatHandler chatHandler; @Autowired private WebSocketHandshakeInterceptor webSocketHandshakeInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // withSockJS() 方法声明我们想要使用 SockJS 功能,如果WebSocket不可用的话,会使用 SockJS; registry.addHandler(chatHandler,"/chat").addInterceptors(webSocketHandshakeInterceptor).withSockJS(); }}
客户端配置
具体做法是 依赖于 JavaScript 模块加载器(如 require.js or curl.js) 还是简单使用 script 标签加载 JavaScript 库。最简单的方法是 使用 script 标签从 SockJS CDN 中进行加载,如下所示:
//加载sockjs<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>var url = '/chat';var sock = new SockJS(url);//.....
对以上代码分析
- SockJS 所处理的 URL 是 “http://“ 或 “https://“ 模式,而不是 “ws://“ or “wss://“;
- 其他的函数如 onopen, onmessage, and onclose ,SockJS 客户端与 WebSocket 一样,在此代码省略
后台输出:
- Connection established- getId: qtfwdtti**(注意:此处和直接利用websocket API有区别)**- getLocalAddress:/127.0.0.1:8080- getUri: /chat/668/qtfwdtti/websocket**(注意:此处和直接利用websocket API有区别)**
3.STOMP
SockJS 为 WebSocket 提供了 备选方案。但无论哪种场景,对于实际应用来说,这种通信形式层级过低。下面看一下如何 在 WebSocket 之上使用 STOMP协议,来为浏览器 和 server 间的 通信增加适当的消息语义。(STOMP—— Simple Text Oriented Message Protocol——面向消息的简单文本协议)
3.1 WebSocket、SockJs、STOMP三者关系
简而言之,WebSocket 是底层协议,SockJS 是WebSocket 的备选方案,也是 底层协议,而 STOMP 是基于 WebSocket(SockJS) 的上层协议
- 假设HTTP协议并不存在,只能使用TCP套接字来编写web应用,你可能认为这是一件疯狂的事情。
- 不过幸好,我们有HTTP协议,它解决了 web 浏览器发起请求以及 web 服务器响应请求的细节。
- 直接使用 WebSocket(SockJS) 就很类似于 使用 TCP 套接字来编写 web 应用;因为没有高层协议,因此就需要我们定义应用间所发送消息的语义,还需要确保 连接的两端都能遵循这些语义。
- 同HTTP在TCP套接字上添加请求-响应模型层一样,STOMP在 WebSocket之上提供了一个基于帧的线路格式层,用来定义消息语义。
3.2 STOMP
STOMP帧由命令,一个或多个头信息以及负载所组成。如下就是发送数据的一个STOMP帧:
SENDdestination:/app/room-messagecontent-length:20{\"message\":\"Hello!\"}
对以上代码分析:
- SEND:STOMP命令,表明会发送一些内容;
- destination:头信息,用来表示消息发送到哪里;
- content-length:头信息,用来表示 负载内容的 大小;
- 空行;
- 帧内容(负载)内容
3.3 WebSockConfig配置
@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息 config.enableSimpleBroker("/topic"); //定义了服务端接收地址的前缀,也即客户端给服务端发消息的地址前缀 config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //添加一个/chat端点,客户端就可以通过这个端点来进行连接;withSockJS作用是添加SockJS支持 registry.addEndpoint("/gs-guide-websocket")).withSockJS(); }}
对以上代码分析:
- EnableWebSocketMessageBroker 注解表明: 这个配置类不仅配置了 WebSocket,还配置了基于代理的 STOMP 消息;
- 它复写了 registerStompEndpoints() 方法:添加一个服务端点,来接收客户端的连接。将 “/gs-guide-websocket
- ” 路径注册为 STOMP 端点。这个路径与之前发送和接收消息的目的路径有所不同, 这是一个端点,客户端在订阅或发布消息到目的地址前,要连接该端点,即用户发送请求 :url=’/127.0.0.1:8080/gs-guide-websocket
- ’ 与 STOMP server 进行连接,之后再转发到订阅url;
- 它复写了 configureMessageBroker() 方法:配置了一个 简单的消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。
- 发送应用程序的消息将会带有 “/app” 前缀。
3.4 Controller
@Controllerpublic class GreetingController { @Autowired private SimpMessagingTemplate template; @MessageMapping("/hello") @SendToUser("/topic/greetings") //@SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay return new Greeting("Hello, " + message.getName() + "!"); }}
对以上代码分析
- @MessageMapping 标识客户端发来消息的请求地址,前面我们全局配置中制定了服务端接收的地址以“/app”开头,所以客户端发送消息的请求连接是:/app/hello;
- @SendToUser可以将消息只返回给发送者
- @SendTo会将消息广播给所有订阅
/hello
这个路径的用户。
3.5 客户端代码
function connect() { var socket = new SockJS('/gs-guide-websocket'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/user/topic/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); });}
3.6 获取用户信息,定向推送
虽然STMOP的协议很好的实现了订阅,返回的模式,但是没法定向的从服务端推送消息个某个用户,要解决这个问题就需要获取用户的信息,使得我们可以对其推送。
3.6.1 MyHandsHandler
public class MyHandsHandler extends DefaultHandshakeHandler { public MyHandsHandler() { } public MyHandsHandler(RequestUpgradeStrategy requestUpgradeStrategy) { super(requestUpgradeStrategy); } @Override protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) { URI uri = request.getURI(); String id = null; if (uri != null) { String query = uri.getPath(); String[] pairs = query.split("/"); id = pairs[3]; } MyPrincipal principal = new MyPrincipal(); principal.setName(id); System.out.println(id); return principal; } class MyPrincipal implements Principal { private String name; @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyPrincipal that = (MyPrincipal) o; return name != null ? name.equals(that.name) : that.name == null; } @Override public int hashCode() { return name != null ? name.hashCode() : 0; } }}
继承DefaultHandshakeHandler,重写其determineUser方法,根据需要自定义Principal的返回值,其name就是用来标记返回对象的id。更进一步可以用一个List来维护用户的登陆状态等。
3.6.2 注册MyHandsHandler
@Configuration@EnableWebSocketMessageBrokerpublic class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { //定义了一个客户端订阅地址的前缀信息,也就是客户端接收服务端发送消息的前缀信息 config.enableSimpleBroker("/topic"); //定义了服务端接收地址的前缀,也即客户端给服务端发消息的地址前缀 config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { //添加一个/chat端点,客户端就可以通过这个端点来进行连接;withSockJS作用是添加SockJS支持 registry.addEndpoint("/gs-guide-websocket").setHandshakeHandler(new MyHandsHandler()).withSockJS(); }}
3.6.3 Controller
@Controllerpublic class GreetingController { @Autowired private SimpMessagingTemplate template; @RequestMapping("/xxx") public String greetingx(String id) throws Exception { template.convertAndSendToUser(id, "/topic/greetings", new Greeting("Hello, " + id + "!")); return "success"; }}
以上代码分析
- 通过依赖注入
SimpMessagingTemplate
我们可以在服务端的任何地方发送消息 - template.convertAndSendToUser(id, “/topic/greetings”, new Greeting(“Hello, ” + id + “!”))可以将消息发送给我们指定id的用户,此处的id就是Principal中的name值
3.7 其他Annotation说明
3.7.1@DestinationVariable
@MessageMapping("/hello/{roomId}") public void roomMessage(HelloMessage message, @DestinationVariable String roomId){ String dest = "/topic/" + roomId + "/" + "greetings"; this.template.convertAndSend(dest, message); }
- @DestinationVariable 用以取出请求地址中的房间 id 参数 roomId;
参考文献:
- http://blog.csdn.net/pacosonswjtu/article/details/51914567
- http://tech.lede.com/2017/03/08/qa/websocket+spring/
- WebSocket+SockJs+STMOP
- WebSocket-SockJS
- WebSocket+STOMP+SockJS
- Spring+WebSocket+SockJS实现实时聊天
- springboot+websocket+sockjs进行消息推送
- WebSocket connection to 'ws://.../sockjs/pushWords/635/78epgvv8/websocket'
- AngularJs + sockjs + stomp 报错websocket连接失败
- 基于spring websocket+sockjs实现的长连接请求
- WebSocket spring示例demo (已使用sockJs库)
- Spring WebSocket+SockJS+Stomp实现一对一、一对多通信
- 基于Spring Websocket+SockJS实现的长连接请求
- spring boot+quartz+websocket+sockJs实现定时发送
- Spring WebSocket+SockJS+Stomp实现一对一、一对多通信
- Using Spring 4 WebSocket, sockJS and Stomp support to implement two way server client communication
- 使用maven搭建XML风格的Spring MVC+WebSocket+sockjs+STOMP+ActiveMQ应用
- tomcat 7下spring 4.x mvc集成websocket以及sockjs完全参考指南
- spring+stomp+webSocket+SockJS 实现简单的订阅广播消息和订阅个人消息
- 使用maven搭建XML风格的Spring MVC+WebSocket+sockjs+STOMP+ActiveMQ应用
- 8种对话框(Dialog)使用方法汇总
- 图像插值算法--最邻近、双线性、双三次插值
- ABP官方文档(四)【启动配置】
- Jquery append 方法在ie8不兼容问题
- gulp常见插件总结
- WebSocket+SockJs+STMOP
- ZXing 生成二维码和条形码
- jQuery
- Vue 创建项目
- UVA
- Spring配置过程 (二)面向切面编程AOP
- 关于 Spring 中 Autowired 注入接口的几个问题
- linux学习笔记一:在linux编写及调试c语言
- Oracle数据库:分区,重组,性能优化。