什么RPC?

这一段是从度娘摘抄的。

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。

RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

Callback Queue

之前在做学习笔记(二)工作队列的例子的时候,我们只是发送了任务给消息消费者实例去完成,但是对于消息生产者,它并不知道任务是否完成,所以这里缺少一个回调方法。

既然我们使用RabbitMQ, 所以很容易想到的就是我们可以互换一下消息消费者和消息生产者的角色,当消费生产者完成任务之后,通过创建一个新的消息队列,将完成任务的消息,发送给消息生产者。

那么如何让消息消费者,知道消费生产者关注的消息队列呢?

在我们发布消息的时候,会调用channel对象的BasicPublish方法,这个方法中有一个IBasicProperties的参数basicProperties

在这对象中,有一个ReplyTo属性,我们可以将生产者监听的消息队列名称存放在里面。当消费者程序接收到这条消息的时候,就可以在Receive事件的ea对象中获取ReplyTo属性的值

var props = channel.CreateBasicProperties();

props.ReplyTo = replyQueueName;

 

var messageBytes = Encoding.UTF8.GetBytes(message);

channel.BasicPublish(exchange: "",

                     routingKey: "rpc_queue",

                     basicProperties: props,

                     body: messageBytes);

Correlation Id

那么当消息生产者接收到消息消费者任务完成的消息之后,该如何确定完的是哪一个任务呢?

在现实情况,消息生产者通常会发出多个任务,多个消息消费者分别进行不同的任务,这时候我们就需要知道是哪个消息消费者完成了任务。

当消息生产者调用channel对象的BasicPublish方法发送消息时,IBasicProperties对象除了可以帮助我们传递消息生产者监听的消息队列名,还可以帮我们传递一个CorrelationId(相关Id),当发送任务消息的时候,我们给每个任务消息定义一个唯一的相关Id, 并存储在IBasicProperties对象的CorrelationId属性中。

var properties = channel.CreateBasicProperties();

properties.ReplyTo = replyQueueName;

properties.CorrelationId = Guid.NewGuid().ToString();

这样消息消费者在接收到任务消息时,可以从Receive的ea参数中获取CorrelationId。当任务完成时,再将保存有这个CorrelationId的任务完成消息发送到消息生产者关注的消息队列中, 消息生产者就可以知道是哪个任务完成了

手动实现RabbitMQ的RPC

Send

static void Main(string[] args)

        {

            var factory = new ConnectionFactory()

            {

                HostName = "localhost"

            };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    var replyQueueName = channel.QueueDeclare().QueueName;

                    var consumer = new EventingBasicConsumer(channel);

                    channel.BasicConsume(queue: replyQueueName,

                                         autoAck: true,

                                         consumer: consumer);

 

                    channel.QueueDeclare(queue: "manual_rpc_queue",

                        durable: true,

                        exclusive: false,

                        autoDelete: false,

                        arguments: null);

 

                    string message = args[0];

                    var body = Encoding.UTF8.GetBytes(message);

 

                    var properties = channel.CreateBasicProperties();

                    properties.ReplyTo = replyQueueName;

                    properties.CorrelationId = Guid.NewGuid().ToString();

 

                    consumer.Received += (m, ea) =>

                    {

                        if (ea.BasicProperties.CorrelationId == properties.CorrelationId)

                        {

                            Console.WriteLine($"Task {properties.CorrelationId} completed.");

                            Console.WriteLine($"{Encoding.UTF8.GetString(ea.Body)}");

 

                            Environment.Exit(1);

                        }

 

                    };

 

                    channel.BasicPublish(exchange: "",

                        routingKey: "manual_rpc_queue",

                        basicProperties: properties,

                        body: body

                    );

 

                    Console.WriteLine("[x] Sent {0}", message);

                    Console.Read();

                }

            }

        }

Receive

