RabbitMQ--客户端常用API详解

连接RabbitMQ

创建Connection:

//通过设置参数创建
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(IP_ADDRESS);
factory.setPort(PORT);
factory.setUsername("guest");
factory.setPassword("guest");
Connection connection = factory.newConnection();

//通过uri创建
factory.setUri("amqp:/userName:password@ipAddress:portNumber/virtualHost");
Connection connection = factory.newConnection();

创建Channel:

Channel channel = connection.createChannel();

Connection可以用来创建多个Channel实例,但是Channel实例不能在线程间共享,应用程序应该为每一个线程开辟一个Channel。某些情况下Channel的操作可以并发运行,但是在其他情况下会导致在网络,上出现错误的通信帧交错,同时也会影响发送方确认( publisherconfirm )机制的运行,所以多线程间共享Channel实例是非线程安全的

通常情况下,在调用createXXX或者newXXX 方法之后,我们可以简单地认为Connection或者Channel已经成功地处于开启状态,而并不会在代码中使用isOpen这个检测方法。如果在使用Channel 的时候其已经处于关闭状态,那么程序会抛出一个com.rabbitmq.client.ShutdownSignalException,我们只需捕获这个异常即可。当然同时也要试着捕获IOException或者SocketException,以防Connection意外关闭。

创建交换器:
exchangeDeclare有多个重载方法,这些重载方法都是由下面这个方法中缺省的某些参数构成的。

Exchange.DeclareOk exchangeDeclare(String exchange,
	String type, boolean durable,
	boolean autoDelete, boolean internal,
	Map<String, Object> arguments) throws IOException;

这个方法的返回值是Exchange . Declare0K,用来标识成功声明了一个交换器。

  • exchange: 交换器的名称。
  • type: 交换器的类型,常见的如fanout、direct、 topic
  • durable: 设置是否持久化。durable 设置为true表示持久化,反之是非持久化。持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息。
  • autoDelete: 设置是否自动删除。autoDelete 设置为true则表示自动删除。自动删除的前提是至少有一个队列或者交换器与这个交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑。注意不能错误地把这个参数理解为:“当与此交换器连接的客户端都断开时,RabbitMQ 会自动删除本交换器”。
  • internal: 设置是否是内置的。如果设置为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
  • argument: 其他一些结构化参数,比如alternate-exchange。

exchangeDeclare的重载方法如下:

(1) Exchange.DeclareOk exchangeDeclare(String exchange, String type) throws IOException;
(2) Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable) throws IOException;
(3) Exchange.DeclareOk exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,Map<String, Object> arguments) throws IOException;

与此对应的,将第二个参数String type 换成BuiltInExchangeType type 对应的几个重载方法(不常用):

(1)  Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type) throws IOException;
(2)  Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable) throws IOException;
(3) Exchange.DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete,
(4) Exchange.DeclareOk exchangeDeclare(String exchange,
        BuiltinExchangeType type,
        boolean durable,
        boolean autoDelete,
        boolean internal,
        Map<String, Object> arguments) throws IOException;

与exchangeDeclare师出同门的还有几个方法,比如exchangeDeclareNoWait方法,具体定义如下(当然也有BuiltExchangeType版的,这里就不展开了):

void exchangeDeclareNoWait(String exchange,
                               String type,
                               boolean durable,
                               boolean autoDelete,
                               boolean internal,
                               Map<String, Object> arguments) throws IOException;

这个nowait参数指的是AMQP中Exchange.Declare命令的参数,意思是不需要服务器返回,注意这个方法的返回值是voi id, 而普通的exchangeDeclare 方法的返回值是Exchange.DeclareOk,意思是在客户端声明了一个交换器之后,需要等待服务器的返回(服务器会返回Exchange. Declare-Ok这个AMQP命令)。

针对“exchangeDeclareNoWait不需要服务器任何返回值"这一点, 考虑这样一种情况,在声明完一个交换器之后(实际服务器还并未完成交换器的创建),那么此时客户端紧接着使用这个交换器,必然会发生异常。如果没有特殊的缘由和应用场景,并不建议使用这个方法。

检测交换器是否存在:

(1) Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException;

(2) Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException;

这个方法在实际应用过程中还是非常有用的,它主要用来检测相应的交换器是否存在。如果存在则正常返回;如果不存在则抛出异常: 404 channel exception,同时Channel也会被关闭。

删除交换器:

(1) Exchange.DeleteOk exchangeDelete(String exchange) throws IOException;

