RabbitMQ .NET消息队列使用入门(三)【MVC实现RPC例子】

每一个孤独的灵魂都需要陪伴

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

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

有多种 RPC模式和执行。最初由 Sun 公司提出。IETF ONC 宪章重新修订了 Sun 版本,使得 ONC RPC 协议成为 IETF 标准协议。现在使用最普遍的模式和执行是开放式软件基础的分布式计算环境(DCE)。

工作原理(以Windows下为例)

运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据

RPC OVER HTTP

Microsoft RPC-over-HTTP 部署(RPC over HTTP)允许RPC客户端安全和有效地通过Internet 连接到RPC 服务器程序并执行远程过程调用。这是在一个名称为RPC-over-HTTP 代理,或简称为RPC 代理的中间件的帮助下完成的。
RPC 代理运行在IIS计算机上。它接受来自Internet 的RPC 请求,在这些请求上执行认证,检验和访问检查,如果请求通过所有的测试,RPC 代理将请求转发给执行真正处理的RPC 服务器。通过RPC over HTTP,RPC客户端不和服务器直接通信,它们使用RPC 代理作为中间件。

新建一个控制台程序,Program.cs

using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Common;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions;

namespace RPCServer
{
     class Program
    {
        private static IConnection _recvConn; //接收消息的连接
        private static IConnection _senderConn; //返回结果的连接
        private static IModel _recvChannel; //接收消息的信道
        private static IModel _sendChannel; //返回结果的信道
        private static bool isExit;

        private static void Main(string[] args)
        {
            Setup();

            Console.WriteLine("开始使用RPC消息:");

            WaitCommand();
        }

        private static void Setup()
        {
            var factory = new ConnectionFactory
            {
                HostName = "localhost",
                UserName = "guest",
                Password = "guest",
                //VirtualHost = "test",
                AutomaticRecoveryEnabled = true
            };

            try
            {
                _recvConn = factory.CreateConnection();
                _recvChannel = _recvConn.CreateModel();
                _recvChannel.QueueDeclare("rpcQueue", false, false, false, null);
                _recvChannel.BasicQos(0, 10, false);
                var consumer = new EventingBasicConsumer(_recvChannel);
                consumer.Received += consumer_Received;
                _recvChannel.BasicConsume("rpcQueue", false, consumer);

                _senderConn = factory.CreateConnection();
                //_sendChannel = _senderConn.CreateModel();
            }
            catch (BrokerUnreachableException ex)
            {
                Console.WriteLine("RabbitMQ服务器尚未启动!");
                Thread.Sleep(2000);
                isExit = true;
            }
        }

        /// <summary>
        ///     等待接收指令
        /// </summary>
        private static void WaitCommand()
        {
            while (!isExit)
            {
                string line = Console.ReadLine().ToLower().Trim();
                switch (line)
                {
                    case "exit":
                        Close();
                        isExit = true;
                        break;
                    case "clear":
                        Console.Clear();
                        break;
                    default:
                        break;
                }
            }

            Console.WriteLine("Goodbye!");
        }

        private static void Close()
        {
            if (_recvChannel != null && _recvChannel.IsOpen)
            {
                _recvChannel.Close();
            }

            if (_recvConn != null && _recvConn.IsOpen)
            {
                _recvConn.Close();
            }

            if (_senderConn != null && _senderConn.IsOpen)
            {
                _senderConn.Close();
            }
        }

        private static void consumer_Received(object sender, BasicDeliverEventArgs e)
        {
            byte[] body = e.Body;

            Task.Run(() => HandlingMessage(body, e));
        }

