每一个孤独的灵魂都需要陪伴
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>