什么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. ECMAScript 6 之 let 和 const 命令

    1.let基本用法 1.1.声明变量 let声明的变量只在它所在的代码块有效. 1.2.不存在变量提升 var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined: let命令 ...

  2. BlockChain:Py实现区块链简单场景应用:程序猿记录在区块里的收入记录图——Jason niu

    # -*- coding: utf-8 -*- ''' Created on 2018年3月11日 @author: Jason niu ''' import hashlib #该模块实现了诸多安全哈 ...

  3. vue与avuex

    现在 使用avuex做出来表格效果,但是看到源码看到需要使用vue,不得不开始学习vue 配置环境:cnpm配置过程:a:首先下载node.js然后根据https://www.cnblogs.com/ ...

  4. MachineLearningOnCoursera

    Week Six F Score \[\begin{aligned} P &= &\dfrac{2}{\dfrac{1}{P}+\dfrac{1}{R}}\\ &= & ...

  5. 在dcef3当中执行js代码并获得返回值

    1.如何在dcef3当中执行js代码 procedure TForm1.btnWriteZMClick(Sender: TObject);var  js: string;begin  js := 'd ...

  6. linux系统资源监控

    top命令 1.平均负载(load average): 正在耗费CPU进程与正在等待io的进程之和,三个值分别是一分钟,五分钟,十五分钟的平均负载,负载值只要小于CPU颗粒数属于正常情况 任务进程(T ...

  7. Java Web 获取客户端真实IP

    Java Web 获取客户端真实IP 发生的场景:服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等情况,在进行客户端IP限定的时候,需要首先获取该真实的IP.一般分为两种情况: 方 ...

  8. vue 源码学习(一) 目录结构和构建过程简介

    Flow vue框架使用了Flow作为类型检查,来保证项目的可读性和维护性.vue.js的主目录下有Flow的配置.flowconfig文件,还有flow目录,指定了各种自定义类型. 在学习源码前可以 ...

  9. js 单行注释

    不可以: var a = 1;//这是注释 应当: var a = 1; //这是注释 1

  10. 完整的系统帮助类Utils

    //来源:http://www.cnblogs.com/yuangang/p/5477324.html using System; using System.Collections.Generic; ...