WebSocket是爲瀏覽器和服務端提供雙工藝部通訊功能一種工具,即瀏覽器能夠先服務端發送消息,服務端也能夠先瀏覽器發送消息。如今支持Websocket的瀏覽器有 IE10+,Crome13+,FileFox6+。html
WebSocket只是一個消息傳遞的體系結構,沒有指定任何的消息傳遞協議。與HTTP協議不一樣的是,WebSocket只是一個應用層的協議,它很是簡單,並不能理解傳入的消息,也不能對消息進行路由或處理,所以WebSocket協議只是一個應用層的協議,其上須要一個框架來理解和處理消息。前端
Spring框架提供了對使用STOMP子協議的支持。STOMP,全稱Streaming Text Orientated Message Protol,流文本定向協議。STOMP是一個簡單的消息傳遞協議,是一種爲MOM(Message Orientated Middleware,面向消息的中間件)設計的簡單文本協議。STOMP提供了一個可操做的鏈接格式,容許STOMP客戶端與任意代理(Broker)進行交互,相似於OpenWire(一種二進制協議)。java
SpringBoot對內嵌的Tomcat(7或者8)、Jetty9和Undertow使用了WebSocket提供了支持。web
廣播式即服務端有消息時,會將消息發送到全部鏈接了當前endpoint的瀏覽器。spring
須要在配置類上使用@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"; } }
下載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
鏈接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>
同時在兩個瀏覽器上面,用在內存中指定的兩個用戶登錄,這樣兩個用戶就能夠互相發送消息了,延時效果以下: