核心API中定义接口和类 RabbitMQ.Client 名称空间: node
1
|
using
RabbitMQ.Client;
|
ConnectionFactory factory = new ConnectionFactory(); factory.UserName = user; // "gue factory.Password = pass; factory.VirtualHost = vhost; factory.HostName = hostName; IConnection conn = factory.CreateConnection();
因为.NET客户端使用AMQP 0-9-1 URI规格比其余客户的严格的解释,必须当心使用URI时服用。特别是,主机部分不能被省略,而且与空名称虚拟主机不可寻址(可寻址的)。全部出厂的属性都有默认值。若是该属性保持创建链接以前未分配将用于一个属性的默认值: 用户名 “guest” 密码 “guest” 虚拟主机 “/” 主机名 “localhost” 端口 5672按期链接,5671链接使用TLS 而后IConnection接口可用于打开一个通道:ConnectionFactory factory = new ConnectionFactory(); factory.Uri = "amqp://user:pass@hostName:port/vhost";IConnection conn = factory.CreateConnection();IModel channel= conn.CreateModel();该 通道 如今能够被用来发送和接收消息,如在随后的章节中描述。
model.ExchangeDeclare(exchangeName, ExchangeType.Direct);model.QueueDeclare(queueName, false, false, false, null);model.QueueBind(queueName, exchangeName, routingKey, null);
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!");model.BasicPublish(exchangeName, routingKey, null, messageBodyBytes);
byte[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes("Hello, world!");IBasicProperties props = model.CreateBasicProperties();props.ContentType = "text/plain";props.DeliveryMode = 2;model.BasicPublish(exchangeName,routingKey,props,messageBodyBytes);
以持续性的交互模式发送文本消息,有关消息属性的详细信息请查看IBasicProperties接口的定义。git
在下面的例子中,咱们发送定义了Header(头)的消息:
正则表达式
1
2
3
4
5
6
7
8
9
10
|
byte
[] messageBodyBytes = System.Text.Encoding.UTF8.GetBytes(
"Hello, world!"
);
IBasicProperties props = model.CreateBasicProperties();
props.ContentType =
"text/plain"
;
props.DeliveryMode = 2;
props.Headers =
new
Dictionary<
string
,
object
>();
props.Headers.Add(
"latitude"
, 51.5252949);
props.Headers.Add(
"longitude"
, -0.0905493);
model.BasicPublish(exchangeName, routingKey,props, messageBodyBytes);
|
下面的示例代码设置消息过时时间:数据库
1
2
3
4
5
6
7
8
|
byte
[] messageBodyBytes=System.Text.Encoding.UTF8.GetBytes(
"Hello, world!"
);
IBasicProperties props = model.CreateBasicProperties();
props.ContentType =
"text/plain"
;
props.DeliveryMode = 2;
props.Expiration =
"36000000"
mode.BasicPublish(exchangeName,routingKey,props,messageBodyBytes);
|
获取单条消息(Fetching Individual Messages ("pull API"))编程
使用IModel.BasicGet获取单条消息,从消息的Header(属性)和消息主体能够获取到BasicGetResult的实例c#
1
2
3
4
5
6
7
8
|
bool
noAck =
false
;
BasicGetResult result = channel.BasicGet(queueName, noAck);
if
(result ==
null
) {
// No message available at this time.
}
else
{
IBasicProperties props = result.BasicProperties;
byte
[] body = result.Body;
...
|
上面的 noAck=false 你也可使用 IModel.BasicAck 来确认成功的接受并处理了消息。服务器
1
2
3
4
|
...
// acknowledge receipt of the message
channel.BasicAck(result.DeliveryTag,
false
);
}
|
注意:使用该API获取消息是效率较低。若是你想使用RabbitMQ将邮件推送到客户端,请参阅下一节。
网络
经过订阅检索消息(Retrieving Messages By Subscription ("push API"))并发
接收消息的另外一种方法是使用IBasicConsumer接口创建订阅。该消息将在到达时被自动推送,而没必要进行主动请求。实现消费者的一种方法是使用的方便(convenience)类EventingBasicConsumer,其中调度交付和其余消费的生命周期事件为C#事件:
app
1
2
3
4
5
6
7
8
|
var consumer =
new
EventingBasicConsumer(channel);
consumer.Received += (ch, ea) =>
{
var body = ea.Body;
// ... process the message
ch.BasicAck(ea.DeliveryTag,
false
);
};
String consumerTag=channel.BasicConsume(queueName,
false
,consumer);
|
另外一种选择是继承DefaultBasicConsumer类,重写必要的方法,或者直接实现IBasicConsumer。一般要实现核心方法IBasicConsumer.HandleBasicDeliver。更复杂的消费者将须要实施进一步的方法。特别是,HandleModelShutdown使 channel/connection关闭。消费者还能够实现HandleBasicCancelOk通知取消的消息。 在没有被提交给原始IModel.BasicConsume状况下,DefaultBasicConsumer的ConsumerTag属性可用于检索服务器生成的消费者标签。您可使用IModel.BasicCancel主动取消消费者:
1
|
channel.BasicCancel(consumerTag);
|
当调用API方法,你老是经过消费者标签提交到他们本身的消费者,它能够是客户端或服务器生成的,详见AMQP规范0-9-1文件中解释。
消费者的并发性考虑(Concurrency Considerations for Consumers)
每一个IConnection实例,在当前实现中,由单个后台线程从Socket中读取并调度所得事件给应用程序的支持。若是启用心跳,必须3.5.0版本,它们用.NET的定时器来实现的。一般,所以,在使用这种库的应用程序至少须要激活两个线程:
1:应用程序线程(the application thread)
包含应用程序的逻辑,调用 IModel 的方法执行协议操做。
2:活动的 I/O 线程
经过IConnection的实例隐藏和彻底管理
在任何 回调的应用程序和库中,线程模型对应用程序是可见的。这样的回调包括:
一、任何IBasicConsumer方法
二、在IModel的BasicReturn事件
三、任何对IConnection了各类关闭事件,IModel等。
消费者回调和订阅(Consumer Callbacks and Ordering)
从版本3.5.0应用回调处理程序能够调用阻塞操做(如IModel.QueueDeclare或IModel.BasicCancel)。IBasicConsumer回调并发调用。然而,每一个通道的操做顺序将予以保留。换句话说,若是消息A和B在该顺序输送在同一通道上,它们将被以该顺序进行处理。若是消息A和B分别在不一样的通道输送,它们能够以任何顺序进行处理(或并行)。消费者回调在派往由.NET运行库提供的默认的TaskScheduler任务调用。
使用自定义计划任务(Using a Custom Task Scheduler)
咱们能够经过设置ConnectionFactory.TaskScheduler使用自定义的任务调度程序:
1
2
3
4
5
6
7
|
public
class
CustomTaskScheduler : TaskScheduler
{
// ...
}
var cf =
new
ConnectionFactory();
cf.TaskScheduler =
new
CustomTaskScheduler();
|
此处的例子,能够用来限制与一个自定义的TaskScheduler并发程度。
线程之间共享通道(Sharing Channels Between Threads)
根据经验,IModel实例不该由多个线程同时使用:应用程序代码应该为IModel实例维护一个清晰的线程全部权概念。若是多个线程须要访问特定的IModel实例,应用程序应该实施互斥自己。 实现这一点的一种方式是对于IModel的全部用户锁定实例自己
IModel ch = RetrieveSomeSharedIModelInstance();lock (ch) { ch.BasicPublish(...);}
一、 在线路上发送的无效帧序列(例如,若是同时运行多于一个BasicPublish操做,则发生),和/或NotSupportedExceptions从RpcContinuationQueue类中的方法抛出,引起“禁止请求的管道”(在同时运行多个AMQP 0-9-1同步操做(如ExchangeDeclare)的状况下)。
处理不可路由的消息(Handling Unroutable Messages)
若是发布的消息具备设置的“mandatory”标志,但不能传递,代理将返回给发送客户端(经过basic.return AMQP 0-9-1命令)。 为了通知这样的返回,客户能够订阅IModel.BasicReturn事件。 若是没有链接到事件的侦听器,则返回的消息将被静默删除。
model.BasicReturn += new RabbitMQ.Client.Events.BasicReturnEventHandler(...);
要断开链接,只需关闭通道和链接:
channel.Close(200, "Goodbye");conn.Close();
IConnection conn = factory.CreateConnection(...);IModel channel = conn.CreateModel();conn.AutoClose = true;
客户端和RabbitMQ节点之间的网络链接可能失败。 RabbitMQ .NET / C#客户端支持自动恢复链接和拓扑(queues, exchanges, bindings, and consumers)。 许多应用程序的自动恢复过程遵循如下步骤:
一、从新链接 (Reconnect)
二、还原链接侦听器( Restore connection listeners)
三、从新打开通道(Re-open channels)
四、还原频道侦听器(Restore channel listeners)
五、恢复通道basic.qos设置,发布者确认和事务设置( Restore channel basic.qos setting, publisher confirms and transaction settings)
拓扑恢复包括对每一个通道执行的如下操做:
一、从新声明交易(除了预约义的交易)(Re-declare exchanges (except for predefined ones))
二、从新声明队列(Re-declare queues)
三、恢复全部绑定(Recover all bindings)
四、恢复全部消费者(Recover all consumers)
要启用自动链接恢复,请将ConnectionFactory.AutomaticRecoveryEnabled设置为true:
ConnectionFactory factory = new ConnectionFactory();factory.AutomaticRecoveryEnabled = true;// connection that will recover automaticallyIConnection conn = factory.CreateConnection();
ConnectionFactory factory = new ConnectionFactory();// attempt recovery every 10 secondsfactory.NetworkRecoveryInterval = TimeSpan.FromSeconds(10);
拓扑恢复涉及恢复queues, exchanges, bindings, and consumers。 默认状况下启用它,但能够禁用:
ConnectionFactory factory = new ConnectionFactory();Connection conn = factory.CreateConnection();factory.AutomaticRecoveryEnabled = true;factory.TopologyRecoveryEnabled = false;
当使用RabbitMQ构建分布式系统时,会有一些不一样的消息模式反复出现。在本节中,咱们将介绍一些最多见的编码模式和交互风格:
点对点消息:远程过程调用(RPC)和指向特定接收器的异步消息。
事件广播:一对多交互;隐含地指向一组感兴趣的接收者的消息的传输,以及零个或多个可能的响应的收集。
责任转移:选择网络中的哪一个部分负责任何给定的消息。
消息传输:至少一次和最多一次消息传递。
在与外部资源交互时保持原子性和幂等性。
有限库支持也可用于处理这些模式,在RabbitMQ.Client.MessagePatterns命名空间:
订阅提供了从服务器接收消息的高级接口。
SimpleRpcServer构建在Subscription上以实现RPC或单向服务。
SimpleRpcClient构建在Subscription上,与远程服务交互。
未来的RabbitMQ .NET客户端库版本将包括改进对最多见的消息传递模式及其变体的高级支持。
点对点消息(Point-to-point Messaging)
当消息的发布者具备特定的接收应用时,例如,当经过AMQP服务器使得RPC样式的服务可用时,或者当工做流链中的应用接收到消息时,发生点对点消息传递模式工做项,并将转换后的工做项发送给其后继者。
同步,客户机 - 服务器远程过程调用(RPC)
为了执行请求/响应RPC,
一些解决服务的手段必须可用
一些接收答复的方法必须可用
将请求消息与回复消息相关联的一些装置必须可用
寻址服务(Addressing the service)
因为AMQP消息是使用一对交换名称和路由密钥发布的,所以这足以用于寻址服务。使用简单的交换名/路由 - 密钥组合容许多种不一样的方式来实现服务,同时向客户端呈现相同的接口。例如,服务能够被实现为从队列消耗的单个进程和在内部的负载均衡,或者其能够是从单个队列消耗的多个进程,被递送请求循环式,从而在没有特殊编码的状况下进行负载均衡服务逻辑。消息也能够寻址到服务请求队列
直接,使用AMQP默认交换(“”);要么
间接地经过使用服务特定交换,其使得路由密钥免费用于诸如方法选择或附加服务特定寻址信息的目的;要么
间接地,经过使用由多个服务共享的交换,其中服务名称在路由密钥中编码。
使用默认交换以外的交换容许其余应用程序接收每一个请求消息的副本,这对于监视,审计,日志记录和调试是有用的。
确保服务实例正在侦听
AMQP 0-9-1发布操做(IModel.BasicPublish)提供了交付标志“强制性”,可用于确保客户端发送请求时的服务可用性。若是不能将请求路由到队列,则设置“mandatory”标志会致使返回请求。返回的消息显示为basic.return命令,经过IModel上用于发布消息的IModel.BasicReturn事件使其可见。
因为已发布的消息经过basic.return方法返回到客户端,而basic.return是异步否认确认事件,所以不能将特定消息的basic.return做为传递的确认:使用传递标志只提供了提升杆的方法,而不是彻底消除故障。
另外,消息被标记为“强制性”而且成功地入队在一个或多个队列上的事实不能保证其最终接收:最日常地,队列能够在消息被处理以前被删除,可是其余状况,例如使用noAck标志的消息消费者,也可使得“强制”提供的保证有条件。
或者,您可使用发布商确认。经过调用IModel.ConfirmSelect将通道设置为确认模式会致使代理在经过传递到就绪消费者或持久存储到磁盘来处理每一个消息后发送Basic.Ack。一旦经过IModel.BasicAcks事件处理程序确认成功处理的消息,代理就承担了该消息的责任,客户端能够考虑处理消息。注意,代理还能够经过发送回Basic.Nack来否认确认消息。在这种状况下,若是经过IModel.BasicNacks事件处理程序拒绝消息,客户端应该假定消息丢失或以其余方式没法投递。此外,请注意,不可路由的消息 - 发布为不存在队列的强制性消息 - 都是Basic.Return和Basic.Ack'ed。
接收回复(Receiving Replies)
AMQP 0-9-1内容头(IBasicProperties)包含一个称为ReplyTo的字段,可用于告知服务在何处发布对接收到的RPC请求的答复。在当前的RabbitMQ客户端库中,ReplyTo头中的字符串使用最普遍的格式是一个简单的队列名称,尽管传递经过应用程序特定规则加入的交换名称和路由键也是一个选项。服务实例将其答复发布到指定的目的地,而且请求客户端应该安排接收如此寻址的消息,使用在适当绑定的队列上的BasicGet或BasicConsume。
将接收到的应答与发送的请求相关联
IBasicProperties包含一个名为CorrelationId的字段,在AMQP 0-9-1中是一个非结构化字符串,可用于将请求匹配到回复。应答消息应具备与附加到请求消息的相同的相关标识。
异步,单向消息传递(Asynchronous, one-way messaging)
在某些状况下,简单的请求 - 回复交互模式不适合您的应用程序。 在这些状况下,感兴趣的交互模式能够从异步,单向,点对点消息构造。 若是应用程序要响应同步,RPC样式请求和异步单向请求,它应该使用ReplyTo的值来决定请求它的交互样式:若是ReplyTo存在而且非空, 请求能够假定是一个RPC样式的调用; 不然,应假定它是单向消息。 CorrelationId字段能够用于将多个相关消息分组在一块儿,就像对于RPC样式的状况同样,可是更一般地将任意数量的消息绑定在一块儿。
点对点的确认模式(Acknowledgment modes for point-to-point)
当从服务器接收消息时,AMQP能够以两种模式之一操做:自动确认模式(当BasicGet,BasicConsume或Subscription构造函数上设置noAck标志时)或手动确认模式。选择正确的确认模式对于您的应用程序很重要:
自动确认模式意味着当服务器在网络上传输消息时,服务器将内部将消息标记为已成功传递。以自动确认模式传送的消息一般不会从新传送到任何其余接收器。
手动确认模式意味着在将消息标记为已成功传送以前,服务器将等待接收的确定确认。若是在服务器接收到确认以前关闭了交付的手动确认模式下的通道(IModel),则将从新排队。
通常来讲,
若是服务处于手动确认模式,则它不该该确认请求消息,直到它回复它;请参阅下面有关与外部资源交互的部分。
客户端可使用自动确认模式,这取决于请求消息的重传的结果。
库支持点对点消息传递(Library support for point-to-point messaging)
RabbitMQ .NET客户端库包括涉及点对点消息传递的常见任务的基本支持。
SimpleRpcServer
类RabbitMQ.Client.MessagePatterns.SimpleRpcServer实现同步RPC样式的请求处理以及异步消息处理。用户应该继承SimpleRpcServer,覆盖一个或多个以“Handle”开头的方法。 SimpleRpcServer实例具备请求调度循环MainLoop,它将请求解释为RPC样式的请求,若是请求的IBasicProperties的ReplyTo字段为非空且非空,则须要回复。具备缺乏或空的ReplyTo字段的请求被视为单向。当处理了RPC样式的请求时,将答复发送到ReplyTo地址。答复地址首先与描述上面给出的相似URI的语法的正则表达式相匹配;若是匹配,则使用相似URI的语法的组件做为回复地址,若是不匹配,则将整个字符串用做简单队列名称,并将回复发送到默认交换(“”)一个等于ReplyTo字符串的路由键。
SimpleRpcClient
类RabbitMQ.Client.MessagePatterns.SimpleRpcClient实现与SimpleRpcServers或相似的交互的代码。 RPC风格的交互是用Call方法执行的。 (私人)订阅设置为从服务接收回复,而且ReplyTo字段设置为指向订阅。请求的CorrelationId字段被初始化为新的GUID。异步/单向交互被简单地传递到IModel.BasicPublish而不修改:它是由调用者在异步状况下设置CorrelationId。该类目前不支持在已发布的请求消息上设置“mandatory”标志,也不支持处理因为设置该标志而可能产生的任何BasicReturn事件。从内部订阅检索答复的代码当前没法处理多个同时未解决的RPC请求,由于它要求答复以与发送请求相同的顺序到达。在解除此限制以前,不要尝试管理经过SimpleRpcClient的单个实例发送的请求。另请参见可覆盖的受保护方法SimpleRpcClient.RetrieveReply。使用SimpleRpcClient的基本模式以下:
using (IConnection conn = new ConnectionFactory() .CreateConnection(args[0])) { using (IModel ch = conn.CreateModel()) { SimpleRpcClient client = new SimpleRpcClient(ch, /* ... */); // in the line above, the "..." indicates the parameters // used to specify the address to use to route messages // to the service. // The next three lines are optional: client.TimeoutMilliseconds = 5000; // defaults to infinity client.TimedOut += new EventHandler(TimedOutHandler); client.Disconnected += new EventHandler(DisconnectedHandler); byte[] replyMessageBytes = client.Call(requestMessageBytes); // other useful overloads of Call() and Cast() are // available. See the code documentation of SimpleRpcClient // for full details. }}
事件广播(Event Broadcasting)
当应用程序但愿在不知道每一个感兴趣方的地址的状况下向应用程序池指示状态改变或其余通知时,发生事件广播模式。 对某个事件子集感兴趣的应用程序使用交换和队列绑定来配置哪些事件被路由到其本身的专用队列。
一般,事件将经过主题交换广播,可是直接交换虽然不太灵活,可是有时对于其有限模式匹配能力足够的应用能够执行得更好。
发布事件(Publishing events)
要发布事件,首先确保交换存在,而后肯定适当的路由密钥。 例如,对于股票,一个键如“stock.ibm.nyse”多是合适的; 对于其余应用程序,其余主题层次结构将天然出现。 主题交换经常使用。 而后发布消息。 例如:
using (IConnection conn = new ConnectionFactory() .CreateConnection(args[0])) { using (IModel ch = conn.CreateModel()) { IBasicProperties props = ch.CreateBasicProperties(); FillInHeaders(props); // or similar byte[] body = ComputeBody(props); // or similar ch.BasicPublish("exchangeName", "chosen.routing.key", props, body); }}
// "IModel ch" in scope.Subscription sub = new Subscription(ch, "STOCK.IBM.#");foreach (BasicDeliverEventArgs e in sub) { // handle the message contained in e ... // ... and finally acknowledge it sub.Ack(e);}
// "IModel ch" in scope.ch.ExchangeDeclare("prices", "topic");ch.QueueDeclare("MyApplicationQueue", false, true, true, null);ch.QueueBind("MyApplicationQueue", "prices", "STOCK.IBM.#", false, null);
事件广播的确认模式(Acknowledgment modes for event broadcasting)
与用于点对点消息传递的相同的自动确认/手动确认决定可用于广播事件的消费者,可是交互的模式引入不一样的权衡:
对于高容量消息传递,其中偶尔可接受的是不接收一个感兴趣的消息,自动确认模式是有意义的
对于其中知足咱们的订阅的每一个消息须要被递送的状况,手动确认是适当的
有关详细信息,请参阅下面的可靠邮件传输部分。还要注意,只要为每一个接收的消息调用Subscription.Ack(),类Subscription就会负责确认和各类确认模式。
可靠的消息传输(Reliable message transfer)
消息能够在具备不一样服务质量(QoS)水平的端点之间传输。通常来讲,不能彻底排除故障,但重要的是要了解各类交付故障模式,以了解从故障中恢复的种类,以及可能恢复的状况。重申:不可能彻底排除故障。能够作的最好是缩小可能发生故障的条件,而且当检测到故障时通知系统操做员。
至少一次递送
该QoS水平确保消息被传递到其最终目的地至少一次。也就是说,接收器能够接收消息的多个副本。若是对于给定消息,反作用仅发生一次是重要的,则应该使用至多一次递送。
要实施至少一次投放(At-least-once delivery)
像往常同样发布消息,在其上具备一些相关标识符和回复地址,使得接收方能够确认对发送方的接收。当接收到消息时,将确认消息发送回发送者。若是消息是RPC请求,则RPC应答消息隐式地是对请求的接收的确认。
或者,不是手动实现往返逻辑,而是客户端可使用发布者确认。经过在通道上启用确认模式,客户端请求代理确认或否认确认从该点开始在该通道上发送的全部消息。请参阅“责任转移”中有关如何使用确认的说明。
决定邮件重发策略可能很困难。一些简单的从新发送策略是:
若是您的链接丢失或在您收到收据确认以前发生其余崩溃,请从新发送
若是您在几秒钟内没有收到确认,则超时并从新发送。请确保每次从新发送的超时时间加倍,以帮助避免与重试相关的拒绝服务和网络拥塞。
最多一次传送(At-most-once delivery)
对于最多一次传递,只需发布消息,一次,照常。不须要相关标识符。在使用应用程序中接收消息,注意交货时的Redelivered标志。 Redelivered标志只有在服务器认为它提供第一次消息消息时才会清除。若是以前已进行任何交货尝试,则从新送达标志将被设置。 Redelivered标志是一个很是有限的信息,只给出最多一次的语义。
用多节点RabbitMQ集群编码(Coding with multi-node RabbitMQ clusters)
在须要连续服务的状况下,能够经过一些仔细的编程和用于故障转移的热备份集群的可用性来对抗服务器故障的可能性。
失败时的主要关注点是
公布/认可的工做单位的原子性,以及备份服务器上已配置资源的可用性
消息生产者应注意使用事务,以便从服务器接收一组消息的确定确认,而且应该保留他们为了执行它们的工做须要可用的交换,队列和绑定的记录,所以在故障切换时,能够在重放最近的要恢复的事务以前声明适当的资源。
消息消费者应该意识到在故障转移时丢失或重复消息的可能性:发布者能够决定从新发送其结果有疑问的事务,或者发布者认为完成的事务可能因为集群节点的故障而彻底消失。
与外部资源交互
服务的常见模式是
经由队列接收服务请求
更新一些外部资源,如文件或数据库
经过RabbitMQ进行回复,或至少向服务器确认触发操做的消息已完成
至少一次模式的元素一般与外部资源模式一块儿出现 - 具体地,上面关于可靠消息传递的部分中讨论的反作用一般对外部资源产生影响。
在交付必须被处理不超过一次而且与外部资源结合使用的状况下,重要的是编写可以在每一个步骤的代码以肯定该步骤是否已经在完成整个交易的一些先前尝试中采起,而且若是它具备,则可以在该尝试中省略它并继续下一步骤。例如:
若是工做触发请求丢失,另外一个副本将(最终)从最终请求者到达。
若是已经执行了工做,例如更新了数据库表,则在先前接收到有问题的工做项时,服务须要保持对于原子工做自己的原子的外部工做的完成的记录:例如,在相同的数据库事务中,能够更新尊敬请求的一些日志,或者能够更新被修改的行以包括引发修改的请求的ID,以及修改该行的先前请求ID题。
这使得重要的是可以压缩请求ID,以便它们不在执行的工做的日志中占用无界空间,而且使得咱们不须要与最终请求者引入彻底分布式垃圾收集协议。这样作的一种方式是选择使用严格增长的请求ID,使得可使用“高水位线”。一旦知道已经执行了工做,而且已经产生了答复(若是存在答复),则能够根据须要将答复发送回请求者。请求者知道它指望的回复,而且能够丢弃不想要的重复。只要相同请求的重复老是收到相同的答复消息,则复制者没必要当心发送太多的复制副本。一旦已经将答复发送到服务器,则能够确认请求消息为已接收而且与服务器服务器一块儿处理。在没有对请求的答复的状况下,确认仍然有用以确保请求不丢失。