RabbitMQ Java Client源码解析——FrameHandler

FrameHandler

帧处理器接口(FrameHandler),用于处理AMQP协议的帧传输。它继承自网络连接接口,提供了发送/读取帧、设置超时、连接初始化等方法。实现类必须是线程安全的,确保帧不会交错读写。接口包含默认的心跳和连接协商方法。源码如下:

java 复制代码
/**
 * Interface to a frame handler.
 * <h2>Concurrency</h2>
 * Implementations must be thread-safe, and not allow frames to be interleaved, either while reading or writing.
 */

public interface FrameHandler extends NetworkConnection {

    default boolean internalHearbeat() {
        return false;
    }

    /**
     * Set the underlying socket's read timeout in milliseconds, if applicable.
     * @param timeoutMs The timeout in milliseconds
     */
    void setTimeout(int timeoutMs) throws SocketException;

    /**
     * Get the underlying socket's read timeout in milliseconds.
     * @return The timeout in milliseconds
     */
    int getTimeout() throws SocketException;

    /**
     * Send the initial connection header, thus kickstarting the AMQP
     * protocol version negotiation process and putting the underlying
     * connection in a state such that the next layer of startup can
     * proceed.
     * @throws IOException if there is a problem accessing the connection
     */
    void sendHeader() throws IOException;

    void initialize(AMQConnection connection);

    default void finishConnectionNegotiation() {

    }

    /**
     * Read a {@link Frame} from the underlying data connection.
     * @return an incoming Frame, or null if there is none
     * @throws IOException if there is a problem accessing the connection
     * @throws SocketTimeoutException if the underlying read times out
     */
    Frame readFrame() throws IOException;

    /**
     * Write a {@link Frame} to the underlying data connection.
     * @param frame the Frame to transmit
     * @throws IOException if there is a problem accessing the connection
     */
    void writeFrame(Frame frame) throws IOException;

    /**
     * Flush the underlying data connection.
     * @throws IOException if there is a problem accessing the connection
     */
    void flush() throws IOException;

    /** Close the underlying data connection (complaint not permitted). */
    void close();

}

其中创建FrameHandler是使用工厂模式来创建,对应的工厂接口是FrameHandlerFactory,相应源码如下:

java 复制代码
public interface FrameHandlerFactory {

    FrameHandler create(Address addr, String connectionName) throws IOException;

}

用于实现FrameHandlerFactory接口的抽象类是AbstractFrameHandlerFactory,源码如下:

java 复制代码
public abstract class AbstractFrameHandlerFactory implements FrameHandlerFactory {

    protected final int connectionTimeout;
    protected final SocketConfigurator configurator;
    protected final boolean ssl;
    protected final int maxInboundMessageBodySize;

    protected AbstractFrameHandlerFactory(int connectionTimeout, SocketConfigurator configurator,
                                          boolean ssl, int maxInboundMessageBodySize) {
        this.connectionTimeout = connectionTimeout;
        this.configurator = configurator;
        this.ssl = ssl;
        this.maxInboundMessageBodySize = maxInboundMessageBodySize;
    }
}

要想知道有哪些类型的FrameHandler,主要是看对应的FrameHandlerFactory子类即可,根据使用的通信方式分成NettyFrameHandlerFactory、SocketChannelFrameHandlerFactory和SocketChannelFrameHandlerFactory。

NettyFrameHandler

其中NettyFrameHandlerFactory相应源码如下:

java 复制代码
public final class NettyFrameHandlerFactory extends AbstractFrameHandlerFactory {

  private static final Logger LOGGER = LoggerFactory.getLogger(NettyFrameHandlerFactory.class);
  private final EventLoopGroup eventLoopGroup;
  private final Function<String, SslContext> sslContextFactory;
  private final Consumer<Channel> channelCustomizer;
  private final Consumer<Bootstrap> bootstrapCustomizer;
  private final Duration enqueuingTimeout;
  private final Predicate<ShutdownSignalException> willRecover;

