spring boot websocket的實現

簡單介紹

    WebSocket是爲瀏覽器和服務端提供雙工藝部通訊功能一種工具,即瀏覽器能夠先服務端發送消息,服務端也能夠先瀏覽器發送消息。如今支持Websocket的瀏覽器有  IE10+,Crome13+,FileFox6+。html

WebSocket子協議

    WebSocket只是一個消息傳遞的體系結構,沒有指定任何的消息傳遞協議。與HTTP協議不一樣的是,WebSocket只是一個應用層的協議,它很是簡單,並不能理解傳入的消息,也不能對消息進行路由或處理,所以WebSocket協議只是一個應用層的協議,其上須要一個框架來理解和處理消息。前端

    Spring框架提供了對使用STOMP子協議的支持。STOMP,全稱Streaming Text Orientated Message Protol,流文本定向協議。STOMP是一個簡單的消息傳遞協議,是一種爲MOM(Message Orientated  Middleware,面向消息的中間件)設計的簡單文本協議。STOMP提供了一個可操做的鏈接格式,容許STOMP客戶端與任意代理(Broker)進行交互,相似於OpenWire(一種二進制協議)。java

    

Spring Boot的WebSocket實現

     SpringBoot對內嵌的Tomcat(7或者8)、Jetty9和Undertow使用了WebSocket提供了支持。web

廣播式

廣播式即服務端有消息時,會將消息發送到全部鏈接了當前endpoint的瀏覽器。spring

配置WebSocket

須要在配置類上使用@EnableWebSocketMessageBroker開啓WebSocket支持,並經過集成AbstractWebSocketMessageBrokerConfigurer類,重寫其方法來配置WebSocket。
        json

package com.example.config;

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;

/**
 * Created by lenovo on 2017/3/15.
 */
@Configuration
@EnableWebSocketMessageBroker //經過@EnableWebSocketMessageBroker 註解凱旗使用STOMP協議來傳輸基於代理(message broker)的消息
//這時控制器支持使用@MessageMapping,就像使用@RequestMapping同樣
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{


    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        stompEndpointRegistry.addEndpoint("/endpoint").withSockJS();//註冊STOMP協議的節點,映射指定的URL,並指定使用SockJS協議
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {//配置消息代碼(Message Broker)
        registry.enableSimpleBroker("/topic");//廣播式應配置一個/topic消息代理
    }
}

消息的接收器、發送器和控制器segmentfault

package com.example.model;

/**
 * Created by lenovo on 2017/3/15.
 */
public class MessageSender {
    private String msg;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public MessageSender(String msg) {
        this.msg = msg;
    }
}
package com.example.model;

import java.io.Serializable;

/**
 * Created by lenovo on 2017/3/15.
 */
public class MessageAcceptor implements Serializable{

    private String msg;

    public String getMsg() {
        return msg;
    }

}
package com.example.websocket;

import com.example.model.MessageAcceptor;
import com.example.model.MessageSender;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Created by lenovo on 2017/3/15.
 */
@Controller
public class TestWeb {

    @MessageMapping(value = "/message/test")//當瀏覽器向服務端發送請求時,經過@MessageMapping映射的地址,相似於@RequestMapping
    @SendTo(value = "/topic/response")//當服務端有消息時,會對訂閱了@SendTo中的路徑的瀏覽器發送消息
    public MessageSender say(MessageAcceptor acceptor){
        return new MessageSender("HELLO!"+acceptor.getMsg());
    }

    @RequestMapping("index")
    public String index(){
        return "index";
    }
}

準備 WebSocket須要的前端文件。

下載stomp.min.js和sockjs.min.js文件,並放在static下,而後在templates下新建index.html頁面瀏覽器

stomp的API參考連接:https://segmentfault.com/a/11...安全

<!DOCTYPE html>
<html xmlns="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
</head>
<body>
<div>
    <button onclick="connect()" id="connect">鏈接</button>
    <button onclick="disconnect()" id="disconnect">斷開</button>
</div>
<div>
    <input type="text" id="name"/>
    <button id="send">發送</button>
</div>
<div id="msg">

</div>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{sockjs.min.js}"></script>
<script>
    var stompClient = null;

    function connect() {
        var socket = new SockJS("/endpoint");//鏈接SockJS的endpoint名稱爲"/endpoint"
        stompClient = Stomp.over(socket);//使用STOMP子協議的WebSocket客戶端
        stompClient.connect({}, function (frame) {//鏈接WebSocket服務端
            stompClient.subscribe("/topic/response", function (msg) {//經過stopmClient.subscribe訂閱"/topic/response"目標發送的消息,這個路徑是在控制器的@SendTo中定義的
                console.log(msg);
                var msgDom = document.getElementById("msg");
                var html = msgDom.innerHTML;
                msgDom.innerHTML = html + "\n" + msg.body;
            });
        });

    }

    function disconnect() {
        if(stompClient!=null){
            stompClient.disconnect();
        }
    }

    function send() {
        var name = document.getElementById("name").value;
        stompClient.send("/message/test", {}, JSON.stringify({//經過stompClient.send向"/message/test"目標發送消息,這個在控制器的@MessageMapping中定義的。
            'msg': name
        }));
    }

    document.getElementById("send").onclick = send;
</script>
</body>
</html>

上述代碼都已經準備好了,那麼一塊兒來看一看運行效果

