Java WebSocket 實踐2 - 基於Spring 低層級WebSocket API 實現WebSocket

Java WebSocket 實踐2 - 基於Spring 低層級WebSocket API 實現WebSocket
  • 基於Spring 低層級WebSocket API 實現WebSocket
    基於Spring 低層級實現一個WebSoket Demo 不基於註解,而是需要用到一些Spring 提供的接口和實現類。
    主要會用到的接口和實現類如下:
    • WebSocketHandler: 這是一個接口,提供了五個實現類:
      以下是這個接口的源碼。源碼說的很清楚,這個handler 的作用是處理websoket 消息和 websoket 的生命週期事件。
      類似於javax 中提供的那五個註解。我們用低層級API 的第一步就是實現這個接口,實現一個我們自己的webSocketHandler。

	/**
 * A handler for WebSocket messages and lifecycle events.
 *
 * <p>Implementations of this interface are encouraged to handle exceptions locally where
 * it makes sense or alternatively let the exception bubble up in which case by default
 * the exception is logged and the session closed with
 * {@link CloseStatus#SERVER_ERROR SERVER_ERROR(1011)}. The exception handling
 * strategy is provided by
 * {@link org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator
 * ExceptionWebSocketHandlerDecorator} and it can be customized or replaced by decorating
 * the {@link WebSocketHandler} with a different decorator.
 *
 * @author Rossen Stoyanchev
 * @author Phillip Webb
 * @since 4.0
 */
public interface WebSocketHandler {

	/**
	 * Invoked after WebSocket negotiation has succeeded and the WebSocket connection is
	 * opened and ready for use.
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void afterConnectionEstablished(WebSocketSession session) throws Exception;

	/**
	 * Invoked when a new WebSocket message arrives.
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;

	/**
	 * Handle an error from the underlying WebSocket message transport.
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;

	/**
	 * Invoked after the WebSocket connection has been closed by either side, or after a
	 * transport error has occurred. Although the session may technically still be open,
	 * depending on the underlying implementation, sending messages at this point is
	 * discouraged and most likely will not succeed.
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;

	/**
	 * Whether the WebSocketHandler handles partial messages. If this flag is set to
	 * {@code true} and the underlying WebSocket server supports partial messages,
	 * then a large WebSocket message, or one of an unknown size may be split and
	 * maybe received over multiple calls to
	 * {@link #handleMessage(WebSocketSession, WebSocketMessage)}. The flag
	 * {@link org.springframework.web.socket.WebSocketMessage#isLast()} indicates if
	 * the message is partial and whether it is the last part.
	 */
	boolean supportsPartialMessages();

}

但是實際上,我們一般很少會直接去實現這個接口,因爲這個接口實在太底層了,Spring 中已經提供了一些實現類:
` 在這裏插入圖片描述
AbstractWebSocketHandler 是一個抽象類,這個抽象類中除了實現WebSocketHandler 中定義的那幾個方法外,最重要的是這一段代碼:

   @Override
   public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
   	if (message instanceof TextMessage) {
   		handleTextMessage(session, (TextMessage) message);
   	}
   	else if (message instanceof BinaryMessage) {
   		handleBinaryMessage(session, (BinaryMessage) message);
   	}
   	else if (message instanceof PongMessage) {
   		handlePongMessage(session, (PongMessage) message);
   	}
   	else {
   		throw new IllegalStateException("Unexpected WebSocket message type: " + message);
   	}
   }

   protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
   }

   protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
   }

   protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
   }

可以看到,這段代碼的主要作用就是:具體化了handleMessage()方法,每個方法對應於
某一種特定類型的消息。
基於這個實現類,可以看到BinaryWebSocketHandlerTextWebSocketHandler兩個實現類,我們根據需要繼承一個既可以。這裏我們選擇``TextWebSocketHandler`.
具體實現代碼如下:

public class MyWebScoketHandler extends TextWebSocketHandler {

    public MyWebScoketHandler() {
        super();
    }

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
        super.handleBinaryMessage(session, message);
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        super.afterConnectionEstablished(session);
        System.out.println("客戶端連接成功");
    }



    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        System.out.println("收到消息客戶端消息 : "+ message.getPayload());
        session.sendMessage(new TextMessage(message.getPayload()));
    }



    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        super.handleTransportError(session, exception);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
        System.out.println("連接已經斷開");
    }

    @Override
    public boolean supportsPartialMessages() {
        return super.supportsPartialMessages();
    }
}

這就是最核心的處理邏輯。可以看到 WebSocketSession 這個對象扮演了非常重要的角色,消息的收和發都是由它來負責的,和Javax中的Session 類似。這個接口對象會常常用到。

  • 配置類
package com.example.websocket_demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Component
// 開啓WebSocket 
@EnableWebSocket
public class WebScoketConfig implements WebSocketConfigurer {

    /**
     *  register  message handler and  webSocket Uri path
     * @param registry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWebScoketHandler(),"/test/websocket").setAllowedOrigins("*");
    }

    /**
     * register MyWebSocketHandler to Spring container
     * @return  MyWebSocketHandler
     */
    @Bean
    public MyWebScoketHandler myWebScoketHandler(){
        return  new MyWebScoketHandler();
    }

}

我們需要在這個配置中定義我們的消息處理器和webSoket 的URI 。可以說這個Handler 的含義和JavaX中的@ServerEndpoint概念是一致的。
@EnableWebSocket 的作用:名字已經說明了,啓用WebSocket 。

  • 啓動類
    不貼代碼了,只有一個run,自己腦補。

實現客戶端

  • Handler 同樣需要自定義一個Handler ,和服務端保持一致。代碼一樣的,這裏不貼了。
  • 實現Client :
package com.example.websocket_client.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHttpHeaders;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;

@Component
public class MyWebSocketClient  {

    @Autowired
    private MyWebSocketHandler myWebSocketHandler ;
    private WebSocketSession webSocketSession;
    private boolean isConnected  = false ;

    public void connected(){
        WebSocketClient webSocketClient = new StandardWebSocketClient();
        WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
        ListenableFuture<WebSocketSession> future = webSocketClient.doHandshake(myWebSocketHandler, headers, URI.create("ws://localhost:8080/test/websocket"));
        try {
            webSocketSession = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        isConnected = true ;

    }

    public void sendMessage() throws IOException {
        int id =  0 ;
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {


            }
            webSocketSession.sendMessage(new TextMessage("消息序列:id = " + id));
            id++ ;
        }
    }
}


這是一個自定義的處理類,最主要的就是用到了一個WebSocketClient 的對象, 需要用這個對象來和服務端發起連接和通信。用WebSocketSession 來發送消息。我們自定義的Handler 在這兒注入進來,也直接在握手的時候指明處理消息的Handler。

  • 啓動類
    不貼代碼了,只有一個run,自己腦補。

    至此,client 也就實現了。

兩種方式的比較

1: 如果只是想簡單的實現一個WebSocket 的demo,javax的方式更爲簡單一些。畢竟幾個註解就搞定了。
2: 因爲這是Spring 的低層級API ,所以比較偏向於代碼實現風格。它最大的優勢在於能實現一些攔截器之類的功能。
3: 實際上,JavaX API 提供了Spring 框架的很多功能,比如處理不同message 能力的功能,可以參考:https://docs.oracle.com/javaee/7/api/javax/websocket/OnMessage.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章