WebFlux 剖析之 WebSocket 请求处理

阅读这篇文章可能需要 20 分钟,文章内容遵循由浅到深的顺序。

1. WebSocket 原理

WebSocket 是基于 TCP 的通信协议,它与 HTTP 唯一的关联在于,最开始的连接建立过程是借助 HTTP 完成的,连接建立后,即切换到 WebSocket 协议。(RFC 6455 # Relationship to TCP and HTTP)

WebSocket 连接建立过程

客户端先发送一个 HTTP 请求,与普通 HTTP 请求不同,这里需要指定一些特殊的 header,其中最关键的三个 header 分别是 ConnectionUpgradeSec-WebSocket-Key

GET /socket HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: HAeNX0HSSu7ViucwTRVE4w==

服务器收到 WebSocket 连接的建立请求后,如果没有问题,会返回 101 Switching Protocols 响应,响应中同样包含了几个关键的 header。

HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: OhVSHlCtdtU+Upy3i1SuxW51J60=

其中,Sec-WebSocket-Accept 是服务端根据收到的请求中的 Sec-WebSocket-Key 计算得到的,表明服务器同意 WebSocket 连接的创建,它的计算方法如下:Base64Encode(Sha1(Sec-WebSocket-Key + GUID)),其中 GUID 是一个定值 258EAFA5-E914-47DA-95CA-C5AB0DC85B11

以上述请求为例:

  1. Sec-WebSocket-Key + GUID = HAeNX0HSSu7ViucwTRVE4w==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
  2. Sha1(Sec-WebSocket-Key + GUID) = 0x3a 0x15 0x52 0x1e 0x50 0xad 0x76 0xd5 0x3e 0x52 0x9c 0xb7 0x8b 0x54 0xae 0xc5 0x6e 0x75 0x27 0xad
  3. Base64Encode(Sha1(Sec-WebSocket-Key + GUID)) = OhVSHlCtdtU+Upy3i1SuxW51J60=

客户端收到 101 响应后,会校验 Sec-WebSocket-Accept 的值,校验成功后正式建立连接。

连接建立之后,客户端和服务器可以双向传输数据(全双工通信),直到连接被关闭。

参考文档:RFC 6455 - The WebSocket Protocol

2. WebFlux 请求处理流程

到达 WebFlux 的请求,会进入到 DispatcherHandler,由它完成 handler 的查找、调用和结果处理等步骤,这些步骤分别由以下类型的 Bean 完成,DispatcherHandler 会在 WebFlux 初始时检测这些 Bean。

Bean 类型 解释
HandlerMapping 将请求映射到对应的 handler。主要的 HandlerMapping 实现有处理 @RequestMapping 注解的 RequestMappingHandlerMapping ,处理函数路由的RouterFunctionMapping,以及处理简单 URL 映射的 SimpleUrlHandlerMapping
HandlerAdapter 帮助 DispatcherHandler 调用请求对应的 handler,而不用关心该 handler 具体的调用方式。例如,调用一个通过注解的方式定义的 controller 就需要寻找对应的注解,而 HandlerAdapter 的主要目的就是为了帮助 DispatcherHandler 屏蔽类似的细节.
HandlerResultHandler 处理 handler 调用后的结果,并生成最后的响应。参考 Result Handling

参考文档:webflux-special-bean-types

请求进入到 DispatcherHandler 的 handle 方法中,首先从 HandlerMapping 里找到对应的 handler 来处理请求:

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
  // ......
  @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        if (this.handlerMappings == null) {
            return createNotFoundError();
        }
        return Flux.fromIterable(this.handlerMappings)
                .concatMap(mapping -> mapping.getHandler(exchange)) // 从所有 HandlerMapping 中查找
                .next() // 取出找到的第一个 handler
                .switchIfEmpty(createNotFoundError())
                .flatMap(handler -> invokeHandler(exchange, handler)) // 通过 invokeHandler 进行调用
                .flatMap(result -> handleResult(exchange, result));
    }
  // ......
}

handler 的类型有很多,调用方式也不尽相同,因此需要由对应的 HandlerAdapter 负责具体的调用细节。DispatcherHandler 会通过 invokeHandler 方法找到对应的 HandlerAdapter 来完成调用。

