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;
}
}
}