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()方法,每個方法對應於
某一種特定類型的消息。
基於這個實現類,可以看到BinaryWebSocketHandler
和TextWebSocketHandler
兩個實現類,我們根據需要繼承一個既可以。這裏我們選擇``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