从下面的源码可以知道,DispatcherHandler 会遍历所有的 HandlerAdapter,通过其 supports 方法来判断是否支持调用当前 handler,找到第一个合适的 HandlerAdapter,并由它完成 handler 的实际调用:

private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
  if (this.handlerAdapters != null) {
    for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
      if (handlerAdapter.supports(handler)) {
        return handlerAdapter.handle(exchange, handler);
      }
    }
  }
  return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}

最后,由 HandlerResultHandler 对结果进行处理,并生成响应。

private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
        return getResultHandler(result).handleResult(exchange, result)
                .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->
                        getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
    }

3. WebFlux 处理 WebSocket 请求

WebFlux 本身提供了对 WebSocket 协议的支持,处理 WebSocket 请求需要对应的 handler 实现 WebSocketHandler 接口,每一个 WebSocket 都有一个关联的 WebSocketSession,包含了建立请求时的握手信息 HandshakeInfo,以及其它相关的信息。

可以通过 session 的 receive() 方法来接收客户端的数据,通过 session 的 send() 方法向客户端发送数据。

下面是一个简单的 WebSocketHandler 示例:

@Component
public class EchoHandler implements WebSocketHandler {
    public Mono<Void> handle(WebSocketSession session) {
        return session.send(
                session.receive().map(
                        msg -> session.textMessage("ECHO -> " + msg.getPayloadAsText())));
    }
}

有了 handler 之后,还需要让 WebFlux 知道哪些请求需要交给这个 handler 进行处理,因此要创建相应的 HandlerMapping。

在处理 HTTP 请求时,我们经常使用 WebFlux 中最简单的 handler 定义方式,即通过注解 @RequestMapping 将某个方法定义为处理特定路径请求的 handler。 但是这个注解是用于处理 HTTP 请求的,对于 WebSocket 请求而言,收到请求后还需要协议升级的过程,之后才是 handler 的执行,所以我们不能直接通过该注解定义请求映射,不过可以使用 SimpleUrlHandlerMapping 来添加映射。

@Configuration
public class WebSocketConfiguration {
    @Bean
    public HandlerMapping webSocketMapping(EchoHandler echoHandler) {
        final Map<String, WebSocketHandler> map = new HashMap<>(1);
        map.put("/echo", echoHandler);

        final SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
        mapping.setUrlMap(map);
        return mapping;
    }

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

这样就能够将发往 /echo 的 WebSocket 请求交给 EchoHandler 处理。

我们还要为 WebSocket 类型的 handler 创建对应的 WebSocketHandlerAdapter,以便让 DispatcherHandler 能够调用我们的 WebSocketHandler。

完成这三个步骤后,当一个 WebSocket 请求到达 WebFlux 时,首先由 DispatcherHandler 进行处理,它会根据已有的 HandlerMapping 找到这个 WebSocket 请求对应的 handler,接着发现该 handler 实现了 WebSocketHandler 接口,于是会通过 WebSocketHandlerAdapter 来完成该 handler 的调用。

4. 用自定义注解注册 Handler

我们能否像注册 HTTP 请求的 Handler 那样,也通过类似 RequestMapping 的注解来注册 Handler 呢?

虽然官方没有相关实现,但我们可以自己实现一个类似的注解,不妨叫作 WebSocketMapping

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebSocketMapping {
    String value() default "";
}

@Retention(RetentionPolicy.RUNTIME) 表明该注解工作在运行期间,@Target(ElementType.TYPE) 表明该注解作用在类上。

我们先看下该注解最终的使用方式。下面是一个 TimeHandler 的示例,它会每秒钟会向客户端发送一次时间。我们通过注解 @WebSocketMapping("/time") 完成了 TimeHandler 的注册,告诉 WebFlux 当有 WebSocket 请求发往 /time 路径时,就交给 TimeHandler 处理:

@Component
@WebSocketMapping("/time")
public class TimeHandler implements WebSocketHandler {
    @Override
    public Mono<Void> handle(WebSocketSession session) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return session.send(Flux.interval(Duration.ofSeconds(1)).map(
          i -> session.textMessage(sdf.format(new Date()))));
    }
}

是不是和 RequestMapping 一样方便?