static void Main(string[] args)

        {

            var factory = new ConnectionFactory() { HostName = "localhost" };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    channel.QueueDeclare(queue: "manual_rpc_queue",

                        durable: true,

                        exclusive: false,

                        autoDelete: false,

                        arguments: null);

 

                    channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);

 

                    var consumer = new EventingBasicConsumer(channel);

 

                    consumer.Received += (model, ea) =>

                    {

                        var body = ea.Body;

                        var message = Encoding.UTF8.GetString(body);

 

                        Thread.Sleep(4000);

                        Console.WriteLine("[x] Received {0}", message);

 

                        var prop = channel.CreateBasicProperties();

                        prop.CorrelationId = ea.BasicProperties.CorrelationId;

 

                        channel.BasicPublish(

                            exchange: "",

                            routingKey: ea.BasicProperties.ReplyTo,

                            mandatory: false,

                            basicProperties: prop,

                            body: Encoding.UTF8.GetBytes($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} Task {prop.CorrelationId} Completed."));

 

                        channel.BasicAck(ea.DeliveryTag, false);

                    };

 

                    channel.BasicConsume(queue: "manual_rpc_queue", autoAck: false, consumer: consumer);

 

                    Console.Read();

                }

            }

        }

最终效果

RabbitMQ提供的RPC实现

RabbitMQ提供了一个SimpleRpcClient类和SimpleRpcServer类,来简化我们的代码,代码不难理解

Send

static void Main(string[] args)

        {

            var factory = new ConnectionFactory()

            {

                HostName = "localhost"

            };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    SimpleRpcClient client = new SimpleRpcClient(channel, new PublicationAddress(exchangeType: ExchangeType.Direct, exchangeName: string.Empty, routingKey: "RpcQueue"));

 

                    var prop = channel.CreateBasicProperties();

                    prop.CorrelationId = Guid.NewGuid().ToString();

                    IBasicProperties outProp;

 

                    var msg = client.Call(prop, Encoding.UTF8.GetBytes(args[0]), out outProp);

 

                    if (prop.CorrelationId == outProp.CorrelationId)

                    {

                        Console.WriteLine($"Task {prop.CorrelationId} completed.");

                        Console.WriteLine(Encoding.UTF8.GetString(msg));

                    }

                }

            }

        }

Receive

  1. 创建MySimpleRpcServer类,继承自SimpleRpcServer类
  2. HandleSimpleCall方法里添加回调返回值
  3. ProcessRequest方法为任务处理方法

MySimpleRpcServer.cs

public class MySimpleRpcServer : SimpleRpcServer

    {

        public MySimpleRpcServer(Subscription subscription) : base(subscription)

        {

 

        }

 

        public override byte[] HandleSimpleCall(bool isRedelivered, IBasicProperties requestProperties, byte[] body, out IBasicProperties replyProperties)

        {

            replyProperties = null;

            return Encoding.UTF8.GetBytes($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} Task {requestProperties.CorrelationId} Completed.");

        }

 

        /// <summary>

        /// 进行处理

        /// </summary>

        /// <param name="evt"></param>

        public override void ProcessRequest(BasicDeliverEventArgs evt)

        {

            Console.WriteLine("[x] Received {0}", Encoding.UTF8.GetString(evt.Body));

            Thread.Sleep(4000);

            base.ProcessRequest(evt);

        }

    }

Program.cs

static void Main(string[] args)

        {

            var factory = new ConnectionFactory() { HostName = "localhost" };

 

            using (var connection = factory.CreateConnection())

            {

                using (var channel = connection.CreateModel())

                {

                    channel.QueueDeclare("RpcQueue", true, false, false, null);

 

                    SimpleRpcServer rpc = new MySimpleRpcServer(new Subscription(channel, "RpcQueue"));

 

                   

                    rpc.MainLoop();

                    Console.ReadKey();

                }

            }

        }

