Spring Boot的WebSocket

WebSocket

什么是WebSocket?

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

实战

准备

新建项目,选择thymeleaf和websocket;

广播式

广播式即服务端有消息时,会将消息发送给所有连接了当前endpoint的浏览器;
WebSocketConfig.java

@Configuration
@EnableWebSocketMessageBroker  //开启使用STOMP协议来传输基于代理的消息
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpointWisely").withSockJS(); 

    }


    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //表明在topic、queue、app这三个域上可以向客户端发消息。
        registry.enableSimpleBroker("/queue","/topic","app");
        //客户端向服务端发起请求时,需要以/app为前缀。
        registry.setApplicationDestinationPrefixes("/app");
    }

}

WebSocketConfig 重载了registerStompEndpoints()方法,将/endpointWisely注册为STOMP端点。这个路径与接收和发送消息的目的地路径有所不同。这是一个端点,客户端在订阅或发布消息到目的地前,要连接该端点。

浏览器向服务器端发送的消息用此类接受:

public class WiselyMessage {
    private String name;

    public String getName(){
        return name;
    }
}

服务端向浏览器发送的消息用此类接受:

public class WiselyResponse {
    private String responseMessage;
    public WiselyResponse(String responseMessage){
        this.responseMessage = responseMessage;
    }
    public String getResponseMessage(){
        return responseMessage;
    }
}

控制器代码:

    @MessageMapping("/welcome")
    @SendTo("/topic/getResponse")
    public WiselyResponse say(WiselyMessage message) throws Exception {

        return new WiselyResponse("Welcome, " + message.getName() + "!");
    }

浏览器端演示页面ws.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Spring Boot+WebSocket+广播式</title>

</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2></noscript>
<div>
    <div>
        <button id="connect" onclick="connect();">连接</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
    </div>
    <div id="conversationDiv">
        <label>输入你的名字</label><input type="text" id="name" />
        <button id="sendName" onclick="sendName();">发送</button>
        <p id="response"></p>
    </div>
</div>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>
<script type="text/javascript">
    var stompClient = 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'); //连接端点
        stompClient = Stomp.over(socket);
        stompClient.connect({}, function(frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            stompClient.subscribe('/topic/getResponse', function(respnose){ //订阅/topic/getResponse目标发送的消息
                showResponse(JSON.parse(respnose.body).responseMessage);
            });
        });
    }


    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }

    function sendName() {
        var name = $('#name').val();
        //发送消息,加上app前缀
        stompClient.send("/app/welcome", {}, JSON.stringify({ 'name': name }));
    }

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

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

@Configuration
public class WebMvcConfig  extends WebMvcConfigurerAdapter{ 
     @Override
       public void addViewControllers(ViewControllerRegistry registry) {
           registry.addViewController("/ws").setViewName("/ws");
       }

}

这里写图片描述

点对点式

广播式有自己的应用场景,但是广播式不能解决我们一个常见的场景,即消息由谁发送、由谁接收的问题。
实战:简单聊天室
引入security依赖

扫描二维码关注公众号,回复: 2440435 查看本文章

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>

WebSecurityConfig.java

@Configuration
@EnableWebSecurity  
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/","/login").permitAll()//根路径和/login路径不拦截
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login") //登陆页面
                .defaultSuccessUrl("/chat") //登陆成功转向该页面
                .permitAll()
                .and()
                .logout()
                .permitAll();
    }

    //内存中配置两个用户,角色是USER;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                .withUser("wyf").password("wyf").roles("USER")
                .and()
                .withUser("wisely").password("wisely").roles("USER");
    }
    //5忽略静态资源的拦截
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/static/**");
    }

}

WebSocketConfig.java加上:

 registry.addEndpoint("/endpointChat").withSockJS();

控制器代码:

@Autowired
    private SimpMessagingTemplate messagingTemplate;//通过SimpMessagingTemplate向浏览器发送消息

    @MessageMapping("/chat")
    public void handleChat(Principal principal, String msg) { //principal参数中包含当前用户的信息
        if (principal.getName().equals("wyf")) {//如果发送人是wyf,则发送给wisely。如果发送人是wisely,则发送给wfy。
            messagingTemplate.convertAndSendToUser("wisely",
                    "/queue/notifications", principal.getName() + "-send:"
                            + msg);
        } else {
            messagingTemplate.convertAndSendToUser("wyf",
                    "/queue/notifications", principal.getName() + "-send:"
                            + msg);
        //messagingTemplate.convertAndSendToUser向用户发送信息,第一个参数是接收消息的用户,第二个是
        //浏览器订阅的地址,第三个是消息本身。                    
        }
    }

登录页面login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<meta charset="UTF-8" />
<head>
    <title>登陆页面</title>
</head>
<body>
<div th:if="${param.error}">
    无效的账号和密码
</div>
<div th:if="${param.logout}">
    你已注销
</div>
<form th:action="@{/login}" method="post">
    <div><label> 账号 : <input type="text" name="username"/> </label></div>
    <div><label> 密码: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="登陆"/></div>
</form>
</body>
</html>

聊天页面chat.html

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8" />
<head>
    <title>Home</title>
    <script th:src="@{sockjs.min.js}"></script>
    <script th:src="@{stomp.min.js}"></script>
    <script th:src="@{jquery.js}"></script>
</head>
<body>
<p>
    聊天室
</p>

<form id="wiselyForm">
    <textarea rows="4" cols="60" name="text"></textarea>
    <input type="submit"/>
</form>

<script th:inline="javascript">
    $('#wiselyForm').submit(function(e){
        e.preventDefault();
        var text = $('#wiselyForm').find('textarea[name="text"]').val();
        sendSpittle(text);
    });

    var sock = new SockJS("/endpointChat"); //连接端点
    var stomp = Stomp.over(sock);
    stomp.connect('guest', 'guest', function(frame) {
        stomp.subscribe("/user/queue/notifications", handleNotification);//订阅/user/queue/notifications发送的消息
        //与控制器中convertAndSendToUser中定义的订阅地址一致。并且这个/user是必须的,使用了/user才会发送消息到指定用户
    });



    function handleNotification(message) {
        $('#output').append("<b>Received: " + message.body + "</b><br/>")
    }

    function sendSpittle(text) {
        stomp.send("/app/chat", {}, text);//如果你设置了客户端向服务端发起请求时的需要加的前缀的话,那么你这里就要加上你设置的前缀。
    }
    $('#stop').click(function() {sock.close()});
</script>

<div id="output"></div>
</body>
</html>

配置viewController,为.html提供便捷的路径映射:

@Configuration
public class WebMvcConfig  extends WebMvcConfigurerAdapter{

     @Override
       public void addViewControllers(ViewControllerRegistry registry) {
           registry.addViewController("/ws").setViewName("/ws");
          registry.addViewController("/login").setViewName("/login");
           registry.addViewController("/chat").setViewName("/chat");

       }

}

登录两个用户发送消息:
这里写图片描述
这里写图片描述
本篇单单讲了个例子,如果对security不了解的话可以看我的另外两篇”Spring Security”,
对WebSocket有疑问可以看我另外一篇” Spring使用WebSocket、SockJS、STOMP实现消息功能”。

参考书籍:Spring Boot 实战
以上只是学习所做的笔记, 以供日后参考。如有错误请指正,谢谢啦!!!

猜你喜欢

转载自blog.csdn.net/z1790424577/article/details/81110658