一、引言
按照专用队列解释: MachineName\Private$\QueueName,只针对于本机的程序才可以调用的队列,有些情况下为了安全起见定义为私有队列。所以刚开始的时候认为,要想访问远程消息队列,只能使用公共队列。但是后来发现,公共队列依赖Domain Controller(域控),在实际部署的时候,要求使用消息队列的应用一定要在某个域中,有些太苛刻!后来发现,私有队列也是可以远程访问的。(很困惑为什么私有队列只能本地访问,这句话,到处都能看到?!)
二、工作组下的本地C/S
2.1、项目建立
新建4个项目:
2.2、项目代码
2.2.1、Model项目


/// <summary>
/// 消息队列实体
/// </summary>
[Serializable]
public class MqMessage
{
/// <summary>
/// 对应Message的Label
/// </summary>
public string Label { get; set; }
/// <summary>
/// 对应Message的Body,CommandType为操作类型,List<string>为操作列表。
/// </summary>
public Dictionary<CommandType, List<string>> Body { get; set; } = new Dictionary<CommandType, List<string>>();
/// <summary>
/// 无参构造函数
/// </summary>
public MqMessage()
{
}
/// <summary>
/// 有参构造函数
/// </summary>
/// <param name="label"></param>
/// <param name="body"></param>
public MqMessage(string label, Dictionary<CommandType, List<string>> body)
{
Label = label;
Body = body;
}
}
/// <summary>
/// 操作类型
/// </summary>
public enum CommandType
{
Create = 1, //创建
Update = 2, //更新
Delete = 3 //删除
}
2.2.2、Common项目


/// <summary>
/// 日志帮助类
/// </summary>
public static class LogHelper
{
private static readonly string errLogSavePath = ConfigurationManager.AppSettings["ErrLogSavePath"] ?? AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// 异常日志方法重载
/// </summary>
/// <param name="ex">异常信息</param>
public static void WriteLog(Exception ex)
{
WriteLog(GetErrMsg(ex));
}
/// <summary>
/// 异常日志方法重载
/// </summary>
/// <param name="message">日志内容</param>
public static void WriteLog(string message)
{
WriteLog(errLogSavePath, message);
}
/// <summary>
/// 异常日志方法重载
/// </summary>
/// <param name="filepath">日志文件路径</param>
/// <param name="message">日志内容</param>
public static void WriteLog(string filepath, string message)
{
try
{
if (!Directory.Exists(filepath))
{
Directory.CreateDirectory(filepath);
}
string filename = DateTime.Now.ToString("yyyy-MM-dd") + ".txt";
using (StreamWriter sw = new StreamWriter(filepath + "\\" + filename, true))
{
sw.WriteLine("--------------------------------------------");
sw.WriteLine($"{DateTime.Now.ToLongTimeString()}:{DateTime.Now.Millisecond}\t{message}");
sw.Close();
}
}
catch (Exception ex)
{
throw new Exception(GetErrMsg(ex));
}
}
/// <summary>
/// 获取异常详细信息
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
private static string GetErrMsg(Exception ex)
{
string errMessage = "";
for (Exception tempException = ex; tempException != null; tempException = tempException.InnerException)
{
errMessage += tempException.Message + Environment.NewLine + Environment.NewLine;
}
errMessage += ex.ToString();
return errMessage;
}
}