(2) void exchangeDeleteNoWait(String exchange, boolean ifUnused) throws IOException;

(3) Exchange.DeleteOk exchangeDelete(String exchange) throws IOException;
  • exchange表示交换器的名称
  • ifUnused用来设置是否在交换器没有被使用的情况下删除。如果isUnused设置为true,则只有在此交换器没有被使用的情况下才会被删除;如果设置false,则无论如何这个交换器都要被删除。

创建队列:
queueDeclare相对于exchangeDeclare方法而言,重载方法的个数就少很多,它只有两个重载方法:

(1) Queue.DeclareOk queueDeclare() throws IOException;

(2) Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;

不带任何参数的queueDeclare方法默认创建一个由RabbitMQ命名的(类似这种amq.gen-LhQz1gv3GhDOv8PIDabOXA名称,这种队列也称之为匿名队列)、排他的、自动删除的、非持久化的队列。

  • queue: 队列的名称。

  • durable: 设置是否持久化。为true 则设置队列为持久化。持久化的队列会存盘,在服务器重启的时候可以保证不丢失相关信息。

  • exclusive: 设置是否排他。为true 则设置队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:

    1. 排他队列是基于连接(Connection) 可见的,同一个连接的不同信道(Channel)是可以同时访问同一连接创建的排他队列;
    2. “首次”是指如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同;
    3. 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的应用场景。
  • autoDelete: 设置是否自动删除。为true则设置队列为自动删除。自动删除的前提是:
    至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。不能把这个参数错误地理解为:“当连接到此队列的所有客户端断开时,这,个队列自动删除”,因为生产者客户端创建这个队列,或者没有消费者客户端与这个队列连接时,都不会自动删除这个队列。

  • arguments:设置队列的其他一些参数,如x-message=ttl、x-expires、x-max-length、x-max-length-bytes、x-dead-letter-exchange、x-dead-letter-routing-key、x-max-priority等。

注意要点:生产者和消费者都能够使用queueDeclare 来声明一个队列,但是如果消费者在同一个信道上订阅了另一个队列,就无法再声明队列了。必须先取消订阅,然后将信道置为“传输”模式,之后才能声明队列。

