Spring Boot——WebSocket(广播式)

什么是WebSocket

WebSocket 为 浏览器 和 服务 端 提供 了 双工 异步 通信 的 功能, 即 浏览器 可以向 服务 端 发送 消息, 服务 端 也可以 向 浏览器 发送 消息。 WebSocket 需 浏览器 的 支持, 如 IE 10+、 Chrome 13+、 Firefox 6+, 这对 我们 现在 的 浏览器 来说 都不 是 问题。 WebSocket 是 通过 一个 socket 来 实现 双工 异步 通信 能力 的。 但是 直接 使用 WebSocket( 或者 SockJS: WebSocket 协议 的 模拟, 增 加了 当 浏览器 不支持 WebSocket 的 时候 的 兼容 支持) 协议 开发 程序 显得 特别 烦琐, 我们 会使 用 它的 子 协议 STOMP, 它是 一个 更高 级别 的 协议, STOMP 协议 使用 一个 基于 帧( frame) 的 格式 来 定义 消息, 与 HTTP 的 request 和 response 类似( 具有 类似于@ RequestMapping 的@ MessageMapping)。

实战源码()

新建spring Boot项目,添加Thymeleaf 和 Websocket 依赖。

新建WebSocekt配置类WebSocketConfig.java,需要@EnableWebSocketMessageBroker开启WebSocket支持,并且继承AbstractWebSocketMessageBrokerConfigurer。源码如下:

/**
 * 
 */
package com.sunshuo.springboot_websocket;

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;

/**
 * @ClassName: WebSocketConfig
 * @Description: TODO(这里用一句话描述这个类的作用)
 * @author SunShuo
 * @date 2019年3月28日
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.web.socket.config.annotation.
     * WebSocketMessageBrokerConfigurer#registerStompEndpoints(org.
     * springframework.web.socket.config.annotation.StompEndpointRegistry)
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /*
         * 注册STOPM协议的节点(endpoint),并映射的指定的URL
         */
        /*
         * 注册一个STOPM节点(endpoint),并指定使用SockJS协议
         */
        registry.addEndpoint("/endpointWisely").withSockJS();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.springframework.web.socket.config.annotation.
     * AbstractWebSocketMessageBrokerConfigurer#configureMessageBroker(org.
     * springframework.messaging.simp.config.MessageBrokerRegistry)
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /*
         * 配置消息代理
         */
        /*
         * 广播式应配置一个/topic消息代理
         */
        registry.enableSimpleBroker("/topic");
    }

}

代码解释
① 通过@ EnableWebSocketMessageBroker 注解 开启 使用 STOMP 协议 来 传输 基于 代理( message broker) 的 消息, 这时 控制器 支持 使用@ MessageMapping, 就 像 使用@ RequestMapping 一样。
② 注册 STOMP 协议 的 节点( endpoint), 并 映射 的 指定 的 URL。
③ 注册 一个 STOMP 的 endpoint, 并 指定 使用 SockJS 协议。
④ 配置 消息 代理( Message Broker)。
⑤ 广播 式 应 配置 一个/ topic 消息 代理。

新建WiselyMessage类。浏览器向服务端发送消息用该类接收封装。
源码如下:

/**
 * 
 */
package com.sunshuo.springboot_websocket.domain;

/**
 * @ClassName: WiselyMessage
 * @Description: 浏览器想服务端发送和的消息由此类接受
 * @author SunShuo
 * @date 2019年3月28日
 */
public class WiselyMessage {
    private String name;

    public String getName() {
        return name;
    }

}

新建WiselyResponse类。服务端向浏览器发送消息用该类接收。
源码如下:

/**
 * 
 */
package com.sunshuo.springboot_websocket.domain;

/**
 * @ClassName: WiselyResponse
 * @Description: 服务端向浏览器发送消息
 * @author SunShuo
 * @date 2019年3月28日
 */
public class WiselyResponse {
    private String responseMessage;

    public WiselyResponse(String responseMessage) {
        this.responseMessage = responseMessage;
    }

    public String getResponseMessage() {
        return responseMessage;
    }

}

新建一个WsController类当控制器。

/**
 * 
 */
