SpringBoot整合WebSocket案例

WebSocket简介

WebSocket协议是基于TCP的一种网络协议。它实现了浏览器与服务器全双工通信——允许服务器主动发送信息给客户端。
WebSocket是通过一个socket来实现双工异步通信的。直接使用WebSocket或者SockJS协议显得特别繁琐。使用它的子协议STOMP,它是一个更高级别的协议,STMOP协议使用一个基于帧格式来定义消息,与HTTP的Request和Response类似。
SpringBoot对使用WebSocket提供了支持,配置源码在org.springframework.boot.autoconfigure.websocket包下。
案例中使用到的maven依赖:

<!--springBoot其它依赖省略-->
<!-- webSocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
 <!-- thymeleaf模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- spring security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

广播模式

广播式即服务端有消息时,会将信息发送给所有连接了当前endpoint的浏览器。
1、 写消息类

    /**
     * 浏览器向服务端发送的消息
     */
    public class AricMessage {

        private String name;

        public String getName() {
            return name;
        }

    }

    /**
     * 服务端向浏览器发送的消息
     */
    public class AricResponse {

        private String responseMessage;

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

        public String getResponseMessage() {
            return responseMessage;
        }

    }

2、配置websocket

    /**
     * 配置WebSocket
     */
    @Configuration
    //注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {//注册STOMP协议的节点(endpoint),并映射指定的url
            //注册一个STOMP的endpoint,并指定使用SockJS协议
            registry.addEndpoint("/endpointAric").withSockJS();

        }

        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(Message Broker)
            //广播式应配置一个/topic消息代理
            registry.enableSimpleBroker("/topic");

        }
    }

3、写Controller

    /**
     * webSocket控制器
     */
    @Controller
    public class WebSocketController {

        @MessageMapping("/welcome") //当浏览器向服务端发送请求时,通过@MessageMapping映射/welcome这个地址,类似于@ResponseMapping
        @SendTo("/topic/getResponse")//当服务器有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
        public AricResponse say(AricMessage message) {
            try {
                //睡眠1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return new AricResponse("welcome," + message.getName() + "!");
        }
    }

4、在src\main\resource\templates目录下编写webSocket.html,使用thymeleaf模板引擎

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8" />
    <title>SpringBoot实现广播式WebSocket</title>
        <script th:src="@{sockjs.min.js}"></script>
        <script th:src="@{stomp.min.js}"></script>
        <script th:src="@{jquery-1.11.3.min.js}"></script>
    </head>
    <body>
        <noscript><h2 style="color:#ff0000">抱歉,您的浏览器不支持改功能!</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>
    </body>
    <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('/endpointAric'); //连接SockJS的endpoint名称为"endpointWisely"
            stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
            stompClient.connect({},function(frame){//连接WebSocket服务端
                setConnected(true);
                console.log('Connected:' + frame);
                //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息,这个是在控制器的@SentTo中定义的
                stompClient.subscribe('/topic/getResponse',function(response){
                    showResponse(JSON.parse(response.body).responseMessage);
                });
            });
        }

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

        function sendName(){
            var name = $("#name").val();
            //通过stompClient.send向/welcome 目标(destination)发送消息,这个是在控制器的@MessageMapping中定义的
            stompClient.send("/welcome",{},JSON.stringify({'name':name}));
        }

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

5、配置viewController,为webSocket.html提供路径映射。

    /**
     * 配置viewController,为页面提供路径映射
     */
    @Controller
    public class WebMvcConfig extends WebMvcConfigurerAdapter{

        /**
         * 配置viewController,提供映射路径
         */
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/webSocket").setViewName("/webSocket");
        }
    }

6、打开多个浏览器,访问地址,其中一个发送消息,其它浏览器能接收消息。

点对点式