void queueDeclareNoWait(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;

方法的返回值也是void,表示不需要服务端的任何返回。同样也需要注意,在调用完queueDeclareNowait方法之后,紧接着使用声明的队列时有可能会发生异常情况。

检测队列:

Queue.DeclareOk queueDeclarePassive(String queue) throws IOException;
  • 存在,返回Queue.DeclareOk
  • 不存在,抛出异常

删除队列:

(1) Queue.DeleteOk queueDelete(String queue) throws IOException;

(2) Queue.DeleteOk queueDelete(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;

(3) void queueDeleteNoWait(String queue, boolean ifUnused, boolean ifEmpty) throws IOException;

参数基本与删除交换器一致。

清空队列:

Queue.PurgeOk queuePurge(String queue) throws IOException;

该方法用来清空队列的内容,而不删除队列本身。

绑定队列和交换器:

(1) Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException;

(2) Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;

(3) void queueBindNoWait(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;
  • queue: 队列名称;
  • exchange: 交换器的名称;
  • routingKey: 用来绑定队列和交换器的路由键;
  • argument: 定义绑定的一些参数。

解除队列和交换器的绑定:

(1)  Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey) throws IOException;

(2) Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map<String, Object> arguments) throws IOException;

绑定交换器和交换器:

(1) Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException;

(2) Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;

(3) void exchangeBindNoWait(String destination, String source, String routingKey, Map<String, Object> arguments) throws IOException;

示例:

channel.exchangeDeclare("source","direct",false,true,null);
channel.exchangeDeclare("destination","fanout",false,true,null);
channel.exchangeBind("destination","source","exKey");
channel.queueDeclare("queue",false,false,true,null);
channel.queueBind("queue","destination“,"");
channel.basicPublish("source","exKey",null,"exToExDemao".getBytes());

生产者发送消息至交换器source中,交换器source根据路由键找到与其匹配的另一个交换器destination, 并把消息转发到destination中,进而存储在’destination绑定的队列queue中
在这里插入图片描述

发送消息:

如果要发送一个消息,可以使用Channel类的basicPublish方法,比如发送一条内容为“Hello World!”的消息,参考如下:

//发送消息
byte[] messageBodyBytes = "Hello World!".getBytes();
channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes);

为了更好地控制发送,可以使用mandatory这个参数,或者可以发送一些特定属性的信息

channel.basicPublish(exchangeName, 
	routingKey, 
	mandatory, 
	MessageProperties.PERSISTENT_TEXT_PLAIN, 
	messageBodyBytes);

更加详细的控制消息:

byte[] messageBodyBytes = "Hello World!".getBytes();
channel.basicPublish(exchangeName, routingKey, 
       new AMQP.BasicProperties().builder()
           .contentType("text/plain")
           .deliveryMode(2)
           .priority(1)
           .userId("hidden")
           .build(), 
       messageBodyBytes);

上面这行代码发送了一条消息,这条消息的投递模式(delivery mode)设置为2,即消息会被持久化(即存入磁盘)在服务器中。同时这条消息的优先级( priority)设置为1, content-type为“text/plain”。可以自己设定消息的属性。

也可以发送一条带有headers的消息:

byte[] messageBodyBytes = "Hello World!".getBytes();
Map<String, Object> headers = new HashMap<>();
headers.put("localtion", "here");
headers.put("time", "today");
channel.basicPublish(exchangeName, routingKey,
        new AMQP.BasicProperties().builder()
            .headers(headers)
            .build(),
        messageBodyBytes);

还可以发送一条带有过期时间(expiration)的消息:

byte[] messageBodyBytes = "Hello World!".getBytes();
channel.basicPublish(exchangeName, routingKey, new AMQP.BasicProperties.Builder()
    .expiration("60000")
    .build(), messageBodyBytes); 

重载方法:

(1) void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException;

(2) void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
            throws IOException;

(3) void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
            throws IOException;
  • exchange:交换器的名称,指明消息需要发送到哪个交换器中。如果设置为空字符串,则消息会被发送到RabbitMQ默认的交换器中。

  • routingKey: 路由键,交换器根据路由键将消息存储到相应的队列之中。

  • props : 消息的基本属性集,其包含14个属性成员,分别有contentType、contentEncoding、headers(Map<String, object>)、deliveryMode、priority、correlationId、replyTo、expiration、messageId、timestamp、type、userId、appId、clusterId。其中常用的几种都在上面的示例中进行了演示。

  • byte[] body: 消息体(payload), 真正需要发送的消息。

  • mandatory:当mandatory标志位设置为true时,如果exchange根据自身类型和消息routingKey无法找到一个合适的queue存储消息,那么broker会调用basic.return方法将消息返还给生产者;当mandatory设置为false时,出现上述情况broker会直接将消息丢弃;通俗的讲,mandatory标志告诉broker代理服务器至少将消息route到一个队列中,否则就将消息return给发送者;

  • immediate:已过期

消费消息

RabbitMQ的消费模式分两种:

  • 推( Push )模式
  • 拉( Pull )模式

推模式采用Basic.Consume进行消费,而拉模式则是调用Basic.Get进行消费。

推模式:

在推模式中,可以通过持续订阅的方式来消费消息,使用到的类有:

com.rabbitmq.client.Consumer
com.rabbitmq.client.DefaultConsumer

接收消息一般通过实现Consumer接口或者继承DefaultConsumer类来实现。当调用与Consumer相关的API方法时,不同的订阅采用不同的消费者标签(consumerTag) 来区分彼此,在同一个Channel中的消费者也需要通过唯一的消费者标签以作区分,代码如下:

boolean autoAck = false;
channel.basicQos(64);
channel.basicConsume(queueName, autoAck, "myConsumerTag",
        new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String routingKey = envelope.getRoutingKey();
                String contentType = properties.getContentType();
                long deliveryTag = envelope.getDeliveryTag();
                //process the message components here ...
                channel.basicAck(deliveryTag, false);
            }
        });

注意,上面代码中显式地设置autoAck为false,然后在接收到消息之后进行显式ack操作(channel.basicAck ),对于消费者来说这个设置是非常必要的,可以防止消息不必要地丢失。

Channel类中basicConsume方法重载:

(1) String basicConsume(String queue, Consumer callback) throws IOException;

(2) String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;

(3) String basicConsume(String queue, boolean autoAck, Map<String, Object> arguments, Consumer callback) throws IOException;

(4) String basicConsume(String queue, boolean autoAck, String consumerTag, Consumer callback) throws IOException;

