Spring WebFlux 基礎教程:WebSocket 使用

lzyer發表於2021-08-10

WebSocket 協議簡介

WebSocket 協議提供了一種標準化的方式,在客戶端和服務端建立在一個TCP 連線之上的全雙工,雙向通訊的協議。

WebSocket 互動開始於 HTTP 請求,使用 HTTP 請求的 header 中的 Upgrade 進行切換到 WebSocket 協議。

HTTP 和 WebSocket 對比

即使 WebSocket 的設計相容 HTTP 協議和開始於 HTTP 請求,瞭解兩個協議是不同架構和應用模型是非常重要的。

在 HTTP 和 REST, 一個應用有多個URL, 對於互動的應用,客戶端可以訪問這些 URL,請求和響應的風格。服務端會根據 URL、http method、header 等等資訊進行路由處理。

相比之下, WebSocket 協議是使用一個URL 初始化連線。應用所有的訊息通過同一個 TCP 通訊,這一點是完全不同的非同步、事件驅動的訊息傳遞體系結構。

WebSocket API 的使用

  1. Webflux 依賴
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  1. 定義一個Handler,用於處理資料的請求和響應
import java.time.Duration;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class MyWebSocketHandler implements WebSocketHandler {
// 每間隔 1 秒,數字加 1
  private Flux<Long> intervalFlux = Flux.interval(Duration.ofSeconds(1L), Duration.ofSeconds(1L));

  @Override
  public Mono<Void> handle(WebSocketSession session) {
    return session.send(intervalFlux.map(item -> session.textMessage(item + "")))
        .and(session.receive().map(WebSocketMessage::getPayloadAsText).log());
  }
}
  1. 配置類,配置 WebSocket 的 URL 資訊
@Configuration
public class WebConfig {

  @Bean
  public WebSocketHandlerAdapter handlerAdapter() {
    return new WebSocketHandlerAdapter();
  }

  @Bean
  public HandlerMapping handlerMapping() {
    Map<String, WebSocketHandler> urlMap = new HashMap<>();
    urlMap.put("/path", new MyWebSocketHandler());
    int order = -1;
    return new SimpleUrlHandlerMapping(urlMap, order);
  }
}
  1. 客戶端
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import reactor.core.publisher.Mono;

public class Client {

  public static void main(String[] args) throws URISyntaxException {

    WebSocketClient client = new ReactorNettyWebSocketClient();
    URI url = new URI("ws://localhost:8080/path");
    client.execute(url,
        session -> session.send(Mono.just(session.textMessage("hello world")))
            .thenMany(session.receive().map(WebSocketMessage::getPayloadAsText).log())
            .then())
        .block(Duration.ofSeconds(10));
  }
}

服務端日誌

2021-08-10 22:42:36.162  INFO 85792 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2021-08-10 22:42:36.168  INFO 85792 --- [           main] c.e.s.SpringBootDemoApplication          : Started SpringBootDemoApplication in 3.583 seconds (JVM running for 4.462)
2021-08-10 22:42:51.518  INFO 85792 --- [ctor-http-nio-2] reactor.Flux.Map.1                       : onSubscribe(FluxMap.MapSubscriber)
2021-08-10 22:42:51.522  INFO 85792 --- [ctor-http-nio-2] reactor.Flux.Map.1                       : request(unbounded)
2021-08-10 22:42:51.534  INFO 85792 --- [ctor-http-nio-2] reactor.Flux.Map.1                       : onNext(hello world)
2021-08-10 22:43:00.956  INFO 85792 --- [ctor-http-nio-2] reactor.Flux.Map.1                       : onComplete()

客戶端日誌

22:42:51.527 [reactor-http-nio-1] DEBUG reactor.netty.channel.FluxReceive - [id: 0xd95be56c, L:/127.0.0.1:50773 - R:localhost/127.0.0.1:8080] Subscribing inbound receiver [pending: 0, cancelled:false, inboundDone: false]
22:42:51.530 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onSubscribe(FluxMap.MapSubscriber)
22:42:51.531 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - request(unbounded)
22:42:52.518 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(0)
22:42:53.513 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(1)
22:42:54.518 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(2)
22:42:55.513 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(3)
22:42:56.514 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(4)
22:42:57.516 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(5)
22:42:58.518 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(6)
22:42:59.512 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(7)
22:43:00.513 [reactor-http-nio-1] INFO reactor.Flux.Map.1 - onNext(8)
22:43:00.947 [main] INFO reactor.Flux.Map.1 - cancel()
22:43:00.948 [reactor-http-nio-1] DEBUG reactor.netty.http.client.HttpClientOperations - [id: 0xd95be56c, L:/127.0.0.1:50773 - R:localhost/127.0.0.1:8080] Cancelling Websocket inbound. Closing Websocket
Exception in thread "main" java.lang.IllegalStateException: Timeout on blocking read for 10000 MILLISECONDS

至此,Spring WebFlux 中 WebSocket 協議使用的結束了。

相關文章