RabbitMQ学习笔记(六) RPC的更多相关文章

  1. rabbitMQ学习笔记(七) RPC 远程过程调用

    关于RPC的介绍请参考百度百科里的关于RPC的介绍:http://baike.baidu.com/view/32726.htm#sub32726 现在来看看Rabbitmq中RPC吧!RPC的工作示意 ...

  2. rabbitMQ学习笔记(六) topic类型消息。

    上一节中使用了消息路由,消费者可以选择性的接收消息. 但是这样还是不够灵活. 比如某个消费者要订阅娱乐新闻消息 . 包括新浪.网易.腾讯的娱乐新闻.那么消费者就需要绑定三次,分别绑定这三个网站的消息类 ...

  3. RabbitMQ学习笔记六:RabbitMQ之消息确认

    使用消息队列,必须要考虑的问题就是生产者消息发送失败和消费者消息处理失败,这两种情况怎么处理. 生产者发送消息,成功,则确认消息发送成功;失败,则返回消息发送失败信息,再做处理. 消费者处理消息,成功 ...

  4. 官网英文版学习——RabbitMQ学习笔记(一)认识RabbitMQ

    鉴于目前中文的RabbitMQ教程很缺,本博主虽然买了一本rabbitMQ的书,遗憾的是该书的代码用的不是java语言,看起来也有些不爽,且网友们不同人学习所写不同,本博主看的有些地方不太理想,为此本 ...

  5. # go微服务框架kratos学习笔记六(kratos 服务发现 discovery)

    目录 go微服务框架kratos学习笔记六(kratos 服务发现 discovery) http api register 服务注册 fetch 获取实例 fetchs 批量获取实例 polls 批 ...

  6. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  7. Learning ROS for Robotics Programming Second Edition学习笔记(六) indigo xtion pro live

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  8. RabbitMQ学习笔记(五) Topic

    更多的问题 Direct Exchange帮助我们解决了分类发布与订阅消息的问题,但是Direct Exchange的问题是,它所使用的routingKey是一个简单字符串,这决定了它只能按照一个条件 ...

  9. Typescript 学习笔记六:接口

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  10. RabbitMQ学习笔记1-hello world

    安装过程略过,一搜一大把. rabbitmq管理控制台:http://localhost:15672/   默认账户:guest/guest RabbitMQ默认监听端口:5672 JAVA API地 ...

随机推荐

  1. PTA_Have fun with numbers(C++)

    #include<iostream> #include<cstring> using namespace std; int main() { ; ]="; ]={}, ...

  2. 【web安全】-- springboot实现两次MD5加密

    一.为什么要做两次MD5 客户端MD5:HTTP在网络上是使用明文传输,用户输入的明文密码直接在网络上传输太危险.所以,在客户端先进行一次MD5(明文+固定盐). 服务端:服务端接受到后,也不是直接写 ...

  3. json格式new Date()的一个小坑

    见图:JSON.stringify( new Date(Date.parse('xxxx-xx-xx'))) 若是传的日期,在10号前,要进行转换.

  4. 查看Android应用包名、Activity的几个方法

    一.有源码情况 直接打开AndroidManifest.xml文件,找到包含android.intent.action.MAIN和android.intent.category.LAUNCHER对应的 ...

  5. Android应用程序如何使用Internet资源?

    思路:连接Internet资源-->分析XML资源-->使用Download Manager下载文件 Android的Internet连接模型和用于分析Internet数据源的Java技术 ...

  6. BZOJ 4665

    orz gery 一发rk1真有趣(其实我没想着常数优化 inline int sqr(int x){return 1ll*x*x%mo;} const int N=2011; int n,a[N], ...

  7. TimesTen数据库表中显示中文乱码的真正原因

    上一篇博客TimesTen中文乱码问题(其实是cmd.exe中文乱码)的内容可能不对,也许只是个巧合?不得而知了.因为我今天重装系统了,把win10换成了win7(64bit).又安装了timeste ...

  8. react 的进阶

    一 react 中table报错 validateDOMNesting(...): <tr> cannot appear as a child of <table>. See ...

  9. Go语言基础(二)

    Go语言基础(二) 跟着上篇,继续看Go基础 一.变量作用域 与C类似,有全局变量.局部变量.形参之分 package main import "fmt" // 全局变量 var ...

  10. ionic3+angular4开发混合app 之自定义组件

    这里主要是记录ionic3+angular4开发混合app时自定义组件,我想自定义组件的方法和angular4应该类似,具体在纯angular4中自定义组件,暂时没有实践,个人觉得差别不大,之后实践了 ...