  public NettyFrameHandlerFactory(
      EventLoopGroup eventLoopGroup,
      Consumer<Channel> channelCustomizer,
      Consumer<Bootstrap> bootstrapCustomizer,
      Function<String, SslContext> sslContextFactory,
      Duration enqueuingTimeout,
      int connectionTimeout,
      SocketConfigurator configurator,
      int maxInboundMessageBodySize,
      boolean automaticRecovery,
      Predicate<ShutdownSignalException> recoveryCondition) {
    super(connectionTimeout, configurator, sslContextFactory != null, maxInboundMessageBodySize);
    this.eventLoopGroup = eventLoopGroup;
    this.sslContextFactory = sslContextFactory == null ? connName -> null : sslContextFactory;
    this.channelCustomizer = channelCustomizer == null ? Utils.noOpConsumer() : channelCustomizer;
    this.bootstrapCustomizer =
        bootstrapCustomizer == null ? Utils.noOpConsumer() : bootstrapCustomizer;
    this.enqueuingTimeout = enqueuingTimeout;
    this.willRecover =
        sse -> {
          if (!automaticRecovery) {
            return false;
          } else {
            try {
              return recoveryCondition.test(sse);
            } catch (Exception e) {
              // we assume it will recover, so we take the safe path to dispatch the closing
              // it avoids the risk of deadlock
              return true;
            }
          }
        };
  }

  @Override
  public FrameHandler create(Address addr, String connectionName) throws IOException {
    SslContext sslContext = this.sslContextFactory.apply(connectionName);
    return new NettyFrameHandler(
        this.maxInboundMessageBodySize,
        addr,
        sslContext,
        this.eventLoopGroup,
        this.enqueuingTimeout,
        this.willRecover,
        this.channelCustomizer,
        this.bootstrapCustomizer);
  }

在NettyFrameHandlerFactory类中,创建FrameHandler是构建NettyFrameHandler实例来实现的。NettyFrameHandler的源码如下:

java 复制代码
  private static final class NettyFrameHandler implements FrameHandler {