(5) String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map<String, Object> arguments, Consumer callback) throws IOException;
  • queue: 队列的名称;
  • autoAck:设置是否自动确认。建议设成false,即不自动确认;
  • consumerTag: 消费者标签,用来区分多个消费者;
  • noLocal: 设置为true则表示不能将同一个Connection中生产者发送的消息传送给这个Connection中的消费者;
  • exclusive: 设置是否排他;
  • arguments: 设置消费者的其他参数;
  • callback: 设置消费者的回调函数。用来处理RabbitMQ 推送过来的消息,比如DefaultConsumer,使用时需要客户端重写(override) 其中的方法。

对于消费者客户端来说重写handleDelivery方法是十分方便的。更复杂的消费者客户端会重写更多的方法,具体如下:

public void handleConsumeOk(String consumerTag)
public void handleCancelOk(String consumerTag)
public void handleCancel(String consumerTag) throws IOException
public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig)
public void handleRecoverOk(String consumerTag)

比如handleShutdownSignal方法,当Channel或者Connection关闭的时候会调用。再者,handleConsumerOk方法会在其他方法之前调用,返回消费者标签。

重写handleCancelOk和handleCancel方法,这样消费端可以在显示地或者隐式的取消订阅时调用。也可以通过channel.casicCancel方法来显示的取消一个消费者的订阅:

channel.basicCancel(consumerTag);

注意:该行代码会首先触发handleConsumerOk方法,之后触发handleDelivery方法,最后才触发handleCancelOk方法。

和生产者一样,消费者客户端同样需要考虑线程安全的问题。消费者客户端的这些callback会被分配到与Channel不同的线程池上,这意味着消费者客户端可以安全地调用这些阻塞方法,比如channel.queueDeclare、channel.basicCancel等。

每个Channel都拥有自己独立的线程。最常用的做法是一个Channel对应一个消费者,也就是意味着消费者彼此之间没有任何关联。当然也可以在-一个Channel中维持多个消费者,但是要注意一个问题,如果Channel中的一一个消费者一直在运行,那么其他消费者的callback会被“耽搁”。

拉模式:
拉模式的消费方式:通过channel.basicGet方法可以单条地获取消息,其返回值是GetRespone

Channel类的basicGet方法没有其他重载方法,只有:

GetResponse basicGet (String queue, boolean autoAck).throws IOException;

其中queue代表队列的名称,如果设置autoAck为false,那么同样需要调用channel.basicAck来确认消息已被成功接收。

GetResponse getResponse = channel.basicGet(QUEUE_NAME, false);
System.out.println(new String(getResponse.getBody()));
channel.basicAck(getResponse.getEnvelope().getDeliveryTag(), false);

注意:Basic. Consume将信道( Channel)置为接收模式,直到取消队列的订阅为止。在接收模式期间,RabbitMQ会不断地推送消息给消费者,当然推送消息的个数还是会受到Basic.Qos的限制。如果只想从队列获得单条消息而不是持续订阅,建议还是使用Basic.Get 进行消费。但是不能将Basic.Get放在一个循环里来代替Basic.Consume,这样做会严重影响RabbitMQ的性能。如果要实现高吞吐量,消费者理应使用Basic. Consume方法。

在这里插入图片描述

消费端的确认与拒绝

为了保证消息从队列可靠地达到消费者,RabbitMQ 提供了消息确认机制( messageacknowledgement)。消费者在订阅队列时,可以指定autoAck参数,当autoAck等于false时,RabbitMQ 会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除)。当autoAck等于true 时,RabbitMQ 会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息。

采用消息确认机制后,只要设置 autoAck 参数为false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直等待持有消息直到消费者显式调用Basic.Ack命令为止。

当autoAck参数置为false,对于RabbitMQ服务端而言,队列中的消息分成了两个部分:

  1. 一部分是等待投递给消费者的消息;
  2. 一部分是已经投递给消费者,但是还没有收到消费者确认信号的消息。

如果RabbitMQ一直没有收到消费者的确认信号,并且消费此消息的消费者已经断开连接,则RabbitMQ 会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。

RabbitMQ不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开,这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。

RabbtiMQ的Web管理平台上可以看 到当前队列中的“Ready”状态和“Unacknowledged”状态的消息数,分别对应上文中的等待投递给消费者的消息数和已经投递给消费者但是未收到确认信号的消息数

在这里插入图片描述

拒绝消息:
在消费者接收到消息后,如果想明确拒绝当前的消息而不是确认,那么应该怎么做呢?RabbitMQ在2.0.0 版本开始引入了Basic.Reject这个命令,消费者客户端可以调用与其对应的channel. basicReject方法来告诉RabbitMQ拒绝这个消息。