        /// <summary>
        ///     消息处理
        /// </summary>
        /// <param name="msgModel"></param>
        /// <param name="e"></param>
        private static async void HandlingMessage(byte[] body, BasicDeliverEventArgs e)
        {
            bool isSuccess = false;
            bool hasRejected = false;
            string message = Encoding.UTF8.GetString(body);
            string replyMsg = "";
            IModel _senderChannel = null;

            try
            {
                _senderChannel = _senderConn.CreateModel(); //多线程中每个线程使用独立的信道
                replyMsg = message + "   处理成功";

                var random = new Random();
                int num = random.Next(0, 4);

                //模拟处理失败
                /*if (random.Next(0, 11) == 4)
                {
                    throw new Exception("处理失败", null);
                }*/

                //模拟解析失败
                if (random.Next(0, 11) == 8)
                {
                    throw new MessageException("消息解析失败");
                }
                //await Task.Delay(num * 1000);   //模拟消息处理

                //这里简单处理,仅格式化输出消息内容
                Console.WriteLine("Time:" + DateTime.Now + " ThreadID:" + Thread.CurrentThread.ManagedThreadId +
                                  " Used: " + num + "s MSG:" + message);

                isSuccess = true;
            }
            catch (MessageException msgEx)
            {
                Console.WriteLine("Time:" + DateTime.Now + " ThreadID:" + Thread.CurrentThread.ManagedThreadId +
                                  " ERROR:" + msgEx.Message + " MSG:" + message);
                _recvChannel.BasicReject(e.DeliveryTag, false); //不再重新分发
                hasRejected = true;
                replyMsg = message + "解析失败";
                isSuccess = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Time:" + DateTime.Now + " ThreadID:" + Thread.CurrentThread.ManagedThreadId +
                                  " ERROR:" + ex.Message + " MSG:" + message);
                replyMsg = "处理失败";
            }

            if (isSuccess)
            {
                try
                {
                    IBasicProperties props = e.BasicProperties;
                    IBasicProperties replyProps = _senderChannel.CreateBasicProperties();
                    replyProps.CorrelationId = props.CorrelationId;
                    _senderChannel.BasicPublish("", e.BasicProperties.ReplyTo, replyProps, Encoding.UTF8.GetBytes(replyMsg)); //发送消息到内容检查队列
                    if (!hasRejected)
                    {
                        _recvChannel.BasicAck(e.DeliveryTag, false); //确认处理成功  此处与不再重新分发,只能出现一次
                    }
                }
                catch (AlreadyClosedException acEx)
                {
                    Console.WriteLine("ERROR:连接已关闭");
                }
            }
            else
            {
                _recvChannel.BasicReject(e.DeliveryTag, true); //处理失败,重新分发
            }
            _senderChannel.Close();
        }
    }
}

MVC站点

TestViewModel.cs代码:

 public class TestViewModel
 {
        public string ReplyMessage { get; set; }
 }

TestController.cs代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Text;
using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using Common;
using WebApp.Models;
using System.Threading.Tasks;

namespace WebApp.Controllers
{
    public class TestController : AsyncController
    { 
        // GET: /Test/
        public async Task<ActionResult> Index()
        { 
                TestViewModel viewModel = new TestViewModel() { ReplyMessage = "交换机关闭着." }; 
                return View(viewModel); 
        }

        [HttpPost]
        public async Task<MvcHtmlString> DoWork()
        {
            string message = "消息:" + new Random().Next(1, 10000).ToString();
            string replyMsg = "";
            RPCClient client = null;
            try
            { 
                client = RPCClient.GetInstance();
                replyMsg = await client.CallAsync(message);
            }
            catch (AlreadyClosedException e)
            {
                replyMsg = "交换机关闭着.";
            }
            catch (NoRPCConsumeException ex)
            {
                replyMsg = ex.Message;
            }
            catch (Exception e)
            {
                replyMsg = e.Message;
            } 

            return new MvcHtmlString("<p>" + replyMsg + "</p>");
        }

        public async Task<ActionResult> Serv()
        {
            string message = "消息:" + new Random().Next(1, 10000).ToString();
            var client = new ServiceReference1.WebService1SoapClient();
            string reply = (await client.HandlerMessageAsync(message)).Body.HandlerMessageResult;
            TestViewModel viewModel = new TestViewModel() { ReplyMessage = reply };
            return View("Index", viewModel);
        }
     }
}

Index.cshtml

@model WebApp.Models.TestViewModel

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <input type="button" name="sendBtn" id="sendBtn" value="发一条消息到队列" />
    <div id="receiveArea"> 
        <p>@Model.ReplyMessage</p>
    </div>
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <script>
        $("#sendBtn").on("click", function () {
            $.ajax({
                url: "/Test/DoWork",
                dataType: "html",
                type: "POST",
                success: function (result) {
                    console.log(result);
                    $("#receiveArea").append($(result));
                }
            });
        });
    </script>
</body>
</html>

运行结果如图:

这里写图片描述

这里写图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值