到目前为止,这个注解还没有实际的功能,还不能自动注册 handler。回顾我们上面注册路由的方式,我们创建了一个 SimpleUrlHandlerMapping,并手动添加了 EchoHandler 的映射规则,然后将其作为 HandlerMapping 的 Bean 返回。

现在我们要创建一个专门的 HandlerMapping 类来处理 WebSocketMapping 注解,自动完成 handler 的注册:

public class WebSocketMappingHandlerMapping extends SimpleUrlHandlerMapping {

    private Map<String, WebSocketHandler> handlerMap = new LinkedHashMap<>();

    /**
     * Register WebSocket handlers annotated by @WebSocketMapping
     * @throws BeansException
     */
    @Override
    public void initApplicationContext() throws BeansException {
        Map<String, Object> beanMap = obtainApplicationContext()
                .getBeansWithAnnotation(WebSocketMapping.class);
        beanMap.values().forEach(bean -> {
            if (!(bean instanceof WebSocketHandler)) {
                throw new RuntimeException(
                        String.format("Controller [%s] doesn't implement WebSocketHandler interface.",
                                bean.getClass().getName()));
            }
            WebSocketMapping annotation = AnnotationUtils.getAnnotation(
                    bean.getClass(), WebSocketMapping.class);
            handlerMap.put(Objects.requireNonNull(annotation).value(),
                    (WebSocketHandler) bean);
        });
        super.setOrder(Ordered.HIGHEST_PRECEDENCE);
        super.setUrlMap(handlerMap);
        super.initApplicationContext();
    }

}

我们的 WebSocketMappingHandlerMapping 类,实际上就是 SimpleUrlHandlerMapping,只不过增加了一些初始化的操作。

initApplicationContext() 方法是 Spring 中 ApplicationObjectSupport 类的方法,用于自定义类的初始化行为,在我们的 WebSocketMappingHandlerMapping 中,初始化工作主要是收集使用了 @WebSocketMapping 注解并且实现来 WebSocketHandler 接口的 Component,然后将它们注册到内部的 SimpleUrlHandlerMapping 中。之后的路由工作都是由父类 SimpleUrlHandlerMapping 已实现的功能来完成。

现在,我们只需要返回 WebSocketMappingHandlerMapping 的 Bean,就能自动处理 @WebSocketMapping 注解了:

@Configuration
public class WebSocketConfiguration {
    @Bean
    public HandlerMapping webSocketMapping() {
        return new WebSocketMappingHandlerMapping();
    }
}

5. WebSocket 请求处理过程剖析

我们来看下基于 Reactor Netty 的 WebFlux 具体是如何处理 WebSocket 请求的。

前面说过,WebSocket 请求进入 WebFlux 后,首先会从 HandlerMapping 中找到对应的 WebSocketHandler,再由 WebSocketHandlerAdapter 进行实际的调用。

那么,WebSocketHandlerAdapter 是怎么调用 handler 的呢?

public class WebSocketHandlerAdapter implements HandlerAdapter {
    //……
    public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
        WebSocketHandler webSocketHandler = (WebSocketHandler) handler;
        return getWebSocketService().handleRequest(exchange, webSocketHandler).then(Mono.empty());
      }
  //……
}

从上面代码可以看到,WebSocketHandlerAdapter 会将 WebSocket 连接的建立请求(此时仍是 HTTP)交给 WebSocketService(默认情况下为 HandshakeWebSocketService)的 handleRequest 方法进行处理,具体过程如下:

public class HandshakeWebSocketService implements WebSocketService, Lifecycle {
        //……
    public Mono<Void> handleRequest(ServerWebExchange exchange, WebSocketHandler handler) {
        ServerHttpRequest request = exchange.getRequest();
        HttpMethod method = request.getMethod();
        HttpHeaders headers = request.getHeaders();

        if (HttpMethod.GET != method) {
          return Mono.error(new MethodNotAllowedException(
              request.getMethodValue(), Collections.singleton(HttpMethod.GET)));
        }

        if (!"WebSocket".equalsIgnoreCase(headers.getUpgrade())) {
          return handleBadRequest(exchange, "Invalid 'Upgrade' header: " + headers);
        }

        List<String> connectionValue = headers.getConnection();
        if (!connectionValue.contains("Upgrade") && !connectionValue.contains("upgrade")) {
          return handleBadRequest(exchange, "Invalid 'Connection' header: " + headers);
        }

        String key = headers.getFirst(SEC_WEBSOCKET_KEY);
        if (key == null) {
          return handleBadRequest(exchange, "Missing \"Sec-WebSocket-Key\" header");
        }

        String protocol = selectProtocol(headers, handler);

        return initAttributes(exchange).flatMap(attributes ->
            this.upgradeStrategy.upgrade(exchange, handler, protocol,
                () -> createHandshakeInfo(exchange, request, protocol, attributes))
        );
    }
    //……
}