    private static final String HANDLER_FLUSH_CONSOLIDATION =
        FlushConsolidationHandler.class.getSimpleName();
    private static final String HANDLER_FRAME_DECODER =
        LengthFieldBasedFrameDecoder.class.getSimpleName();
    private static final String HANDLER_READ_TIMEOUT = ReadTimeoutHandler.class.getSimpleName();
    private static final String HANDLER_IDLE_STATE = IdleStateHandler.class.getSimpleName();
    private static final String HANDLER_PROTOCOL_VERSION_MISMATCH =
        ProtocolVersionMismatchHandler.class.getSimpleName();
    private static final byte[] HEADER =
        new byte[] {
          'A', 'M', 'Q', 'P', 0, AMQP.PROTOCOL.MAJOR, AMQP.PROTOCOL.MINOR, AMQP.PROTOCOL.REVISION
        };
    private final EventLoopGroup eventLoopGroup;
    private final Duration enqueuingTimeout;
    private final Channel channel;
    private final AmqpHandler handler;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    private NettyFrameHandler(
        int maxInboundMessageBodySize,
        Address addr,
        SslContext sslContext,
        EventLoopGroup elg,
        Duration enqueuingTimeout,
        Predicate<ShutdownSignalException> willRecover,
        Consumer<Channel> channelCustomizer,
        Consumer<Bootstrap> bootstrapCustomizer)
        throws IOException {
      this.enqueuingTimeout = enqueuingTimeout;
      Bootstrap b = new Bootstrap();
      bootstrapCustomizer.accept(b);
      if (b.config().group() == null) {
        if (elg == null) {
          elg = Utils.eventLoopGroup();
          this.eventLoopGroup = elg;
        } else {
          this.eventLoopGroup = null;
        }
        b.group(elg);
      } else {
        this.eventLoopGroup = null;
      }

      if (b.config().group() == null) {
        throw new IllegalStateException("The event loop group is not set");
      } else if (b.config().group().isShuttingDown()) {
        LOGGER.warn("The Netty loop group was shut down, it is not possible to connect or recover");
        throw new IllegalStateException("The event loop group was shut down");
      }

      if (b.config().channelFactory() == null) {
        b.channel(NioSocketChannel.class);
      }
      if (!b.config().options().containsKey(ChannelOption.SO_KEEPALIVE)) {
        b.option(ChannelOption.SO_KEEPALIVE, true);
      }
      if (!b.config().options().containsKey(ChannelOption.ALLOCATOR)) {
        b.option(ChannelOption.ALLOCATOR, Utils.byteBufAllocator());
      }

      // type + channel + payload size + payload + frame end marker
      int maxFrameLength = 1 + 2 + 4 + maxInboundMessageBodySize + 1;
      int lengthFieldOffset = 3;
      int lengthFieldLength = 4;
      int lengthAdjustement = 1;
      AmqpHandler amqpHandler =
          new AmqpHandler(maxInboundMessageBodySize, this::close, willRecover);
      int port = ConnectionFactory.portOrDefault(addr.getPort(), sslContext != null);
      b.handler(
          new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) {
              ch.pipeline()
                  .addFirst(
                      HANDLER_FLUSH_CONSOLIDATION,
                      new FlushConsolidationHandler(
                          FlushConsolidationHandler.DEFAULT_EXPLICIT_FLUSH_AFTER_FLUSHES, true));
              ch.pipeline()
                  .addLast(HANDLER_PROTOCOL_VERSION_MISMATCH, new ProtocolVersionMismatchHandler());
              ch.pipeline()
                  .addLast(
                      HANDLER_FRAME_DECODER,
                      new LengthFieldBasedFrameDecoder(
                          maxFrameLength,
                          lengthFieldOffset,
                          lengthFieldLength,
                          lengthAdjustement,
                          0));
              ch.pipeline().addLast(AmqpHandler.class.getSimpleName(), amqpHandler);
              if (sslContext != null) {
                SslHandler sslHandler = sslContext.newHandler(ch.alloc(), addr.getHost(), port);
                ch.pipeline().addFirst("ssl", sslHandler);
              }
              if (channelCustomizer != null) {
                channelCustomizer.accept(ch);
              }
            }
          });
      ChannelFuture cf = null;
      try {
        cf = b.connect(addr.getHost(), port).sync();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new IOException("Error when opening network connection", e);
      } catch (Exception e) {
        if (e.getCause() instanceof IOException) {
          throw (IOException) e.getCause();
        } else {
          throw new IOException("Error when opening network connection", e);
        }
      }
      this.channel = cf.channel();
      this.handler = amqpHandler;
    }

    @Override
    public boolean internalHearbeat() {
      return true;
    }

    @Override
    public int getTimeout() {
      return this.channel.getOption(ChannelOption.SO_TIMEOUT);
    }

    @Override
    public void setTimeout(int timeoutMs) {
      this.maybeRemoveHandler(HANDLER_READ_TIMEOUT);
      timeoutMs = Math.max(1000, timeoutMs * 4);
      this.channel
          .pipeline()
          .addBefore(
              HANDLER_FRAME_DECODER,
              HANDLER_READ_TIMEOUT,
              new ReadTimeoutHandler(timeoutMs, MILLISECONDS));
      if (handler.connection != null) {
        Duration heartbeat = Duration.ofSeconds(handler.connection.getHeartbeat());
        maybeRemoveHandler(HANDLER_IDLE_STATE);
        if (heartbeat.toMillis() > 0) {
          this.channel
              .pipeline()
              .addBefore(
                  HANDLER_FRAME_DECODER,
                  HANDLER_IDLE_STATE,
                  new IdleStateHandler(
                      (int) heartbeat.multipliedBy(2).getSeconds(),
                      (int) heartbeat.getSeconds(),
                      0));
        }
      }
    }

    private void maybeRemoveHandler(String name) {
      if (this.channel.pipeline().get(name) != null) {
        this.channel.pipeline().remove(name);
      }
    }

    @Override
    public void sendHeader() {
      ByteBuf bb = this.channel.alloc().buffer(HEADER.length);
      bb.writeBytes(HEADER);
      this.channel.writeAndFlush(bb);
    }

    @Override
    public void initialize(AMQConnection connection) {
      LOGGER.debug(
          "Setting connection {} to AMQP handler {}",
          connection.getClientProvidedName(),
          this.handler.id);
      this.handler.connection = connection;
    }

    @Override
    public void finishConnectionNegotiation() {
      maybeRemoveHandler(HANDLER_PROTOCOL_VERSION_MISMATCH);
    }

    @Override
    public Frame readFrame() {
      throw new UnsupportedOperationException();
    }

    @Override
    public void writeFrame(Frame frame) throws IOException {
      if (this.handler.isWritable()) {
        this.doWriteFrame(frame);
      } else {
        if (this.channel.eventLoop().inEventLoop()) {
          // we do not wait in the event loop
          this.doWriteFrame(frame);
        } else {
          // we get the current latch
          CountDownLatch latch = this.handler.writableLatch();
          if (this.handler.isWritable()) {
            // the channel became  writable
            this.doWriteFrame(frame);
          } else {
            try {
              // the channel is still non-writable
              // in case its writability flipped, we have a reference to a latch that has been
              // counted down
              // so, worst case scenario, we'll enqueue only one frame right away
              boolean canWriteNow = latch.await(enqueuingTimeout.toMillis(), MILLISECONDS);
              if (canWriteNow) {
                this.doWriteFrame(frame);
              } else {
                throw new IOException("Frame enqueuing failed");
              }
            } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
            }
          }
        }
      }
    }

    private void doWriteFrame(Frame frame) throws IOException {
      ByteBuf bb = this.channel.alloc().buffer(frame.size());
      frame.writeToByteBuf(bb);
      this.channel.writeAndFlush(bb);
    }

    @Override
    public void flush() {
      this.channel.flush();
    }

    @Override
    public void close() {
      if (this.closed.compareAndSet(false, true)) {
        Runnable closing = () -> closeNettyState(this.channel, this.eventLoopGroup);
        if (this.channel.eventLoop().inEventLoop()) {
          this.channel.eventLoop().submit(closing);
        } else {
          closing.run();
        }
      }
    }

    @Override
    public InetAddress getLocalAddress() {
      InetSocketAddress addr = maybeInetSocketAddress(this.channel.localAddress());
      return addr == null ? null : addr.getAddress();
    }

    @Override
    public int getLocalPort() {
      InetSocketAddress addr = maybeInetSocketAddress(this.channel.localAddress());
      return addr == null ? -1 : addr.getPort();
    }

    @Override
    public InetAddress getAddress() {
      InetSocketAddress addr = maybeInetSocketAddress(this.channel.remoteAddress());
      return addr == null ? null : addr.getAddress();
    }

    @Override
    public int getPort() {
      InetSocketAddress addr = maybeInetSocketAddress(this.channel.remoteAddress());
      return addr == null ? -1 : addr.getPort();
    }

    InetSocketAddress maybeInetSocketAddress(SocketAddress socketAddress) {
      if (socketAddress instanceof InetSocketAddress) {
        return (InetSocketAddress) socketAddress;
      } else {
        return null;
      }
    }
  }
相关推荐
毕设源码-赖学姐2 小时前
【开题答辩全过程】以 果树的生长信息管理系统为例,包含答辩的问题和答案
java·spring boot
CodeCraft Studio2 小时前
国产化PDF处理控件Spire.PDF教程:在Java快速解析PDF文本、表格、图像和元数据
java·python·pdf·pdf解析·spire.pdf·元数据解析·java pdf解析
CryptoRzz2 小时前
墨西哥股票数据 API 对接实战指南(含实时行情与 IPO 功能)
java·后端·websocket·区块链
hgz07102 小时前
Spring Boot自动配置
java·springboot
@淡 定2 小时前
慢查询分析与优化
java
大学生资源网2 小时前
基于springboot的南京特色美食小吃商城(源码+文档)
java·spring boot·后端·mysql·毕业设计·源码
月明长歌2 小时前
【码道初阶】【LeetCode387】如何高效找到字符串中第一个不重复的字符?
java·开发语言·数据结构·算法·leetcode·哈希算法
Filotimo_2 小时前
在java后端开发中,kafka的用处
java·开发语言
Seven972 小时前
剑指offer-55、链表中环的⼊⼝节点
java