当前位置:首页 > 技术分析 > 正文内容

Spring WebFlux基于反应式WebSocket的应用

ruisui881个月前 (03-17)技术分析22

环境:Springboot2.4.13


WebSocket介绍

WebSocket协议RFC 6455提供了一种标准化的方式,通过一个TCP连接在客户端和服务器之间建立全双工、双向的通信通道。它是一个不同于HTTP的TCP协议,但设计为在HTTP之上工作,使用80和443端口,并允许重用现有的防火墙规则。

WebSocket交互开始于一个HTTP请求,使用HTTP Upgrade Header进行升级,在本例中是切换到WebSocket协议。下面的例子展示了这种交互:

GET /spring-websocket-portfolio/portfolio HTTP/1.1

Host: localhost:8080

Upgrade: websocket // The Upgrade header.
Connection: Upgrade // Using the Upgrade connection.

Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==

Sec-WebSocket-Protocol: v10.stomp, v11.stomp

Sec-WebSocket-Version: 13 Origin: http://localhost:8080

支持WebSocket的服务器会返回类似下面的输出,而不是通常的200状态码:

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=

Sec-WebSocket-Protocol: v10.stomp

握手成功后,HTTP upgrade请求的TCP套接字保持打开,客户端和服务器可以继续发送和接收消息。

对WebSockets工作原理的完整介绍超出了本文档的范围。请参阅RFC 6455、HTML5中有关WebSocket的章节,或者网上的任何介绍和教程。

注意,如果WebSocket服务器运行在web服务器(例如nginx)后面,你可能需要配置它来将 WebSocket升级请求传递给WebSocket服务器。

自定义HandlerMapping

自定义HandlerMapping是为了在项目中能够自动的失败0到N的不同请求的WebSocket连接

public class WebSocketHandlerMapping extends SimpleUrlHandlerMapping {
  
  @Override
  public void initApplicationContext() throws BeansException {
    Map handlers = new HashMap<>();
    ApplicationContext context = getApplicationContext() ;
    Map beans = context.getBeansOfType(WebSocketHandler.class) ;
    for (WebSocketHandler handler : beans.values()) {
      WebSocketMapping webSocketMapping = AnnotatedElementUtils.findMergedAnnotation(handler.getClass(), WebSocketMapping.class) ;
      if (webSocketMapping != null) {
        String value = webSocketMapping.value() ;
        if (StringUtils.hasLength(value)) {
          handlers.put(value, handler) ;
        }
      }
    }
    if (handlers.size() > 0) {
      this.setUrlMap(handlers) ;
      super.initApplicationContext();
    }
  }

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE ;
  }
  
}

在这HandlerMapping中使用了自定义的Mapping注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebSocketMapping {
  /**请求路径*/
  String value() default "" ;
}

通过上面的HandlerMapping处理能够识别出当前环境下所有带有@WebSocketMapping注解的Bean,然后进行注册到当前的URL集合中。

@Component
@WebSocketMapping("/chat2/{name}")
public class ChatWebSocketHandler2 implements WebSocketHandler {
  private static final Logger logger = LoggerFactory.getLogger(ChatWebSocketHandler2.class) ;
  public static final Map sessions = new ConcurrentHashMap<>() ;

  @Override
  public Mono handle(WebSocketSession session) {
    System.out.println(session) ;
    URI uri = session.getHandshakeInfo().getUri() ;
    String path = uri.getPath() ;
    String username = path.split("/")[2] ;
    logger.info("Client id: {} Connected, Request URI: {}", session.getId(), uri) ;
    HttpHeaders headers = session.getHandshakeInfo().getHeaders() ;
    logger.info("Request Headers: {}", headers) ;
    Mono receive = session.receive()
        .doOnNext(message -> {
          // 这里如果header中没有to,那么返回null,所以要做好判断,不然默认异常是不会被抛出的
          // 导致连接即关闭,只有加了下面的onErrorMap才能看到异常信息
          List tos = headers.get("to") ;
          if (tos !=null && !tos.isEmpty()) {
            String to = tos.get(0) ;
            WebSocketWrapper wsw = sessions.get(to) ;
            if (wsw != null) {
              String msg = message.getPayloadAsText() ;
              logger.info("给 {} 发送消息: {}", tos, msg) ;
              wsw.send(msg) ;
            }
          } else {
            logger.info("Chat 接收到消息: {}", message.getPayloadAsText());
          }
        }).onErrorMap(ex -> {
          ex.printStackTrace();
          return ex ;
        }).then() ;
    Mono sender = session.send(Flux.create(sink -> sessions.put(username, new WebSocketWrapper(session, sink)))) ;
    return Mono.zip(receive, sender).doFinally(signalType -> {
            logger.info("Client id: {}, 断开连接. 信号: {}", session.getId(), signalType.name());
            sessions.remove(username) ;
            session.close() ;
          }).then() ;
  }
}

WebSocketWrapper

public class WebSocketWrapper {
  private WebSocketSession session ;
  private FluxSink sink ; 
  public void send(String payload) {
    this.sink.next(session.textMessage(payload)) ;
  }
}

测试:

点对点消息

完毕!!!

长期创作关注不迷路!!!

Spring WebFlux使用函数式编程之HandlerFunction(1)
Spring WebFlux使用函数式编程之RouterFunction(2)
Spring WebFlux中使用WebClient远程接口调用
一文带你彻底理解Spring WebFlux的工作原理
Spring WebFlux请求处理流程
SpringBoot WebFlux整合MongoDB实现CRUD及分页功能
Spring WebFlux核心处理组件DispatcherHandler
SpringBoot WebFlux整合R2DBC实现数据库反应式编程
Spring WebFlux使用函数式编程之Filtering Handler Functions
Spring WebFlux入门实例并整合数据库实现基本的增删改查

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/2832.html

分享给朋友:

“Spring WebFlux基于反应式WebSocket的应用” 的相关文章

5个看起来像 MacOS 的 Linux 发行版,赶紧收藏!

既想使用 Linux,又想同时使用 Mac ?那么你可以尝试这些 Linux 发行版,你肯定会觉得自己在用 Mac 系统。1. Elementry OSElementry OS 是看起来像 Mac OS 的最好的 Linux 发行版。和 Mac 一样,这个操作系统也是为了保护隐私而设计的。因此你会得...

2021最全VUE面试题,奥利给

1. vue-router用过没,哪些常用的钩子函数路由钩子的执行流程,钩子函数种类有:全局守卫、路由守卫、组件守卫。首页可以控制导航跳转, beforeEach , afterEach 等,?般?于页? title 的修改。?些需要登录才能调整??的重定向功能。beforeEach 主要有3个参数...

GitLab 14.6发布,优化Geo高可用,安全更新等

昨天,GitLab官方按照管理发布了有一个月度版本GitLab 14.6的发布,这也是本年度收官版本。14.6中在安全合规性方面,在Geo方面以及MD代码块一键复制等方便做了优化,另外还在UI图标方面发布了一套全新的图标。详细情况请和虫虫一起学习。GitLab 14.6主要改进使用 Geo 实现无缝...

快速掌握 Git:程序员必会的版本控制技巧

在现代软件开发中,版本控制系统(VCS)是开发人员不可或缺的工具。无论是个人项目,还是多人协作的团队开发,良好的版本控制都能确保代码管理的高效性与稳定性。而在版本控制系统中,Git 凭借其分布式、灵活性和高效性,成为了最流行的工具之一。几乎所有的开发团队都在使用 Git 来管理代码版本、协作开发和追...

代码分支规范

一.gitflow工作流说明:主分支:master,稳定版本代码分支,对外可以随时编译发布的分支,不允许直接Push代码,只能请求合并(pull request),且只接受hotfix、release分支的代码合并。gitlab上做限制。热修复分支:hotfix,针对现场紧急问题、bug修复的代码分...

软件测试-性能测试专题方法与经验总结

本文 从 性能测试流程,性能测试指标,性能监测工具,性能测试工具,性能测试基线,性能测试策略,性能瓶颈分析方法几个维度,进行知识总结和经验分享;详细见下图总结,欢迎大家补充;性能测试经验与思考1. 性能测试流程1.1. 性格规格评审1.2. 资源排期1.2.1. 人力资源1.2.2. 时间计划· 性...