[RabbitMQ]Java客户端:源码概览
本文简要介绍RabbitMQ提供的Java客户端中最基本的功能性接口/类及相关源码。
Mavan依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.13.1</version>
</dependency>
0 AMQP
com.rabbitmq.client.AMQP
接口将AMQP(Advanced Message Queue Protocol,高级消息队列协议)中的方法和消息属性封装成Java对象,便于以面向对象的思维模式进行编程。
该接口类图简要如下:
AMQP
接口中包含许多内部类,大体可以分成三类:
0.1 协议信息
PROTOCOL
内部类,保存了AMQP的协议版本等信息。
public static class PROTOCOL {
public static final int MAJOR = 0;
public static final int MINOR = 9;
public static final int REVISION = 1;
public static final int PORT = 5672;
}
0.2 方法
包括Connection
、Channel
、Access
、Exchange
、Queue
、Basic
、Tx
和Confirm
内部类,分别封装了向Broker
发送的不同方法的基本数据格式和内容。
它们都实现了com.rabbitmq.client.impl.Method
抽象类(后续会介绍),在发送请求时,通过Method
抽象类的toFrame()
方法可以转换成Frame
(帧),然后com.rabbitmq.client.impl.AMQConnection
将其以二进制数据的方式通过TCP
协议发送给Broker
。
它们都提供了各自的Builder
,便于实例化方法对象(建造者模式)。例如,最常用的Publish
方法类图简要如下:
通过如下代码实例化出Publish
对象:
AMQP.Basic.Publish publish = new AMQP.Basic.Publish.Builder().exchange("myExchange").routingKey("myRoutingKey").mandatory(true).build();
在发送给Broker
前可以通过如下代码将Publish
对象转成帧:
Method method = (Method) publish;
Frame frame = method.toFrame(1);
com.rabbitmq.client.impl.AMQConnection
对象管理着与Broker
的连接,它通过如下代码将方法发送给Broker
:
AMQConnection connection = channel.getConnection();
connection.writeFrame(frame);
0.3 消息属性
BasicProperties
内部类,封装了消息的基本属性。
它也提供了Builder
,我们在发送消息时可以使用BasicProperties
实例携带消息头信息,类图如下:
通过如下代码实例化出BasicProperties
对象,并发送消息:
Map<String, Object> headers = new HashMap<>();
headers.put("color", "blue");
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().headers(headers).expiration("10000").build();
channel.basicPublish("myExchange", "myRoutingKey", true, properties, "hello".getBytes());
BasicProperties
对象在发送前最终会被转换成com.rabbitmq.client.impl.AMQContentHeader
对象,代表AMQ
消息内容的头。
AMQContentHeader
的toFrame()
方法也可以将其转换成Frame
(帧),然后com.rabbitmq.client.impl.AMQConnection
将其以二进制数据的方式通过TCP
协议发送给Broker
。
AMQConnection connection = channel.getConnection();
Frame headerFrame = contentHeader.toFrame(channelNumber, body.length);
connection.writeFrame(headerFrame);
1 ConnectionFactory
com.rabbitmq.client.ConnectionFactory
类是用来创建与RabbitMQ服务器连接(com.rabbitmq.client.Connection
)的工厂类。简要类图如下:
ConnectionFactory
内部封装了许多属性,用来设置与Connection
或Socket
相关的连接信息。
它还提供了一套默认配置:
public static final String DEFAULT_VHOST = "/";
public static final String DEFAULT_HOST = "localhost";
public static final int DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT; // 5672
public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671;
public static final String DEFAULT_PASS = "guest"; // CredentialsProvider.username
public static final String DEFAULT_USER = "guest"; // CredentialsProvider.password
private boolean nio = false;
private boolean automaticRecovery = true;
ConnectionFactory
的基本使用如下:
ConnectionFactory connectionFactory = new ConnectionFactory();
Connection connection = connectionFactory.newConnection();//返回RecoveryAwareAMQConnection或AMQConnection对象
底层会创建出java.net.Socket
或java.nio.channels.SocketChannel
,代表与RabbitMQ服务器的TCP
连接:
Socket socket = SocketFactory.getDefault().createSocket();
SocketChannel channel = SocketChannel.open();
Socket
或SocketChannel
会被封装到com.rabbitmq.client.impl.FrameHandler
中:
// nio==false,使用Socket(默认值)
SocketFrameHandler frameHandler = new SocketFrameHandler(sock, this.shutdownExecutor);
// nio==true,使用SocketChannel
SocketChannelFrameHandlerState state = new SocketChannelFrameHandlerState(channel, nioLoopContext, nioParams, sslEngine);
SocketChannelFrameHandler frameHandler = new SocketChannelFrameHandler(state);
FrameHandler
中提供了readFrame()
和writeFrame()
,分别可以从Socket/SocketChannel
中读取或写入数据。
FrameHandler
又会被封装到com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnection
或com.rabbitmq.client.impl.AMQConnection
中:
// automaticRecovery==true,默认值
FrameHandler frameHandler = factory.create(addr, connectionName());
RecoveryAwareAMQConnection conn = createConnection(params, frameHandler, metricsCollector);
// automaticRecovery==false
FrameHandler handler = fhFactory.create(addr, clientProvidedName);
AMQConnection conn = createConnection(params, handler, metricsCollector);
因此,我们可以使用返回的Connection
对象与RabbitMQ服务器进行交互。
2 Connection
com.rabbitmq.client.Connection
接口代表与RabbitMQ服务器的TCP连接,类图简要如下:
Connection
主要提供了createChannel()
和openChannel()
方法,用来创建Channel
。后者提供了几乎所有与RabbitMQ进行交互的方法,是项目中使用频率最高的一个接口。
Connection
的基本使用如下:
Channe channel = connection.createChannel();
Connection
的实现类主要包括以下几种,分别代表不同类型的连接:
AMQConnection
类:代表最基本的与RabbitMQ服务器的连接。内部持有FrameHandler
等成员变量,用来与服务器交互。RecoveryAwareAMQConnection
接口:代表自动重连的连接,内部没有方法,类似与标志性接口。AutorecoveringConnection
类:自动重连Connection
的实现类,在非正常断开情况下会自动重连,例如I/O
异常。它持有RecoveryAwareAMQConnection
对象作为代理,从而间接可以使用FrameHandler
对象与服务器进行交互。重连时,内部组件也会按如下顺序自动重连:Exchanges
Queues
Bindings
(both queue and exchange-to-exchange)Consumers
RecoveryAwareAMQConnection
:它是对AMQConnection
的修改,主要用作AutorecoveringConnection
的成员变量。它与AMQConnection
主要区别在于它内部使用com.rabbitmq.client.impl.recovery.RecoveryAwareChannelN
作为Channel
。
在项目中使用的实现类主要为AMQConnection
和AutorecoveringConnection
(根据ConnectionFactory
的automaticRecovery
成员变量进行选择)。
AMQConnection.createChannel()
方法会使用ChannelManager
创建出ChannelN
类型的通道:
ChannelManager cm = _channelManager;
Channel channel = cm.createChannel(this);
// 底层:
new ChannelN(connection, channelNumber, workService, this.metricsCollector);
而AutorecoveringConnection.createChannel()
方法会使用RecoveryAwareAMQConnection
创建出RecoveryAwareChannelN
类型的通道,并使用AutorecoveringChannel
包装:
RecoveryAwareChannelN ch = (RecoveryAwareChannelN) delegate.createChannel();
final AutorecoveringChannel channel = new AutorecoveringChannel(this, delegateChannel);
AMQConnection
中有一个十分重要的方法writeFrame()
,可以将数据发送给RabbitMQ服务器:
public void writeFrame(Frame f) throws IOException {
_frameHandler.writeFrame(f);
_heartbeatSender.signalActivity();
}
// SocketFrameHandler
public void writeFrame(Frame frame) throws IOException {
synchronized (_outputStream) {
frame.writeTo(_outputStream);
}
}
public void writeTo(DataOutputStream os) throws IOException {
os.writeByte(type);
os.writeShort(channel);
if (accumulator != null) {
os.writeInt(accumulator.size());
accumulator.writeTo(os);
} else {
os.writeInt(payload.length);
os.write(payload);
}
os.write(AMQP.FRAME_END);
}
3 Channel
com.rabbitmq.client.Channel
中封装了与RabbitMQ服务器交互的API,简要类图如下:
Channel
的基本使用方式如下:
// 声明交换机
channel.exchangeDeclare("myExchange", BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare("myQueue", true, false, false, null);
// 声明绑定
channel.exchangeBind("myQueue", "myExchange", "myRoutingKey");
// 发送消息
channel.basicPublish("myExchange", "myRoutingKey", true, null, "hello".getBytes());
// 订阅消息
channel.basicConsume("myQueue", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
// 拉取消息
channel.basicGet("myQueue", true);
Channel
是实现类包括以下几种:
ChannelN
:AMQP
协议功能API
的主要实现类。RecoveryAwareChannelN
:重写了basicAck()
、basicReject()
和basicNack()
方法,对ChannelN
功能进行扩展,实时跟踪delivery tag
,对最新的tag
进行响应。AutorecoveringChannel
:在connection
重连时会自动恢复的通道,内部通过持有RecoveryAwareChannelN
代理对象来实现具体操作。
3.1 ChannelN
com.rabbitmq.client.impl.ChannelN
是对AMQP
协议功能性API
的主要实现类,它除了实现Channel
中定义的AMQP
协议功能性API
,还继承了AMQChannel
抽象类,通过其_connection
成员变量可以在底层调用到Socket
或SocketChannel
向RabbitMQ服务器进行读写操作。
除此之外,为了实现AMQP
协议的特定功能,如消息确认机制。ChannelN
内部封装了如下成员变量:
_consumers
:消息消费者,以consumerTag
作为key
,用于监听消息。returnListeners
:监听RabbitMQ服务器找不到对应交换机时的返回消息(basicPublish
方法发送消息时设置mandatory
或immediate
)。confirmListeners
:监听RabbitMQ服务器的确认消息(ack
或nack
)。defaultConsumer
:默认的消息消费者。dispatcher
:启动线程执行_consumers
中的任务。
ChannelN
中监听消息的核心源码如下:
public boolean processAsync(Command command) throws IOException {
Method method = command.getMethod();
if (method instanceof Channel.Close) {
asyncShutdown(command);
return true;
}
if (isOpen()) {
// 根据不同方法类型调用对应的处理方法
if (method instanceof Basic.Deliver) {
processDelivery(command, (Basic.Deliver) method);
return true;
} else if (method instanceof Basic.Return) {
callReturnListeners(command, (Basic.Return) method);
return true;
} else if (method instanceof Channel.Flow) {
Channel.Flow channelFlow = (Channel.Flow) method;
synchronized (_channelMutex) {
_blockContent = !channelFlow.getActive();
transmit(new Channel.FlowOk(!_blockContent));
_channelMutex.notifyAll();
}
return true;
} else if (method instanceof Basic.Ack) {
Basic.Ack ack = (Basic.Ack) method;
callConfirmListeners(command, ack);
handleAckNack(ack.getDeliveryTag(), ack.getMultiple(), false);
return true;
} else if (method instanceof Basic.Nack) {
Basic.Nack nack = (Basic.Nack) method;
callConfirmListeners(command, nack);
handleAckNack(nack.getDeliveryTag(), nack.getMultiple(), true);
return true;
} else if (method instanceof Basic.RecoverOk) {
for (Map.Entry<String, Consumer> entry : Utility.copy(_consumers).entrySet()) {
this.dispatcher.handleRecoverOk(entry.getValue(), entry.getKey());
}
return false;
} else if (method instanceof Basic.Cancel) {
Basic.Cancel m = (Basic.Cancel)method;
String consumerTag = m.getConsumerTag();
Consumer callback = _consumers.remove(consumerTag);
if (callback == null) {
callback = defaultConsumer;
}
if (callback != null) {
try {
this.dispatcher.handleCancel(callback, consumerTag);
} catch (WorkPoolFullException e) {
throw e;
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConsumerException(this,
ex,
callback,
consumerTag,
"handleCancel");
}
} else {
LOGGER.warn("Could not cancel consumer with unknown tag {}", consumerTag);
}
return true;
} else {
return false;
}
} else {
if (method instanceof Channel.CloseOk) {
return false;
} else {
return true;
}
}
}
可见,该方法类似于SpringMVC中的DispatcherServlet
,它会根据监听到Command
对象的方法类型进行分发处理。接下来介绍的各成员变量方法调用的入口都在这个方法中。
3.1.1 ConsumerDispatcher
com.rabbitmq.client.impl.ConsumerDispatcher
的作用是从线程池中获取空闲线程处理消息。它的主要作用是开启线程,而实际处理消息的业务逻辑在具体Consumer
代理对象中处理。
例如,在处理生产者发布的消息时,ConsumerDispatcher
会进行如下处理:
public void handleDelivery(final Consumer delegate,
final String consumerTag,
final Envelope envelope,
final AMQP.BasicProperties properties,
final byte[] body) throws IOException {
executeUnlessShuttingDown(
new Runnable() {
@Override
public void run() {
try {
delegate.handleDelivery(consumerTag,
envelope,
properties,
body);
} catch (Throwable ex) {
connection.getExceptionHandler().handleConsumerException(
channel,
ex,
delegate,
consumerTag,
"handleDelivery");
}
}
});
}
3.1.2 Consumer
com.rabbitmq.client.Consumer
接口中定义了不同消息的处理方法,实例对象则表示消息消费者。
com.rabbitmq.client.DefaultConsumer
是默认实现类,它实现了接口中的所有方法(空方法)。我们可以采取匿名内部类的方式,实现具体某个需要的方法,而不是实现所有方法。
我们可以使用如下代码添加消费者:
channel.basicConsume("myQueue", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
在ChannelN
中调用消费者处理消息方法(handleDelivery()
)的源码如下:
protected void processDelivery(Command command, Basic.Deliver method) {
Basic.Deliver m = method;
Consumer callback = _consumers.get(m.getConsumerTag());
if (callback == null) {
if (defaultConsumer == null) {
throw new IllegalStateException("Unsolicited delivery -" +
" see Channel.setDefaultConsumer to handle this" +
" case.");
}
else {
callback = defaultConsumer;
}
}
Envelope envelope = new Envelope(m.getDeliveryTag(),
m.getRedelivered(),
m.getExchange(),
m.getRoutingKey());
try {
metricsCollector.consumedMessage(this, m.getDeliveryTag(), m.getConsumerTag());
this.dispatcher.handleDelivery(callback,
m.getConsumerTag(),
envelope,
(BasicProperties) command.getContentHeader(),
command.getContentBody());
} catch (WorkPoolFullException e) {
throw e;
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConsumerException(this,
ex,
callback,
m.getConsumerTag(),
"handleDelivery");
}
}
需要注意的是,在调用 this.dispatcher.handleDelivery()
之前,会首先调用Consumer callback = _consumers.get(m.getConsumerTag())
根据consumerTag
获取对应的消费者。因此,消费者处理消息是一对一的。
消费者其他方法的调用也可以在ChannelN.processAsync()
中找到。
3.1.3 ReturnListener
com.rabbitmq.client.ReturnListener
接口中定义了监听返回消息的通用方法handleReturn()
,主要用于消息发布者监听返回消息。
消息发布者通过basicPublish
方法发送消息时设置mandatory
或immediate
,但RabbitMQ服务器找不到对应交换机时会返回消息。消息发布者通过往Channel
对象中添加ReturnListener
实现类,即可监听到返回消息:
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("return message: " + new String(body));
}
});
在ChannelN
中处理返回消息的源码如下:
private void callReturnListeners(Command command, Basic.Return basicReturn) {
try {
for (ReturnListener l : this.returnListeners) {
l.handleReturn(basicReturn.getReplyCode(),
basicReturn.getReplyText(),
basicReturn.getExchange(),
basicReturn.getRoutingKey(),
(BasicProperties) command.getContentHeader(),
command.getContentBody());
}
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleReturnListenerException(this, ex);
} finally {
metricsCollector.basicPublishUnrouted(this);
}
}
ReturnListener
是针对ChannelN
级别的。接收到返回消息后,所有添加到ChannelN
对象的ReturnListener
监听器都会被调用。
3.1.4 ConfirmListener
com.rabbitmq.client.ConfirmListener
接口中定义的监听RabbitMQ服务器确认消息(ack
或nack
)的回调方法,主要用于消息发布者使用。
基本使用代码如下:
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// 业务处理
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
// 业务处理
}
});
在ChannelN
中处理返回消息的源码如下:
private void callConfirmListeners(@SuppressWarnings("unused") Command command, Basic.Ack ack) {
try {
for (ConfirmListener l : this.confirmListeners) {
l.handleAck(ack.getDeliveryTag(), ack.getMultiple());
}
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConfirmListenerException(this, ex);
} finally {
metricsCollector.basicPublishAck(this, ack.getDeliveryTag(), ack.getMultiple());
}
}
private void callConfirmListeners(@SuppressWarnings("unused") Command command, Basic.Nack nack) {
try {
for (ConfirmListener l : this.confirmListeners) {
l.handleNack(nack.getDeliveryTag(), nack.getMultiple());
}
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConfirmListenerException(this, ex);
} finally {
metricsCollector.basicPublishNack(this, nack.getDeliveryTag(), nack.getMultiple());
}
}
ConfirmListener
是针对ChannelN
级别的。接收到确认消息后,所有添加到ChannelN
对象的ConfirmListener
监听器都会被调用。
3.1.5 basicPublish()
前几小节讲述的都是RabbitMQ客户端监听从服务器响应的消息,本小节简要分析客户端发送消息的流程。
发送消息的基本方式如下:
channel.basicPublish("myExchange", "myRoutingKey", null, "hello".getBytes());
- 在
ChannelN
中的basicPublish()
方法中执行如下代码,核心步骤如下:- 将形参转换成
AMQCommand
对象中的CommandAssembler
成员变量:exchange
和routingKey
→Basic.Publish
方法对象(Method
),properties
→AMQContentHeader
对象,body
→List<byte[]>
对象。 - 调用
transmit(command)
方法,发送命令。
- 将形参转换成
public void basicPublish(String exchange, String routingKey,
boolean mandatory, boolean immediate,
BasicProperties props, byte[] body)
throws IOException
{
if (nextPublishSeqNo > 0) {
unconfirmedSet.add(getNextPublishSeqNo());
nextPublishSeqNo++;
}
if (props == null) {
props = MessageProperties.MINIMAL_BASIC;
}
AMQCommand command = new AMQCommand(
new Basic.Publish.Builder()
.exchange(exchange)
.routingKey(routingKey)
.mandatory(mandatory)
.immediate(immediate)
.build(), props, body);
try {
transmit(command);
} catch (IOException e) {
metricsCollector.basicPublishFailure(this, e);
throw e;
}
metricsCollector.basicPublish(this);
}
- 在
ChannelN
中执行transmit()
和quiescingTransmit()
方法,最终会调用AMQCommand.transmit()
方法:
public void transmit(AMQCommand c) throws IOException {
synchronized (_channelMutex) {
ensureIsOpen();
quiescingTransmit(c);
}
}
public void quiescingTransmit(AMQCommand c) throws IOException {
synchronized (_channelMutex) {
if (c.getMethod().hasContent()) {
while (_blockContent) {
try {
_channelMutex.wait();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
ensureIsOpen();
}
}
this._trafficListener.write(c);
c.transmit(this);
}
}
- 在
AMQCommand
中执行transmit()
方法,核心步骤如下:- 获取
AMQConnection
对象。 - 分别将
AMQContentHeader
、Method
和List<byte[]>
对象转换成Frame
对象。 - 通过
AMQConnection
对象发送数据。
- 获取
public void transmit(AMQChannel channel) throws IOException {
int channelNumber = channel.getChannelNumber();
AMQConnection connection = channel.getConnection();
synchronized (assembler) {
Method m = this.assembler.getMethod();
if (m.hasContent()) {
byte[] body = this.assembler.getContentBody();
Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length);
int frameMax = connection.getFrameMax();
boolean cappedFrameMax = frameMax > 0;
int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length;
if (cappedFrameMax && headerFrame.size() > frameMax) {
String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
throw new IllegalArgumentException(msg);
}
connection.writeFrame(m.toFrame(channelNumber));
connection.writeFrame(headerFrame);
for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
int remaining = body.length - offset;
int fragmentLength = (remaining < bodyPayloadMax) ? remaining
: bodyPayloadMax;
Frame frame = Frame.fromBodyFragment(channelNumber, body,
offset, fragmentLength);
connection.writeFrame(frame);
}
} else {
connection.writeFrame(m.toFrame(channelNumber));
}
}
connection.flush();
}
3.1.6 basicGet()
除了添加Comsumer
监听器,我们还可以主动调用basicGet()
向RabbitMQ服务器“拉取”消息。
basicGet()
方法本质上是向RabbitMQ服务器发送一个Basic.Get
请求,然后等待响应。
basicGet()
方法的基本使用如下:
// 自动确认模式
GetResponse message = channel.basicGet("myQueue", true);
System.out.println(new String(message.getBody()));
// 手动确认模式
GetResponse myQueue = channel.basicGet("myQueue", false);
System.out.println(new String(message.getBody()));
channel.basicAck(myQueue.getEnvelope().getDeliveryTag(), false);
在
ChannelN
中的basicGet()
方法中执行如下代码,核心步骤如下:- 将形参转换成
AMQCommand
对象中的CommandAssembler
成员变量:queue
→Basic.Get
方法对象(Method
),properties
→AMQContentHeader
对象,body
→List<byte[]>
对象。 - 调用
exnWrappingRpc(command)
方法,发送命令。 - 等待响应
replyCommand
,并封装成GetResponse
对象返回,
- 将形参转换成
public GetResponse basicGet(String queue, boolean autoAck) throws IOException{
validateQueueNameLength(queue);
AMQCommand replyCommand = exnWrappingRpc(new Basic.Get.Builder()
.queue(queue)
.noAck(autoAck)
.build());
Method method = replyCommand.getMethod();
if (method instanceof Basic.GetOk) {
Basic.GetOk getOk = (Basic.GetOk)method;
Envelope envelope = new Envelope(getOk.getDeliveryTag(),
getOk.getRedelivered(),
getOk.getExchange(),
getOk.getRoutingKey());
BasicProperties props = (BasicProperties)replyCommand.getContentHeader();
byte[] body = replyCommand.getContentBody();
int messageCount = getOk.getMessageCount();
metricsCollector.consumedMessage(this, getOk.getDeliveryTag(), autoAck);
return new GetResponse(envelope, props, body, messageCount);
} else if (method instanceof Basic.GetEmpty) {
return null;
} else {
throw new UnexpectedMethodError(method);
}
}
public AMQCommand exnWrappingRpc(Method m) throws IOException {
try {
return privateRpc(m);
} catch (AlreadyClosedException ace) {
throw ace;
} catch (ShutdownSignalException ex) {
throw wrap(ex);
}
}
在
ChannelN
中的privateRpc()
方法中执行如下代码,核心步骤如下:- 实例化
SimpleBlockingRpcContinuation
对象,用于获取响应。 - 调用
rpc(m, k)
方法,发送Basic.Get
请求。 - 调用
k.getReply()
方法,等待响应并返回。
- 实例化
private AMQCommand privateRpc(Method m) throws IOException, ShutdownSignalException{
SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation(m);
rpc(m, k); // 发送请求
if(_rpcTimeout == NO_RPC_TIMEOUT) {
return k.getReply(); // 等待响应
} else {
try {
return k.getReply(_rpcTimeout);
} catch (TimeoutException e) {
throw wrapTimeoutException(m, e);
}
}
}
public void rpc(Method m, RpcContinuation k) throws IOException {
synchronized (_channelMutex) {
ensureIsOpen();
quiescingRpc(m, k);
}
}
public void quiescingRpc(Method m, RpcContinuation k) throws IOException {
synchronized (_channelMutex) {
enqueueRpc(k);
quiescingTransmit(m);
}
}
public void enqueueRpc(RpcContinuation k) {
doEnqueueRpc(() -> new RpcContinuationRpcWrapper(k));
}
public void quiescingTransmit(Method m) throws IOException {
synchronized (_channelMutex) {
quiescingTransmit(new AMQCommand(m));
}
}
public void quiescingTransmit(AMQCommand c) throws IOException {
synchronized (_channelMutex) {
if (c.getMethod().hasContent()) {
while (_blockContent) {
try {
_channelMutex.wait();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
ensureIsOpen();
}
}
this._trafficListener.write(c);
c.transmit(this);
}
}
最终,在
AMQCommand
中执行transmit()
方法,核心步骤如下:- 获取
AMQConnection
对象。 - 分别将
AMQContentHeader
、Method
和List<byte[]>
对象转换成Frame
对象。 - 通过
AMQConnection
对象发送数据。
- 获取
public void transmit(AMQChannel channel) throws IOException {
int channelNumber = channel.getChannelNumber();
AMQConnection connection = channel.getConnection();
synchronized (assembler) {
Method m = this.assembler.getMethod();
if (m.hasContent()) {
byte[] body = this.assembler.getContentBody();
Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length);
int frameMax = connection.getFrameMax();
boolean cappedFrameMax = frameMax > 0;
int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length;
if (cappedFrameMax && headerFrame.size() > frameMax) {
String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
throw new IllegalArgumentException(msg);
}
connection.writeFrame(m.toFrame(channelNumber));
connection.writeFrame(headerFrame);
for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
int remaining = body.length - offset;
int fragmentLength = (remaining < bodyPayloadMax) ? remaining
: bodyPayloadMax;
Frame frame = Frame.fromBodyFragment(channelNumber, body,
offset, fragmentLength);
connection.writeFrame(frame);
}
} else {
connection.writeFrame(m.toFrame(channelNumber));
}
}
connection.flush();
}
4 AMQCommand
com.rabbitmq.client.impl.AMQCommand
类实现了com.rabbitmq.client.Command
接口,其成员变量CommandAssembler
对象是AMQP
规范中method
、header
和body
的容器。
AMQCommand
中提供了一个十分重要的方法:transmit(AMQChannel)
。调用该方法能够将method
、header
和body
通过Connection
发送给RabbitMQ服务器。该方法在前几个小节都有介绍,核心步骤如下:
- 获取
AMQConnection
对象。 - 分别将
AMQContentHeader
、Method
和List<byte[]>
对象转换成Frame
对象。 - 通过
AMQConnection
对象发送数据。
public void transmit(AMQChannel channel) throws IOException {
int channelNumber = channel.getChannelNumber();
AMQConnection connection = channel.getConnection();
synchronized (assembler) {
Method m = this.assembler.getMethod();
if (m.hasContent()) {
byte[] body = this.assembler.getContentBody();
Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length);
int frameMax = connection.getFrameMax();
boolean cappedFrameMax = frameMax > 0;
int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length;
if (cappedFrameMax && headerFrame.size() > frameMax) {
String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
throw new IllegalArgumentException(msg);
}
connection.writeFrame(m.toFrame(channelNumber));
connection.writeFrame(headerFrame);
for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
int remaining = body.length - offset;
int fragmentLength = (remaining < bodyPayloadMax) ? remaining
: bodyPayloadMax;
Frame frame = Frame.fromBodyFragment(channelNumber, body,
offset, fragmentLength);
connection.writeFrame(frame);
}
} else {
connection.writeFrame(m.toFrame(channelNumber));
}
}
connection.flush();
}
4.1 CommandAssembler
com.rabbitmq.client.impl.CommandAssembler
类中封装了AMQP
规范中method
、header
和body
。
4.1.1 Method
com.rabbitmq.client.impl.Method
抽象类代表AMQP
规范中method
,我们平常所使用的com.rabbitmq.client.AMQP
接口中的Connection
、Channel
、Access
、Exchange
、Queue
、Basic
、Tx
和Confirm
等内部类都实现了该抽象类。
我们调用channel.basicPublish()
等方法向RabbitMQ服务器发送消息,或者从通过注册Consumer
监听RabbitMQ服务器的消息时,都会将method
数据段转换成Method
实现类进行处理。
Method.toFrame()
方法则能将自己转换成Frame
对象,进行发送。
4.1.2 AMQContentHeader
com.rabbitmq.client.impl.AMQContentHeader
抽象类代表AMQP
规范中header
。channel.basicPublish()
方法形参BasicProperties
实现了该抽象类,我们可以通过该对象为消息设置属性。
我们调用channel.basicPublish()
等方法向RabbitMQ服务器发送消息,或者从通过注册Consumer
监听RabbitMQ服务器的消息时,都会将method
数据段转换成AMQContentHeader
实现类进行处理。
AMQContentHeader.toFrame()
方法则能将自己转换成Frame
对象,进行发送。
5 Frame
com.rabbitmq.client.impl.Frame
代表AMQP wire-protocol frame
(帧),主要包含以下成员变量:
type
:帧类型。channel
:所属通道。payload
:输入载荷。accumulator
:输出载荷。
Frame
还提供了静态方法readFrom()
,可以从输入流中读取到Frame
对象,主要提供给FrameHandler.readFrame()
方法调用:
public static Frame readFrom(DataInputStream is) throws IOException {
int type;
int channel;
try {
type = is.readUnsignedByte();
} catch (SocketTimeoutException ste) {
return null; // failed
}
if (type == 'A') {
protocolVersionMismatch(is);
}
channel = is.readUnsignedShort();
int payloadSize = is.readInt();
byte[] payload = new byte[payloadSize];
is.readFully(payload);
int frameEndMarker = is.readUnsignedByte();
if (frameEndMarker != AMQP.FRAME_END) {
throw new MalformedFrameException("Bad frame end marker: " + frameEndMarker);
}
return new Frame(type, channel, payload);
}
[RabbitMQ]Java客户端:源码概览的更多相关文章
- 关于FastDFS Java客户端源码中的一个不太明白的地方
下面代码是package org.csource.fastdfs下TrackerGroup.java文件中靠近结束的一段代码,我下载的这个源码的版本是1.24. /** * return connec ...
- Java集合源码学习(一)集合框架概览
>>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...
- Java ArrayList源码剖析
转自: Java ArrayList源码剖析 总体介绍 ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现.除该类未实现同步外 ...
- Java——LinkedHashMap源码解析
以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述 哈希表和链表基于Map接口的实现,其具有可预测的迭 ...
- swift实现饭否应用客户端源码
swift 版 iOS 饭否客户端 源码下载:http://code.662p.com/view/13318.html 饭否是中国大陆地区第一家提供微博服务的网站,被称为中国版Twitter.用户可通 ...
- android版高仿淘宝客户端源码V2.3
android版高仿淘宝客户端源码V2.3,这个版本我已经更新到2.3了,源码也上传到源码天堂那里了,大家可以看一下吧,该应用实现了我们常用的购物功能了,也就是在手机上进行网购的流程的,如查看产品(浏 ...
- Java集合源码分析(四)Vector<E>
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
- Java集合源码分析(三)LinkedList
LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- C#中国象棋+游戏大厅 服务器 + 客户端源码
来源:www.ajerp.com/bbs C#中国象棋+游戏大厅 服务器 + 客户端源码 源码开源 C#版中国象棋(附游戏大厅) 基于前人大虾的修改版 主要用委托实现 服务器支持在线人数,大厅桌数的设 ...
随机推荐
- JSP编码问题
JSP的开头内容: 1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 ...
- JDK 5.0新特性
时间:2016-11-5 12:03 JDK5.0新特性 泛型.枚举.静态导入.自动拆装箱.增强for循环.可变参数1.Junit单元测试 测试的对象是类中的一个方法. junit不 ...
- ks.cfg文件相关
原文转自:https://www.cnblogs.com/itzgr/p/10029631.html作者:木二 目录 一 图形化生成ks.cfg文件 二 ks.cfg文件相关项解析 一 图形化生成ks ...
- linux centos7 获取开机时间
2021-08-03 1. who 命令 who 命令显示关于当前在本地系统上的所有用户信息:登录名,线路,时间,备注 # 列出当前登录本系统的用户 who # 列出本系统的开机/重启时间 who - ...
- 【Python机器学习实战】决策树与集成学习(四)——集成学习(2)GBDT
本打算将GBDT和XGBoost放在一起,但由于涉及内容较多,且两个都是比较重要的算法,这里主要先看GBDT算法,XGBoost是GBDT算法的优化和变种,等熟悉GBDT后再去理解XGBoost就会容 ...
- go语言文件系统
检测文件是否存在 //存在返回 true,不存在返回 false func fileIfExist(filename string) bool { _, err := os.Stat(filename ...
- Object-源码
Object的结构 类构造器 一个类必须要有一个构造器的存在 , Object类源码中,是看不到构造器的,系统会自动添加一个无参构造器. Object obj = new Object(): equa ...
- 解决方案-问题001:物理机、虚机等等Linux操作系统/usr/bin目录权限误操作,导致无法切换root
导语:平常运维人员会误操作一些目录权限,导致一些问题,那么如何恢复呢? 问题:物理机.虚机等等Linux操作系统/usr/bin目录权限误操作,导致无法切换root? 实验环境: ip地址 是否目录正 ...
- 【Azure 应用服务】Python flask 应用部署在Aure App Service中作为一个子项目时,解决遇见的404 Not Found问题
问题描述 在成功的部署Python flask应用到App Service (Windows)后,如果需要把当前项目(如:hiflask)作为一个子项目(子站点),把web.config文件从wwwr ...
- 被面试官问懵:TCP 四次挥手收到乱序的 FIN 包会如何处理?
摘要:收到个读者的问题,他在面试的时候,被搞懵了,因为面试官问了他这么一个网络问题. 本文分享自华为云社区<TCP 四次挥手收到乱序的 FIN 包会如何处理?>,作者:小林coding . ...