Channel类中的basicReject方法定义如下:

void basicReject(long deliveryTag, boolean requeue) throws IOException;

其中deliveryTag 可以看作消息的编号,它是一个64位的长整型值,最大值是9223372036854775807。

  • 如果requeue参数设置为true,则RabbitMQ 会重新将这条消息存入队列,以便可以发送给下一个订阅的消费者;
  • 如果requeue参数设置为false, 则RabbitMQ立即会把消息从队列中移除,而不会把它发送给新的消费者。

Basic.Reject命令一次只能拒绝一条消息,如果想要批量拒绝消息,则可以使用Basic.Nack这个命令。消费者客户端可以调用channel .basicNack方法来实现,方法定义如下:

void basicNack(long deliveryTag, boolean multiple, boolean requeue)
            throws IOException;
  • multiple 参数设置为false 则表示拒绝编号为deliveryTag的这一条消息,这时候basicNack和basicReject方法一样;
  • multiple参数设置为true则表示拒绝deliveryTag编号之前所有未被当前消费者确认的消息。

注意:将channel.basicReject或者channel.basicNack中的requeue 设置为false, 可以启用“死信队列”的功能。死信队列可以通过检测被拒绝或者未送达的消息来追踪问题。

重新发送未确认的消息:
对于requeue, AMQP中还有一个命令Basic. Recover具备可重入队列的特性。其对应的客户端方法为:

(1) Basic.RecoverOk basicRecover() throws IOException;

(2) Basic.RecoverOk basicRecover(boolean requeue) throws IOException;

这个channel . basicRecover方法用来请求RabbitMQ重新发送还未被确认的消息。如果requeue参数设置为true,则未被确认的消息会被重新加入到队列中,这样对于同一条消息来说,可能会被分配给与之前不同的消费者。如果requeue参数设置为false,那么同一条消息会被分配给与之前相同的消费者。默认情况下,如果不设置requeue这个参数,相当于channel . basicRecover (true),即requeue默认为true。

关闭连接
channel.close();
connection.close();

显式地关闭Channel是个好习惯,但这不是必须的,在Connection关闭的时候,Channel也会自动关闭。

AMQP协议中的Connection和Channel采用同样的方式来管理网络失败、内部错误和显式地关闭连接。Connection 和Channel所具备的生命周期如下所述。

  • Open: 开启状态,代表当前对象可以使用。
  • Closing: 正在关闭状态。当前对象被显式地通知调用关闭方法(shutdown), 这样就产生了一个关闭请求让其内部对象进行相应的操作,并等待这些关闭操作的完成。
  • Closed: 已经关闭状态。当前对象已经接收到所有的内部对象已完成关闭动作的通知,并且其也关闭了自身。

Connection和Channel最终都是会成为Closed的状态,不论是程序正常调用的关闭方法,或者是客户端的异常,再或者是发生了网络异常。

在Connection 和Channel中,与关闭相关的方法有

addShutdownListener(ShutdownListener listener) 
//和
removeShutdownListener(ShutdownListner listener)

当Connection 或者Channel 的状态转变为Closed 的时候会调用ShutdownListener。而且如果将一个ShutdownListener注册到一个已经处于Closed状态的对象(这里特指Connection和Channel对象)时,会立刻调用ShutdownListener.

  • getCloseReason方法可以让你知道对象关闭的原因;
  • isOpen方法检测对象当前是否处于开启状态;
  • close (int closeCode, String closeMessage)方法显式地通知当前对象执行关闭操作;
connection.addShutdownListener(new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException cause) {
                //...
                System.out.println("shut down");
            }
        });

当触发ShutdownListener的时候,就可以获取到ShutdownSignalException,这个ShutdownSignalException包含了关闭的原因,这里原因也可以通过调用前面所提及的getCloseReason方法获取。

ShutdownSignalException提供了多个方法来分析关闭的原因。

  • isHardError方法可以知道是Connection的还是Channel的错误;
  • getReason方法可以获取cause相关的信息
connection.addShutdownListener(new ShutdownListener() {
    @Override
    public void shutdownCompleted(ShutdownSignalException cause) {
        if (cause.isHardError()) {
            Connection conn = (Connection) cause.getReference();
            if (!cause.isInitiatedByApplication()) {
                Method reason = cause.getReason();
                //...
            } else {
                Channel ch = (Channel)cause.getReference();
                //...
            }
        }
    }
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值