点对点方式解决消息由谁发送,由谁接收的问题。

  1. 使用到Spring Security,简单配置。

    /**
     * Spring Security配置
     */
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/", "login").permitAll() //设置Spring Security对/和"/login"路径不拦截
                .anyRequest().authenticated()
                .and().formLogin().loginPage("/login")//设置Spring Security的登陆页面访问路径为login
                .defaultSuccessUrl("/chat") //登陆成功后转向/chat路径
                .permitAll().and().logout()
                .permitAll();
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //配置两个用户,角色是user
            auth.inMemoryAuthentication()
                .withUser("james").password("james").roles("user")
                .and()
                .withUser("curry").password("curry").roles("user");
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            // 设置Spring Secutiry不拦截/resources/static/目录下的静态资源
            web.ignoring().antMatchers("/resources/static/**");
        }
    
    }
    
  2. 配置WebSocket

    /**
     * 配置WebSocket
     */
    @Configuration
    //注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {//注册STOMP协议的节点(endpoint),并映射指定的url
            //注册一个STOMP的endpoint,并指定使用SockJS协议
            registry.addEndpoint("/endpointAric").withSockJS();
            //注册名为"endpointChat"的endpoint
            registry.addEndpoint("/endpointChat").withSockJS();
        }
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代理(Message Broker)
            //广播式应配置一个/topic消息代理
    //      registry.enableSimpleBroker("/topic");
    
            //点对点式应配置/queue和/topic消息代理
            registry.enableSimpleBroker("/queue","/topic");
        }
    }
    
  3. 编写Controller

    /**
     * webSocket控制器
     */
    @Controller
    public class WebSocketController {
    
        //通过simpMessagingTemplate向浏览器发送消息
        @Autowired
        private SimpMessagingTemplate simpMessagingTemplate;
    
        @MessageMapping("/chat")
        //在springmvc中,可以直接在参数中获得principal,pinciple中包含当前用户信息
        public void handleChat(Principal principal,String msg){
            if ("james".equals(principal.getName())) {//硬编码,对用户姓名进行判断
                //向用户发送消息,第一个参数:接收消息的用户,第二个参数:浏览器订阅地址,第三个参数:消息
                simpMessagingTemplate.convertAndSendToUser("curry", 
                        "/queue/notifications", principal.getName() + "-send: " + msg);
            } else {
                simpMessagingTemplate.convertAndSendToUser("james", 
                        "/queue/notifications", principal.getName() + "-send: " + msg);
            }
        }
    
        @MessageMapping("/welcome") //当浏览器向服务端发送请求时,通过@MessageMapping映射/welcome这个地址,类似于@ResponseMapping
        @SendTo("/topic/getResponse")//当服务器有消息时,会对订阅了@SendTo中的路径的浏览器发送消息
        public AricResponse say(AricMessage message) {
            try {
                //睡眠3秒
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return new AricResponse("welcome," + message.getName() + "!");
        }
    }
    
  4. 在src\main\resource\templates目录下编写login.html和chat.html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8" />
    <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>
    

    聊天页面

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8" />
        <title>聊天室</title>
        <script th:src="@{sockjs.min.js}"></script>
        <script th:src="@{stomp.min.js}"></script>
        <script th:src="@{jquery-1.11.3.min.js}"></script>
    </head>
    <body>
        <p>聊天室</p>
        <form id="aricForm">
            <textarea rows="4" cols="60" name="text"></textarea>
            <input type="submit" />
        </form>
        <div id="output"></div>
    </body>
    <script type="text/javascript">
        $("#aricForm").submit(function(e){
            e.preventDefault();
            var text = $("#aricForm").find('textarea[name="text"]').val();
            sendSpittle(text);
        });
    
        var sock = new SockJS("/endpointChat");//连接endpoint名为"endpointChat"的endpoint
        var stomp = Stomp.over(sock);
        stomp.connect('guest','guest',function(frame){
            //订阅/user/queue/notifications发送的消息,这里与在simpMessagingTemplate.convertAndSendToUser定义地址一致,/user是必须的
            stomp.subscribe("/user/queue/notifications",handleNotification);
        });
    
        function handleNotification(message){
            $("#output").append("<b>Received: " + message.body + "</b><br/>");
        }
    
        function sendSpittle(text){
            stomp.send("/chat",{},text);
        }
    
        $("#stop").click(function(){
            sock.close();
        });
    </script>
    </html>
    
  5. 配置viewController

    /**
     * 配置viewController,为页面提供路径映射
     */
    @Controller
    public class WebMvcConfig extends WebMvcConfigurerAdapter{
    
        /**
         * 配置viewController,提供映射路径
         */
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/webSocket").setViewName("/webSocket");
            registry.addViewController("/login").setViewName("/login");
            registry.addViewController("/chat").setViewName("/chat");
        }
    
    }
    
  6. 打开浏览器,实现点对点聊天
展开阅读全文

没有更多推荐了,返回首页