spring4 使用websocket


  要了解的內容:

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

  stomp 協議,一種格式比較簡單且被廣泛支持的通信協議,spring4提供了以stomp協議為基礎的websocket通信實現。

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

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

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

  因此在使用的時候,有了一開始的客戶端注冊到指定url,這個所謂的注冊到執行url的過程,實際就是客戶端跟服務端建立websocket連接的過程,連接建立之后,要發送或者接收什么消息都通過這一個websocket通信連接來完成,而不是每一個主題建立一個連接,這樣可以節省開銷。其中服務端代碼.withSockJS()的作用是聲明我們想要使用 SockJS 功能,如果WebSocket不可用的話,會使用 SockJS。

@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處理。

@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連接
  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的資源?有知道的大神請留言指教。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM