Spring Websocket+SockJS+STOMP 实现即时通信(六)—— SubProtocolWebSocketHandler

目录

    • WebsocketHandler
    • SubProtocolWebSocketHandler
      • 四个重要成员变量
        • protocolHandlerLookup
          • 子协议
          • SubProtocolHandler
        • sessions
        • clientInboundChannel
        • clientOutboundChannel


WebsocketHandler

  • 一个用来处理Websocket Messages和生命周期事件的处理程序。

在Spring中,如果我们仅仅使用 Websocket 而非 STOMP,正如官方文档所说:

“创建WebSocket服务器就像实现WebSocketHandler一样简单,或者更可能继承TextWebSocketHandler或BinaryWebSocketHandler”

只需要去实现并配置自己的 WebsocketHandler ,如下所示:

TextWebSocketHandler实现类:

     
      public
      
     
      class
      
     
      MyHandler
      
     
      extends
      
     
      TextWebSocketHandler
      {
    @Override
    
     
      public
      
     
      void
      
     
      handleTextMessage
     (WebSocketSession session, TextMessage message) {
        // ...
    }
}

SubProtocolWebSocketHandler 也是 WebSocketHandler 的一种实现,就像它的名字那样,是一种专门处理子协议(内容交换协议)的 WebSocketHandler 实现。

通过这个类, Spring 将STOMP over Websocket完美的封装起来,以至于我们完全不用去理会如何处理Websocket子协议?,仅仅要做的就是启用消息代理,然后实现自己的业务逻辑。


SubProtocolWebSocketHandler

  • 首先它是WebSocketHandler的一种实现,它将传入的 WebSocketMessage (已经被成功升级成Websocket的消息)委托给 SubProtocolHandler 以及clientInboundChannel, SubProtocolHandler 可以将Upgrade后的消息从WebSocket客户端发送到应用程序;
  • 另外它也是MessageHandler的一种实现,它找到与Message关联的WebSocketSession,并将该session与Message一起传递给 SubProtocolHandler ,用来将Message从应用程序发送回客户端。

持有四个重要成员变量:

  • Map protocolHandlerLookup =
    new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
  • Map sessions = new ConcurrentHashMap<>();
  • MessageChannel clientInboundChannel;
  • SubscribableChannel clientOutboundChannel;

四个重要成员变量

protocolHandlerLookup

“协议处理程序查找” —— 顾名思义,这个变量可以持有不止一个 SubProtocolHandler 实例,通过它我们能够查找到相应子协议的处理程序。该变量的实际类型是new TreeMap(),为了保证元素顺序。key —— 为子协议的名称,value —— 为 SubProtocolHandler 实例。

下面这段代码,可以看到如何向 protocolHandlerLookup 添加 SubProtocolHandler 实例。

SubProtocolWebSocketHandler:


     
      public
      
     
      class
      
     
      SubProtocolWebSocketHandler
     
		
     
      implements
      
     
      WebSocketHandler
     , SubProtocolCapable, MessageHandler, SmartLifecycle {
	/**
	 * 
     
      Register
      a 
     
      sub
     -
     
      protocol
      
     
      handler
     .
	 */
	
     
      public
      
     
      void
      
     
      addProtocolHandler
     (SubProtocolHandler handler) {
		List<
     
      String
     > protocols = handler.
     
      getSupportedProtocols
     ();
		if (CollectionUtils.
     
      isEmpty
     (protocols)) {
			if (logger.
     
      isErrorEnabled
     ()) {
				logger.
     
      error
     ("No 
     
      sub
     -
     
      protocols
      
     
      for
      " + handler);
			}
			
     
      return
     ;
		}
		for (String protocol : protocols) {
			SubProtocolHandler replaced = 
     
      this
     .protocolHandlerLookup.put(protocol, handler);
			if (replaced != null && replaced != handler) {
				
     
      throw
      new 
     
      IllegalStateException
     ("
     
      Cannot
      
     
      map
      " + handler +
						" to protocol '" + protocol + "': 
     
      already
      
     
      mapped
      to " + replaced + ".");
			}
		}
		
     
      this
     .protocolHandlers.add(handler);
	}
}
子协议
  • 处理WebSocket消息的约定,作为更高级别协议的一部分,在WebSocket RFC规范中称为“子协议”
SubProtocolHandler
  • 实际上 SubProtocolWebSocketHandler 所做的工作,最终都是通过 SubProtocolHandler 来完成,可以说后者就是前者的支撑实例。
  • 处理来自客户端的WebSocketMessages以及要发送给客户端的Message。
  • 可以在SubProtocolWebSocketHandler上配置此接口的实现,SubProtocolWebSocketHandler根据客户端传递的 Sec - WebSocket - Protocol 请求标头,来选择子协议处理程序以委派消息。

通过名字和注释我们很容易理解各个方法的作用,不过还是要强调两个:

1. handleMessageFromClient(WebSocketSession session, WebSocketMessage message, MessageChannel outputChannel)
将WebsocketMessage从客户端发送到应用程序。不过这里需要强调两点:

  1. 是WebsocketMessage。也就是说 HandeShake 握手请求成功,请求消息升级成WebsocketMessage后,该处理程序才会被调用,同样其他方法中所强调的连接,此时即是Websocket连接。
  2. 发送到应用程序。所以这个方法的注释和参数名,其实是错的。这里参数MessageChannel,应该是inboundChannel —— a inbound channel to send messages to application,是一个发送消息到应用程序的入栈通道。

2. handleMessageToClient(WebSocketSession session, Message message)
将消息从应用程序发送到客户端。

SubProtocolHandler :


     
      public
      
     
      interface
      
     
      SubProtocolHandler
      {
	/**
	 * Return the list of sub-protocols supported by this handler (never {@code null}).
	 */
	List<
     
      String
     > 
     
      getSupportedProtocols
     ();
	/**
	 * Handle the given {@link WebSocketMessage} received from a client.
	 * @param session the client session
	 * @param message the client message
	 * @param outputChannel an output channel to send messages to
	 */
	
     
      void
      
     
      handleMessageFromClient
     (WebSocketSession session, WebSocketMessage<?> message, MessageChannel outputChannel)
			
     
      throws
      Exception;
	/**
	 * Handle the given {@link Message} to the client associated with the given WebSocket session.
	 * @param session the client session
	 * @param message the client message
	 */
	
     
      void
      
     
      handleMessageToClient
     (WebSocketSession session, Message<?> message) 
     
      throws
      Exception;
	/**
	 * Resolve the session id from the given message or return {@code null}.
	 * @param message the message to resolve the session id from
	 */
	@Nullable
	String 
     
      resolveSessionId
     (Message<?> message);
	/**
	 * Invoked after a {@link WebSocketSession} has started.
	 * @param session the client session
	 * @param outputChannel a channel
	 */
	
     
      void
      
     
      afterSessionStarted
     (WebSocketSession session, MessageChannel outputChannel) 
     
      throws
      Exception;

	/**
	 * Invoked after a {@link WebSocketSession} has ended.
	 * @param session the client session
	 * @param closeStatus the reason why the session was closed
	 * @param outputChannel a channel
	 */
	
     
      void
      
     
      afterSessionEnded
     (WebSocketSession session, CloseStatus closeStatus, MessageChannel outputChannel)
			
     
      throws
      Exception;
}

在Spring中(截止当前Spring Framework 5.1.2 StompSubProtocolHandler —— 处理STOMP子协议目前是 SubProtocolHandler 的唯一实现,但是该接口为以后子协议的扩展提供了钩子(简直惊叹啊,这是编程者值得学习的地方)。


sessions

“会话集合” —— 维持着与该应用程序建立Websocket连接的所有WebsoketSession。这里实际类型是new ConcurrentHashMap<>(),满足应用程序的并发量,key —— WebsocketSession id,value —— WebSocketSessionHolder 实例。

afterConnectionEstablished(WebSocketSession session):
在Websocket 连接建立之后,将WebSocketSession 存入 sessions 。我们可以通过复写该方法来实现session存储的自定义。
     当然Spring早叫为我们想到了,所以它提供了 WebSocketHandlerDecorator WebSocketHandlerDecoratorFactory ,作为一个钩子,允许我们不改变代码的情况下,为 SubProtocolWebSocketHandler 增加功能,比如说“实现分布式WebsocketSession”,以此来满足我们构建分布式应用程序的需求。

SubProtocolWebSocketHandler:


     
      public
      
     
      class
      
     
      SubProtocolWebSocketHandler
     
		
     
      implements
      
     
      WebSocketHandler
     , SubProtocolCapable, MessageHandler, SmartLifecycle {
	@Override
	
     
      public
      
     
      void
      
     
      afterConnectionEstablished
     (WebSocketSession session) 
     
      throws
      Exception {
		// 
     
      WebSocketHandlerDecorator
      
     
      could
      
     
      close
      
     
      the
      
     
      session
     
		if (!session.
     
      isOpen
     ()) {
			
     
      return
     ;
		}

		
     
      this
     .stats.
     
      incrementSessionCount
     (session);
		session = 
     
      decorateSession
     (session);
		
     
      this
     .sessions.put(session.
     
      getId
     (), new 
     
      WebSocketSessionHolder
     (session));
		
     
      findProtocolHandler
     (session).
     
      afterSessionStarted
     (session, 
     
      this
     .clientInboundChannel);
	}
}

clientInboundChannel

“入站通道” —— SubProtocolWebSocketHandler实现了WebSocketHandler的handleMessage(WebSocketSession session, WebSocketMessage message) 方法,处理升级成功WebsocketMessage。

先通过 sessionId sessions 中取出对应的 WebSocketSessionHolder ,再通过 protocol 子协议类型从 protocolHandlerLookup 取出对应的 SubProtocolHandler ,再由 SubProtocolHandler 通过 StompDecoder —— STOMP 消息解码器,将ByteBuffer解码成 STOMP 格式的消息内容,然后对该内容增加更多的信息,最后由 clientInboundChannel 发送到应用程序。

SubProtocolWebSocketHandler:


     
      public
      
     
      class
      
     
      SubProtocolWebSocketHandler
     
		
     
      implements
      
     
      WebSocketHandler
     , SubProtocolCapable, MessageHandler, SmartLifecycle {
	/**
	 * 
     
      Handle
      an 
     
      inbound
      
     
      message
      
     
      from
      a 
     
      WebSocket
      
     
      client
     .
	 */
	@Override
	
     
      public
      
     
      void
      
     
      handleMessage
     (WebSocketSession session, WebSocketMessage<?> message) 
     
      throws
      Exception {
		WebSocketSessionHolder holder = 
     
      this
     .sessions.get(session.
     
      getId
     ());
		if (holder != null) {
			session = holder.
     
      getSession
     ();
		}
		SubProtocolHandler protocolHandler = 
     
      findProtocolHandler
     (session);
		protocolHandler.
     
      handleMessageFromClient
     (session, message, 
     
      this
     .clientInboundChannel);
		if (holder != null) {
			holder.
     
      setHasHandledMessages
     ();
		}
		
     
      checkSessions
     ();
	}
}

clientOutboundChannel

“出站通道” —— SubProtocolWebSocketHandler 的bean在生命周期开始时,订阅了它的成员变量 clientOutboundChannel ,并且 SubProtocolWebSocketHandler MessageHandler 的实现,以至于通过 clientOutboundChannel 发送的信息,会被该处理程序接收,通过handleMessage(Message message)方法进行处理。

先通过 sessionId 获得对应的 WebSocketSession ,再由对应的 SubProtocolHandler 调用handleMessageToClient(WebSocketSession session, Message message)对 Message 进行附加内容,之后通过 StompEcoder —— STOMP 消息编码器,将 STOMP 格式的消息内容编码成字节数组,最后由 WebSocketSession 发送到对应客户端。

SubProtocolWebSocketHandler:


     
      public
      
     
      class
      
     
      SubProtocolWebSocketHandler
     
		
     
      implements
      
     
      WebSocketHandler
     , SubProtocolCapable, MessageHandler, SmartLifecycle {
    @Override
	
     
      public
      
     
      final
      
     
      void
      
     
      start
     () {
		Assert.
     
      isTrue
     (
     
      this
     .defaultProtocolHandler != null || !
     
      this
     .protocolHandlers.
     
      isEmpty
     (), "No 
     
      handlers
     ");

		
     
      synchronized
      (
     
      this
     .lifecycleMonitor) {
			
     
      this
     .clientOutboundChannel.
     
      subscribe
     (
     
      this
     );
			
     
      this
     .running = 
     
      true
     ;
		}
	}
    /**
	 * 
     
      Handle
      an 
     
      outbound
      
     
      Spring
      
     
      Message
      to a 
     
      WebSocket
      
     
      client
     .
	 */
	@Override
	
     
      public
      
     
      void
      
     
      handleMessage
     (Message<?> message) 
     
      throws
      MessagingException {
		String sessionId = 
     
      resolveSessionId
     (message);
		if (sessionId == null) {
			if (logger.
     
      isErrorEnabled
     ()) {
				logger.
     
      error
     ("
     
      Could
      
     
      not
      
     
      find
      
     
      session
      id in " + message);
			}
			
     
      return
     ;
		}

		WebSocketSessionHolder holder = 
     
      this
     .sessions.get(sessionId);
		if (holder == null) {
			if (logger.
     
      isDebugEnabled
     ()) {
				// 
     
      The
      
     
      broker
      
     
      may
      
     
      not
      
     
      have
      
     
      removed
      
     
      the
      
     
      session
      
     
      yet
     
				logger.
     
      debug
     ("No 
     
      session
      
     
      for
      " + message);
			}
			
     
      return
     ;
		}

		WebSocketSession session = holder.
     
      getSession
     ();
		try {
			
     
      findProtocolHandler
     (session).
     
      handleMessageToClient
     (session, message);
		}
		
     
      catch
      (
     
      SessionLimitExceededException
      ex) {
			try {
				if (logger.
     
      isDebugEnabled
     ()) {
					logger.
     
      debug
     ("
     
      Terminating
      '" + session + "'", ex);
				}
				
     
      this
     .stats.
     
      incrementLimitExceededCount
     ();
				
     
      clearSession
     (session, ex.
     
      getStatus
     ()); // 
     
      clear
      
     
      first
     , 
     
      session
      
     
      may
      be 
     
      unresponsive
     
				session.
     
      close
     (ex.
     
      getStatus
     ());
			}
			
     
      catch
      (
     
      Exception
      secondException) {
				logger.
     
      debug
     ("
     
      Failure
      
     
      while
      
     
      closing
      
     
      session
      " + sessionId + ".", secondException);
			}
		}
		
     
      catch
      (
     
      Exception
      ex) {
			// 
     
      Could
      be 
     
      part
      of 
     
      normal
      
     
      workflow
      (e.g. 
     
      browser
      
     
      tab
      
     
      closed
     )
			if (logger.
     
      isDebugEnabled
     ()) {
				logger.
     
      debug
     ("
     
      Failed
      to 
     
      send
      
     
      message
      to 
     
      client
      in " + session + ": " + message, ex);
			}
		}
	}
}

你可能感兴趣的:(Spring,Websocket)