package com.sunshuo.springboot_websocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import com.sunshuo.springboot_websocket.domain.WiselyMessage;
import com.sunshuo.springboot_websocket.domain.WiselyResponse;

/**
 * @ClassName: WsController
 * @Description: TODO(这里用一句话描述这个类的作用)
 * @author SunShuo
 * @date 2019年3月28日
 */
@Controller
public class WsController {
    @MessageMapping("/welcome")
    @SendTo("/topic/getResponse")
    public WiselyResponse say(WiselyMessage wiselyMessage) throws Exception {
        Thread.sleep(3000);
        return new WiselyResponse("Welcome " + wiselyMessage.getName() + "!");
    }
}

代码 解释
① 当 浏览器 向 服务 端 发送 请求 时, 通过@ MessageMapping 映射/ welcome 这个 地址, 类似于@ RequestMapping。
② 当 服务 端 有 消息 时, 会对 订阅 了@ SendTo 中的 路径 的 浏览器 发送 消息。 .

新建一个html页面用于演示,需要引入stomp. min. js( STOMP 协议 的 客户 端 脚本)、 sockjs. min. js( SockJS 的 客户 端 脚本) 以及 jQuery脚本,将脚本放在src/ main/ resources/ static下。
在src/ main/ resources/ templates 下新建ws. html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>Insert title here</title>
</head>

<body "disconnect()">
<noscript><h2 style="color:#FF0000">浏览器不支持websocket</h2></noscript>
<div>
    <div>
        <button id="connect" "connect();">连接</button>
        <button id="disconnect" disabled="disabled" "disconnect();">断开链接</button>
    </div>
    <div id="conversationDiv">
        <label>输入你的名字</label><input type="text" id="name" />
        <button id="sendName" "sendName();">发送</button>
        <p id="response"></p>
    </div>
</div>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{jquery.min.js}"></script>
<script type="text/javascript">
var stopmClient = null;

function setConnected(connected) {
	document.getElementById('connect').disabled = connected;
	document.getElementById('disconnect').disabled = !connected;
	document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
	$('#response').html();
}

function connect() {
	var socket = new SockJS('/endpointWisely');
	stopmClient = Stomp.over(socket);
	stopmClient.connect({}, function(frame) {
		setConnected(true);
		console.log('Connected: ' + frame);
		stopmClient.subscribe('/topic/getResponse', function(response) {
			showResponse(JSON.parse(response.body).responseMessage);
		});
	});
}

function disconnect() {
	if(stopmClient != null) {
		stopmClient.disconnect();
	}
	setConnected(false);
	console.log('disconnected. ');
}

function sendName() {
	var name = $('#name').val();
	stopmClient.send("/welcome", {}, JSON.stringify({'name': name}));
}

function showResponse(message) {
	var response = $('#response');
	response.html(message);
}
</script>
</body>
</html>

代码 解释
① 连接 SockJS 的 endpoint 名称 为“/ endpointWisely”。
② 使用 STOMP 子 协议 的 WebSocket 客户 端。
③ 连接 WebSocket 服务 端。
④ 通过 stompClient. subscribe 订阅/ topic/ getResponse 目标( destination) 发送 的 消息, 这个 是在 控制器 的@ SendTo 中 定义 的。
⑤ 通过 stompClient. send 向/ welcome 目标( destination) 发送 消息, 这个 是在 控制器 的@ MessageMapping 中 定义 的。

配置 viewController, 为 ws. html 提供 便捷 的 路径 映射:
源码如下:

/**
 * 
 */
package com.sunshuo.springboot_websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * @ClassName: WebMvcConfig
 * @Description: TODO(这里用一句话描述这个类的作用)
 * @author SunShuo
 * @date 2019年3月28日
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    /*
     * (non-Javadoc)
     * 
     * @see
     * org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
     * #addViewControllers(org.springframework.web.servlet.config.annotation.
     * ViewControllerRegistry)
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/ws").setViewName("/ws");
    }
}

运行项目:
在浏览器中输入:http://localhost:8080/ws,打开3个界面,分别链接。然后在一个浏览器中输入消息并且发送,其他的浏览器接收消息。
Spring Boot——WebSocket(广播式)
其消息发送的格式为:
Spring Boot——WebSocket(广播式)