spring4 使用websocket

  要了解的內容:javascript

  sockjs,對於低版本的ie等不支持websocket的瀏覽器,採用js模擬websocket對象的辦法來實現兼容(其實也有輪詢的狀況)。sockjs地址 https://github.com/sockjs/sockjs-client前端

  stomp 協議,一種格式比較簡單且被普遍支持的通訊協議,spring4提供了以stomp協議爲基礎的websocket通訊實現。java

  ------------------------------------------------------------------------------------------git

  而後,重點來了,spring實現websocket的大概原理是什麼樣子的呢?spring 的websocket實現,其實是一個簡易版的消息隊列(並且是主題-訂閱模式的),對於要發給具體用戶的消息,spring的實現是建立了一個跟用戶名相關的主題,實際上若是同一用戶登陸多個客戶端,每一個客戶端都會收到消息,所以能夠看出來,spring的websocket實現是基於廣播模式的,要實現真正的單客戶端用戶區分(單用戶多端登陸只有一個收到消息),只能依賴於session(至關於一個終端一個主題了)。消息的具體處理過程如何,先上一圖:github

  客戶端發送消息,服務端接收後先判斷該消息是否須要通過後臺程序處理,也就是是不是application消息(圖中的/app分支),若是是,則根據消息的url路徑轉到controller中相關的處理方法進行處理,處理完畢後發送到具體的主題或者隊列;若是不是application消息,則直接發送到相關主題或者隊列,而後通過處理髮送給客戶端。web

  所以在使用的時候,有了一開始的客戶端註冊到指定url,這個所謂的註冊到執行url的過程,實際就是客戶端跟服務端創建websocket鏈接的過程,鏈接創建以後,要發送或者接收什麼消息都經過這一個websocket通訊鏈接來完成,而不是每個主題創建一個鏈接,這樣能夠節省開銷。其中服務端代碼.withSockJS()的做用是聲明咱們想要使用 SockJS 功能,若是WebSocket不可用的話,會使用 SockJS。spring

@Configuration
@EnableWebSocketMessageBroker //在 WebSocket 上啓用 STOMP
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //webServer就是websocket的端點,客戶端須要註冊這個端點進行連接,
        registry.addEndpoint("/webServer").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
//        registry.setPathMatcher(new AntPathMatcher("."));//能夠已「.」來分割路徑,看看類級別的@messageMapping和方法級別的@messageMapping

        registry.enableSimpleBroker("/topic","/user");
        registry.setUserDestinationPrefix("/user/");
        registry.setApplicationDestinationPrefixes("/app");//走@messageMapping
    }

    @Override
    public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
        return true;
    }
    @Override
    public void configureWebSocketTransport(WebSocketTransportRegistration webSocketTransportRegistration) {
    }
    @Override
    public void configureClientInboundChannel(ChannelRegistration channelRegistration) {
    }
    @Override
    public void configureClientOutboundChannel(ChannelRegistration registration) {
        // TODO Auto-generated method stub
    }
    @Override
    public void addArgumentResolvers(List<org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver> list) {
    }
    @Override
    public void addReturnValueHandlers(List<org.springframework.messaging.handler.invocation.HandlerMethodReturnValueHandler> list) {
    }
}

  @EnableWebSocketMessageBroker 的做用是在WebSocket 上啓用 STOMP,registerStompEndpoints方法的做用是websocket創建鏈接用的(也就是所謂的註冊到指定的url),configureMessageBroker方法做用是配置一個簡單的消息代理。若是補充在,默認狀況下會自動配置一個簡單的內存消息隊列,用來處理「/topic」爲前綴的消息,但通過重載後,消息隊列將會處理前綴爲「/topic」、「/user」的消息,並會將「/app」的消息轉給controller處理。json

@RequestMapping("/myws")
@Controller
public class WebSocketController {
    @Resource
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/hello")
//  @SendTo("/topic/hello")//會把方法的返回值廣播到指定主題(「主題」這個詞並不合適)
    public void toTopic(SocketMessageVo msg , String name) {
        System.out.println(msg.getName()+","+msg.getMsg());
        this.simpMessagingTemplate.convertAndSend("/topic/hello",msg.getName()+","+msg.getMsg());
//      return "消息內容:"+ msg.getName()+"--"+msg.getMsg();
    }

    @MessageMapping("/message")
//  @SendToUser("/message")//把返回值發到指定隊列(「隊列」實際不是隊列,而是跟上面「主題」相似的東西,只是spring在SendTo的基礎上加了用戶的內容而已)
    public void toUser(SocketMessageVo msg ) {
        System.out.println(msg.getName()+","+msg.getMsg());
        this.simpMessagingTemplate.convertAndSendToUser("123","/message",msg.getName()+msg.getMsg());
    }

    @RequestMapping("/sendMsg")
    public void sendMsg(HttpSession session){
        System.out.println("測試發送消息:隨機消息" +session.getId());
        this.simpMessagingTemplate.convertAndSendToUser("123","/message","後臺具體用戶消息");
    }
}

  WebSocketConfig 中配置setApplicationDestinationPrefixes()的消息會被轉發到WebSocketController 中 @MessageMapping 相應方法進行處理。@SendTo("/topic/hello") 會把方法的返回值序列化爲json串,而後發送到指定的主題,不用此註解,使用 simpMessagingTemplate.convertAndSend 效果相同;若爲 @SendToUser("/message") 則爲發送到指定的用戶隊列(實際隊列名字爲/user/用戶名/原隊列名),不用此註解,使用 simpMessagingTemplate.convertAndSendToUser() 效果相同;瀏覽器

   後臺主動往前端推送消息,直接調用 simpMessagingTemplate.convertAndSendToUser() 跟 simpMessagingTemplate.convertAndSend() 便可將消息發往隊列或者主題。websocket

  前端代碼:

  //創建websocket鏈接
  function openWs(){
      websocket = new SockJS("http://localhost:8080/autotest" + "/webServer");

      var stompClient = Stomp.over(websocket);
      stompClient.connect({}, function(frame) {
          stompClient.subscribe('/topic/hello',  function(data) { //訂閱消息
              console.log("收到topic消息:"+data.body);//body中爲具體消息內容
          });
          stompClient.subscribe('/user/' + 123 + '/message', function(message){
              console.log("收到session消息:"+message.body);//body中爲具體消息內容
          });
      });

      document.getElementById("sendws").onclick = function() {
          stompClient.send("/app/message", {}, JSON.stringify({
              name: "nane",
              msg: "發送的消息aaa"
          }));
      }
  }
  //關閉鏈接
  function wsClose() {
      websocket.close();
  }

  代碼完成後運行效果以下:

  

   能夠看到鏈接創建握手的過程,以及訂閱成功後的消息打印,<<<爲從服務端接收到的消息,>>>爲往服務端發的消息。

 --------------------------------------------------------------------------------

最後,websocket 跟輪詢,長鏈接相比有啥優點,參見:https://www.zhihu.com/question/20215561

這裏有一點不明,websocket跟長鏈接都是每一個客戶端跟服務端創建了一個鏈接,爲何說長鏈接對服務端資源消耗嚴重,而不提websocket對服務端的消耗呢?是websocket協議更底層,只在物理鏈路上有個鏈接,並無實際消耗jvm的資源?有知道的大神請留言指教。

相關文章
相關標籤/搜索