/// <summary>
/// 消息队列管理器
/// </summary>
public class MqManager : IDisposable
{
private MessageQueue _mq = null;
private readonly LinkType linkType = LinkType.LocalHost; //链接类型,远程时使用LinkType.RemoteServer。
private readonly string remoteServer = "192.168.2.165"; //远程服务器IP地址
public static MqManager LinkServer { get; } = new MqManager();
/// <summary>
/// 初始化函数
/// </summary>
/// <param name="linkType">链接类型</param>
public void MqManagerInit(LinkType linkType)
{
if (_mq == null)
{
string _path;
if (linkType == LinkType.LocalHost)
{
_path = @".\private$\" + (ConfigurationManager.AppSettings["MSMQName"] ?? "HelloWorld");
}
else
{
_path = "FormatName:DIRECT=TCP:" + remoteServer + @"\private$\" + (ConfigurationManager.AppSettings["MSMQName"] ?? "HelloWorld");
}
_mq = new MessageQueue(_path)
{
Formatter = new BinaryMessageFormatter()
};
}
}
/// <summary>
/// 有参构造函数
/// </summary>
public MqManager()
{
MqManagerInit(linkType);
}
/// <summary>
/// 发送消息队列(事务)
/// </summary>
/// <param name="message"></param>
public void Send(MqMessage message)
{
MessageQueueTransaction transaction = new MessageQueueTransaction();
transaction.Begin();
_mq.Send(message.Body, message.Label, transaction);
transaction.Commit();
}
/// <summary>
/// 接收消息队列
/// </summary>
/// <returns></returns>
public Message Receive()
{
Message msg = null;
try
{
msg = _mq.Receive(new TimeSpan(0, 0, 1));
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return msg;
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
if (_mq != null)
{
_mq.Close();
_mq.Dispose();
_mq = null;
}
}
}
/// <summary>
/// 链接类型
/// </summary>
public enum LinkType
{
LocalHost = 1, //本地服务器
RemoteServer = 2 //远程服务器
}
2.2.3、Send项目


class Program
{
static void Main(string[] args)
{
MqMessage mqMessage = new MqMessage();
List<string> list = new List<string>();
Console.WriteLine("请输入内容按回车发送,多个内容请用英文逗号隔开,退出请输入Exit。");
string receiveKey = Console.ReadLine();
while (receiveKey.ToLower() != "exit")
{
if (receiveKey.Length > 0)
{
mqMessage.Label = Guid.NewGuid().ToString();
list.Clear();
list = receiveKey.Split(new char[] { ',' }).ToList();
mqMessage.Body.Clear();
mqMessage.Body.Add(CommandType.Create, list);
try
{
MqManager.LinkServer.Send(mqMessage);
Console.WriteLine("内容已发送成功。");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
LogHelper.WriteLog(ex);
}
}
receiveKey = Console.ReadLine();
}
MqManager.LinkServer.Dispose();
}
}
2.2.4、Receive项目


/// <summary>
/// 接收消息队列管理(线程)
/// </summary>
public class ReceiveManager : IDisposable
{
private Thread _thread = null;
public static ReceiveManager Instance { get; set; } = new ReceiveManager();
/// <summary>
/// 开始
/// </summary>
public void Start()
{
StartReceive();
}
/// <summary>
/// 接收线程
/// </summary>
private void StartReceive()
{
_thread = new Thread(new ThreadStart(Receive))
{
Name = "ReceiveThread",
IsBackground = true
};
_thread.Start();
}
/// <summary>
/// 接收线程调用方法
/// </summary>
private void Receive()
{
Message msg = null;
while (true)
{
try
{
msg = MqManager.LinkServer.Receive();
if (msg != null)
{
Console.WriteLine("----------------------------------------------------");
Console.WriteLine("Lable: " + msg.Label);
Dictionary<CommandType, List<string>> keyValuePairs = msg.Body as Dictionary<CommandType, List<string>>;
Console.WriteLine("Body CommandType: " + keyValuePairs.Keys.First());
Console.WriteLine("Body Details: ");
foreach (var item in keyValuePairs.Values.First())
{
Console.WriteLine(item);
}
Console.WriteLine("----------------------------------------------------");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
LogHelper.WriteLog(ex);
}
Thread.Sleep(1000);
}
}
/// <summary>
/// 结束
/// </summary>
public void Stop()
{
Dispose();
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
try
{
if (_thread != null)
{
_thread.Abort();
_thread.Join();
_thread = null;
}
MqManager.LinkServer.Dispose();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}


class Program
{
static void Main(string[] args)
{
ReceiveManager.Instance.Start();
Console.WriteLine("退出请输入Exit");
string receiveKey = Console.ReadLine();
while (receiveKey.ToLower() != "exit")
{
receiveKey = Console.ReadLine();
}
ReceiveManager.Instance.Stop();
Console.Read();
}
}
2.3、运行测试
客户端发送hello,world:
服务端接收到的信息:
三、工作组下的远程C/S
3.1、代码调整
工作组下的远程C/S,代码已经在上面的示例中提供,将Common\MqManager.cs下的:
private readonly LinkType linkType = LinkType.LocalHost;改成private readonly LinkType linkType = LinkType.RemoteServer;即可。
3.2、访问权限
既然要与远程服务器交互(发送/接收)队列信息,首当其冲的是访问权限问题,没有权限,一切免谈。
下面讲一下远程服务器(代码中的192.168.2.165,Win7系统)要设置的内容:
3.2.1、在运行中输入compmgmt.msc->服务和应用程序->消息队列->右键属性->服务器安全性->禁用未经身份验证的 RPC 调用->把勾勾去掉->应用。
3.2.2、在消息队列->专用队列->新建一个代码中用到的HelloWorld队列,勾上事务性->确定。
为什么要手工建HelloWorld消息队列?因为要对这个队列进行匿名访问授权,后面会讲到。至于事务性这个勾,这个要与代码相一致。因为本示例中使用了MessageQueueTransaction来发送事务信息,所以必须得勾上这个勾,不然的话,发送时没有任何的报错信息,但是服务器就是收不到队列信息。
3.2.3、专用队列->HelloWorld->右键属性->安全->ANONYMOUS LOGON->完全控制->应用。
3.2.4、在运行中输入regedit->HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSMQ\Parameters\security->新建两个DWORD值:AllowNonauthenticatedRpc、NewRemoteReadServerDenyWorkgroupClient->分别双击将数值数据改成1。
3.2.5、关于防火墙,我是关闭了的,假如您的电脑防火墙是打开了的话,请检查一下Message Queuing是不是被允许的?
3.3、运行测试
客户端发送A,B,C,D:
服务器端接收到的信息:
参考自:
https://www.cnblogs.com/xinhaijulan/archive/2010/08/22/1805768.html
https://www.cnblogs.com/minily/p/7397746.html
https://blog.csdn.net/jiyiqinlovexx/article/details/17803857
https://www.cnblogs.com/mmbbflyer/p/7773303.html
来源:oschina
链接:https://my.oschina.net/u/4323704/blog/3230520