在初步判断 WebSocket 请求合法之后,会调用 UpgradeStrategy(底层使用 Reactor Netty 时该类的具体实现就是 ReactorNettyRequestUpgradeStrategy)的 upgrade 方法,将当前的 HTTP 协议升级为 WebSocket 协议,并完成 WebSocket 协议的处理:

public class ReactorNettyRequestUpgradeStrategy implements RequestUpgradeStrategy {
    //……
        public Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler handler,
            @Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) {

        ServerHttpResponse response = exchange.getResponse();
        HttpServerResponse reactorResponse = ((AbstractServerHttpResponse) response).getNativeResponse();
        HandshakeInfo handshakeInfo = handshakeInfoFactory.get();
        NettyDataBufferFactory bufferFactory = (NettyDataBufferFactory) response.bufferFactory();

        return reactorResponse.sendWebsocket(subProtocol, this.maxFramePayloadLength,
                (in, out) -> {
                    ReactorNettyWebSocketSession session =
                            new ReactorNettyWebSocketSession(
                                    in, out, handshakeInfo, bufferFactory, this.maxFramePayloadLength);
                    return handler.handle(session);
                });
    }
  //……
}

Handler 的返回值

我们处理 WebSocket 连接时,数据的发送和接收都是在 WebSocketHandler 的 handle 方法中定义的,通过 session 的 receive()send() 方法来完成,handler 对 session 处理之后(读写数据,或其它任何操作),返回的是处理结果信号,与处理 HTTP 时的返回不同。在处理 HTTP 时,我们返回的是数据流,它会被后续流程进一步处理,作为响应数据返回给客户端,而这里返回的结果信号,表示对 session 的处理(也就是对 WebSocket 连接的处理)是否成功结束。

所以回顾前面 EchoHandler 的示例,这就是为什么我们在方法末尾返回 session.send() 的调用,并将数据流放入 send() 方法中,而不是直接返回一个数据流 Flux。

@Component
public class EchoHandler implements WebSocketHandler {
    public Mono<Void> handle(WebSocketSession session) {
        return session.send(
                session.receive().map(
                        msg -> session.textMessage("ECHO -> " + msg.getPayloadAsText())));
    }
}

当我们放入 send() 方法中的数据流没有终止时,send() 就不会发出结束信号,这个例子中我们要发送的数据流来自 session.receive(),只要客户端发送数据,该方法就能得到数据,可以看作是一个无限的数据流,因此 WebSocket 会一直于连接状态。只有当客户端主动断开连接时,receive() 方法对应的数据流才会终止,此时 send() 操作完成,发出结束信号,连接随之断开。

6. 分离数据的接收与发送操作

我们知道 HTTP 协议是半双工通信,虽然客户端和服务器都能给对方发数据,但是同一时间内只会由一方向另一方发送数据,并且在顺序上是客户端先发送请求,然后才由服务器返回响应数据。所以服务器处理 HTTP 的逻辑很简单,就是每接收到一个客户端请求,就返回一个响应。

而 WebSocket 是全双工通信,客户端和服务器可以随时向另一方发送数据,所以不再是"发送请求、返回响应"的通信方式了。我们上面的 EchoHandler 示例用的仍旧是这一方式,即收到数据后再针对性地返回一条数据,我们下面就来看看如何充分利用 WebSocket 的双向通信。

WebSocket 的处理,主要是通过 session 完成对两个数据流的操作,一个是客户端发给服务器的数据流,一个是服务器发给客户端的数据流:

WebSocketSession 方法 描述
Flux<WebSocketMessage> receive() 接收来自客户端的数据流,当连接关闭时数据流结束。
Mono<Void> send(Publisher<WebSocketMessage>) 向客户端发送数据流,当数据流结束时,往客户端的写操作也会随之结束,此时返回的 Mono<Void> 会发出一个完成信号。

在 WebSocketHandler 中,最后应该将两个数据流的处理结果整合成一个信号流,并返回一个 Mono<Void> 用于表明处理是否结束。

我们分别为两个流定义处理的逻辑:

  • 对于输出流:服务器每秒向客户端发送一个数字;
  • 对于输入流:每当收到客户端消息时,就打印到标准输出
// Send a number to client every second
Mono<Void> outgoing = session.send(Flux.interval(Duration.ofSeconds(1))
                  .map(String::valueOf)
                  .map(session::textMessage)
);
// Print the message whenever received from client
Mono<Void> incoming = session.receive().map(WebSocketMessage::getPayloadAsText)
                .doOnNext(System.out::println).then();

这两个处理逻辑互相独立,它们之间没有先后关系,操作执行完之后都是返回一个 Mono<Void>,但是如何将这两个操作的结果整合成一个信号流返回给 WebFlux 呢?我们可以使用 WebFlux 中的 Mono.zip() 方法:

@Component
@WebSocketMapping("/counter")
public class CounterHandler implements WebSocketHandler {
    @Override
    public void handle(WebSocketSession session) {
        // Send a number to client every second
                Mono<Void> outgoing = session.send(Flux.interval(Duration.ofSeconds(1))
                                                                         .map(String::valueOf)
                                                                         .map(session::textMessage)
                );
                // Print the message whenever received from client
                Mono<Void> incoming = session.receive().map(WebSocketMessage::getPayloadAsText)
                                                                                             .doOnNext(System.out::println).then();
        return Mono.zip(incoming, outgoing).then();
    }
}

只要有任何一个流发出了结束信号,Mono.zip() 整合后的流也会发出结束信号,对 session 的处理也就结束了。

Mono.zip() 会将多个 Mono 合并为一个新的 Mono,任何一个 Mono 产生 error 或 complete 都会导致合并后的 Mono 也随之产生 error 或 complete,此时其它的 Mono 则会被执行取消操作。

连接关闭时执行操作

如果我们想在 WebSocket 连接关闭时进行某些后续处理,可以通过以下方式实现:

public void handle(WebSocketSession session) {
    //……
    return Mono.zip(incoming, outgoing).doFinally(signalType -> {
        // do something on close
        System.out.println("WebSocket is about to close.");
    }))
}

当客户端或网关处理完 session 后,zip() 方法会产生 complete 信号(或在出错时产生 error 信号),doFinally() 中定义的逻辑会在该信号产生后执行。

参考文档:Web on Reactive Stack # 3.2 WebSocket API

7. 从 Handler 外部发送数据

这里所说的从外部发送数据,指的是需要在 WebSocketHandler 的代码范围之外,在其它地方通过代码调用的方式向 WebSocket 连接发送数据。

思路:在定义 session 的 send() 操作时,通过编程的方式创建 Flux,即使用 Flux.create() 方法创建,将发布 Flux 数据的 FluxSink 暴露出来,并进行保存,然后在需要发送数据的地方,调用 FluxSink<T>next(T data) 方法,向 Flux 的订阅者发布数据。

create 方法是以编程方式创建 Flux 的高级形式,它允许每次产生多个数据,并且可以由多个线程产生。

create 方法将内部的 FluxSink 暴露出来,FluxSink 提供了 next、error、complete 方法。通过 create 方法,可以将响应式堆栈中的 API 与其它 API 进行连接。

考虑这么一个场景:服务器与客户端 A 建立 WebSocket 连接后,允许客户端 B 通过 HTTP 向客户端 A 发送数据。

不考虑安全性、鲁棒性等问题,我们给出一个简单的示例。

首先是 WebSocketHandler 的实现,客户端发送 WebSocket 建立请求时,需要在 query 参数中为当前连接指定一个 id,服务器会以该 id 为键,以对应的 WebSocketSender 为值存放到 senderMap 中:

@Component
@WebSocketMapping("/communicate")
public class CommunicateHandler implements WebSocketHandler {

    @Autowired
    private ConcurrentHashMap<String, WebSocketSender> senderMap;

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        HandshakeInfo handshakeInfo = session.getHandshakeInfo();
        Map<String, String> queryMap = getQueryMap(handshakeInfo.getUri().getQuery());
        String id = queryMap.getOrDefault("id", "defaultId");

        // print message received from client
        Mono<Void> input = session.receive().map(WebSocketMessage::getPayloadAsText)
                .map(msg -> id + ": " + msg).doOnNext(System.out::println).then();

        // store session and FluxSink to WebSocketSender
        Mono<Void> output = session.send(Flux.create(
                sink -> senderMap.put(id, new WebSocketSender(session, sink))
        ));

        return Mono.zip(input, output).then(Mono.fromRunnable(() -> {
            // remove sender from senderMap when processing finished
            senderMap.remove(id);
        }));
    }

    private Map<String, String> getQueryMap(String queryStr) {
        Map<String, String> queryMap = new HashMap<>();
        if (!StringUtils.isEmpty(queryStr)) {
            String[] queryParam = queryStr.split("&");
            Arrays.stream(queryParam).forEach(s -> {
                String[] kv = s.split("=", 2);
                String value = kv.length == 2 ? kv[1] : "";
                queryMap.put(kv[0], value);
            });
        }
        return queryMap;
    }
}

其中,senderMap 是我们自己定义的 Bean:

@Configuration
public class WebSocketConfiguration {
    @Bean
    public ConcurrentHashMap<String, WebSocketSender> senderMap() {
        return new ConcurrentHashMap();
    }
    //……
}

WebSocketSender 是我们自己创建的类,目的是保存 WebSocket 连接的 session 以及对应的 FluxSink,以便在 WebSocketHandler 代码范围外发送数据:

public class WebSocketSender {

    private WebSocketSession session;
    private FluxSink<WebSocketMessage> sink;

    public WebSocketSender(WebSocketSession session, FluxSink<WebSocketMessage> sink) {
        this.session = session;
        this.sink = sink;
    }

    public void sendData(String data) {
        sink.next(session.textMessage(data));
    }

}

接着我们来实现 HTTP Controller,用户在发起 HTTP 请求时,通过 query 参数指定要通信的 WebSocket 连接 id,以及要发送的数据,然后从 senderMap 中取出对应的 WebSocketSender,调用其 send() 方法向客户端发送数据:

@RestController
public class SendController {

    @Autowired
    private ConcurrentHashMap<String, WebSocketSender> senderMap;

    @RequestMapping("/send")
    public String sendMessage(@RequestParam String id, @RequestParam String data) {
        WebSocketSender sender = senderMap.get(id);
        if (sender != null) {
            sender.sendData(data);
            return String.format("Message '%s' sent to connection: %s.", data, id);
        } else {
            return String.format("Connection of id '%s' doesn't exist", id);
        }
    }

}

假设服务器运行在 app.codebelief.com 上,现在可以通过以下方式进行通信:

客户端 A 通过 ws://app.codebelief.com/communicate?id=123456 建立连接;

客户端 B 向 http://app.codebelief.com/send?id=123456&data=Hello! 发送 HTTP 请求;

此时客户端 A 会通过之前与服务器建立的 WebSocket 连接接收到客户端 B 发送的消息 "Hello!"。

8. 总结

在这边文章中,我们从 WebSocket 协议的原理入手,了解 WebSocket 连接的建立过程。之后分析了 WebFlux 处理请求的流程,并引出对 WebSocket 请求的处理。接着参考 RequestMapping 实现了自定义的注解 WebSocketMapping,用于快速注册 WebSocketHandler。然后详细剖析了 WebFlux 对 WebSocket 类型请求的具体处理细节。针对一般性的 WebSocket 请求处理,我们给出了数据接收和发送操作分开处理的方式。对于一些需要从 Handler 外部发送数据的场景,文中也给出了相应的解决方案。

由于笔者水平有限,文中表述或代码难免存在纰漏,欢迎大家批评指正,如有其它建议,也欢迎交流讨论。

发表评论

电子邮件地址不会被公开。 必填项已用*标注