如預期同樣,在鏈接了WebSocket的客戶端發送消息時,其它一樣鏈接了WebSocket的客戶端瀏覽器也收到了消息,沒有鏈接WebSocket的客戶端則沒有收到消息websocket

看完了代碼和代碼的運行效果,咱們再來看一看WebSocket運行中STOMP的幀

鏈接STOMP服務端幀:CONNECT↵accept-version:1.1,1.0↵heart-beat:10000,10000 

鏈接STOMP服務端成功幀:CONNECTED↵version:1.1↵heart-beat:0,0 

訂閱目標/topic/response:SUBSCRIBE↵id:sub-0↵destination:/topic/response 

向目標/message/test發送消息:SEND↵destination:/message/test↵content-length:16↵↵{"msg":"測試"} 

從目標/topic/response接收到消息:MESSAGE↵destination:/topic/response↵content-type:application/json;charset=UTF-8↵subscription:sub-0↵message-id:hstpp2xl-0↵content-length:22↵↵{"msg":"HELLO!測試"}

點對點式

廣播式有本身的應用場景,可是廣播式不能解決咱們咱們一個常見的問題,即消息由誰發送,由誰接受的問題。

1.在進行點對點傳遞消息的時候,必然發生在兩個用戶之間的行爲,那麼就須要添加用戶相關的內容,在這裏先完成一個簡單的登錄。

首先添加Spring Security的starter pom:    

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

2.而後進行spring security的簡單配置

  

package com.example.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * Created by lenovo on 2017/3/17.
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 權限管理配置構造器
     *
     * @param auth 權限管理
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置了兩個用戶和對應的密碼,而且申明瞭他們的角色
        auth.inMemoryAuthentication().withUser("muxiao").password("123456").roles("USER")
                .and().withUser("hahaha").password("123456").roles("USER");//在內存中分別配置兩個用戶muxiao和hahaha
    }

    /**
     * Web安全配置
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        //靜態資源不作安全校驗
        web.ignoring().antMatchers("/resources/static/**");///resources/static/目錄下的靜態資源,不攔截
    }

    /**
     * 配置http安全
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //簡單的配置運行點對點所須要的登錄權限
        http.authorizeRequests()
                .antMatchers("/","login").permitAll()//設置Spring Security對/和/login路徑不攔截
                .anyRequest().authenticated()
                .and().formLogin().loginPage("/login")//設置登陸頁面訪問的路徑爲/login
                .defaultSuccessUrl("/chat").permitAll()//登錄成功後轉向chat頁面
                .and().logout().permitAll();
    }
}
        而後在TestWeb中增長一個MessageMapping接口:

  

  @Autowired private SimpMessagingTemplate messagingTemplate;//spring實現的一個發送模板類 

    @MessageMapping("/chat")
    public void handlerChat(Principal principal, String msg) {//springmvc中能夠直接在參數中得到pricipal,pricipal中包含當前永不的信息
        if (principal.getName().equalsIgnoreCase("muxiao")) {
            messagingTemplate.convertAndSendToUser("hahaha","/queue/notice",principal.getName()+":"+msg);
        } else {
            messagingTemplate.convertAndSendToUser("muxiao","/queue/notice",principal.getName()+":"+msg);
            //經過messaginTemplate.converAndSendTiUser向用戶發送消息,第一次參數是接受信息的用戶,第二個是瀏覽器訂閱的地址,第三個是消息自己
        }
    }

3.同時定義須要的頁面訪問路徑:

package com.example.config;

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

/**
 * Created by lenovo on 2017/3/17.
 */
@Configuration
public class WebViewConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/chat").setViewName("/chat");
        registry.addViewController("/login").setViewName("/login");
    }
}

4.咱們已經準備好了所須要的後臺,這時候就開始實現咱們須要的功能的前端編寫了。

首先,實現登錄頁面,在瀏覽器中訪問除過"/","/index"以外的其它頁面,都會來到login頁面以進行登錄,即下面的頁面:

<!DOCTYPE html>
<html xmlns="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
</head>
<body>
<form th:action="@{/login}" method="post">
    <input type="text" name="username"/>
    <input type="password" name="password"/>
    <input type="submit"/>
</form>
</body>
</html>
              輸入咱們在內存中指定的用戶名和密碼,登錄進入chat頁面

<!DOCTYPE html>
<html xmlns="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Title</title>
</head>
<body>
<textarea id="content"></textarea>
<input type="button" value="發送" onclick="send()"/>
<div id="out">

</div>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script>
    var sock = new SockJS("/endpointOneToOne");//鏈接"endpointOneToOne"
    var stomp = Stomp.over(sock);
    stomp.connect({},function(frame){
        //訂閱/user/queue/notice發送的消息,這裏與在控制器的messagingTemplate.convertAndSendToUser中定義的訂閱地址保持一致。
        //這裏多一個/user,而且這個/user是必須的,使用了/user纔會發送消息到指定的用戶
        stomp.subscribe("/user/queue/notice",function(message){//
            var out = document.getElementById("out");
            var html = out.innerHTML;
            out.innerHTML = html +"<br />"+message.body;
        })
    });

    function send(){
        var content = document.getElementById("content").value;
        stomp.send("/chat",{},content);
    }
</script>
</body>
</html>

同時在兩個瀏覽器上面,用在內存中指定的兩個用戶登錄,這樣兩個用戶就能夠互相發送消息了,延時效果以下:

           圖片描述 

相關文章
相關標籤/搜索