C#使用NETMQ进行通信并且使用RSA进行消息加密

C#使用NETMQ进行通信并且使用RSA进行消息加密

  • C#使用NetMQ
    • 使用NETMQ
    • 属性和构造函数
    • 消息类
    • 消息处理函数
    • RSA类
    • 客户端
    • 后记

C#使用NetMQ

NetMQ是我在网吧项目中所使用的一种消息队列,是ZeroMQ在.net平台上的移植版本,非常轻量级,使用方便,响应速度快,代码也便于阅读和编写,但是功能比较少,适合在小型项目中进行使用。
这次在项目中,需要对发往客户端的消息进行加密,然后客户端自行解密来确保发送的消息不会被别人看到,在这种情况下,很自然的想到了使用非对称加密算法来对消息进行加密(其实在之前有想过采用类似WCF中的身份认证方式来防止别人连接到这个接口,但是我翻阅了一下官方文档,NetMQ似乎并没有这个功能,所以最后采用了消息加密的功能,这个当然也是一个比较普遍的方式)。

使用NETMQ

NetMQ的使用还是非常方便的,在项目中直接引用netmq.dll就可以直接使用了,所以这里就不再赘述如何下载安装,直接进入正题:如何使用NetMQ。

属性和构造函数

 public class ShowServer
    {
        private static ShowServer showServer = null;
        private RouterSocket ServerSocket;
        private NetMQPoller Poller;
        private NetMQMonitor Monitor;
        public Queue<MsgObject> SendMessageQueue;
        public IProgress<MsgObject> ReceiveHanlder;
        private IProgress<MsgObject> receiveHanlder;
        private Queue<MsgObject> EventQueue;

        public bool IsRunning { get; set; }
        /// 
        /// 构造函数
        /// 
        private ShowServer()
        {
            SendMessageQueue = new Queue<MsgObject>();
            EventQueue = new Queue<MsgObject>();
            ServerSocket = new RouterSocket("tcp://0.0.0.0:6667");
            ServerSocket.ReceiveReady += ServerSocket_ReceiveReady;
            ServerSocket.SendReady += ServerSocket_SendReady;
            ServerSocket.Options.RouterHandover = true;
            Monitor = new NetMQ.Monitoring.NetMQMonitor(ServerSocket, "inproc://show.inproc", SocketEvents.All);
            Monitor.Accepted += Monitor_Accepted;
            Monitor.Disconnected += Monitor_Disconnected;
            receiveHanlder = new Progress<MsgObject>(HandleMsg);
            ReceiveHanlder = receiveHanlder;
            Poller = new NetMQPoller() { ServerSocket };
        }
        public static ShowServer GetServer()
        {
            if(showServer == null){
                showServer = new ShowServer();
            }
            return showServer;
        }
 private void Monitor_Accepted(object sender, NetMQMonitorSocketEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now + "连接上 ,address:" + e.Address);
        }

        private void Monitor_Disconnected(object sender, NetMQMonitorSocketEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(DateTime.Now + "" + e.Address);
        }

        private void ServerSocket_SendReady(object sender, NetMQSocketEventArgs e)
        {
            while (SendMessageQueue.Count > 0)
            {
                try
                {
                    var sendMsg = SendMessageQueue.Dequeue();
                    if (sendMsg.Content == null)
                    {
                        continue;
                    }
                    var msgFrame = ConvertToNetMessage(sendMsg);
                    ServerSocket.SendMultipartMessage(msgFrame);
                }
                catch (Exception ex)
                {
                    Log.WriteLog_EX(ex.ToString());
                }

            }

            //发送数据间隔
            Thread.Sleep(10);

        }

        private NetMQMessage ConvertToNetMessage(MsgObject sendMsg)
        {
            var msgFrame = new NetMQMessage();
            msgFrame.Append(sendMsg.MachineName);
            msgFrame.AppendEmptyFrame();
            msgFrame.Append(sendMsg.Content, Encoding.UTF8);
            return msgFrame;
        }

        private MsgObject ConvertToMsgObj(NetMQMessage sendMsg)
        {
            if (sendMsg.FrameCount == 3)
            {
                var newMsg = new MsgObject();
                var machineName = sendMsg[0].ConvertToString();
                //var content = sendMsg[2].ConvertToString();
                var content = Encoding.UTF8.GetString(sendMsg[2].Buffer);
                newMsg.MachineName = machineName;
                newMsg.Content = content;

                return newMsg;
            }
            return null;
        }

        private void ServerSocket_ReceiveReady(object sender, NetMQSocketEventArgs e)
        {
            var msg = ConvertToMsgObj(ServerSocket.ReceiveMultipartMessage());

            //接收到消息,让handler处理
            if (msg != null)
            {
                ReceiveHanlder?.Report(msg);
            }

        }

        public void Start()
        {
            IsRunning = true;
            Poller.RunAsync();
            //Monitor.StartAsync();
        }
        public void Stop()
        {
            IsRunning = false;
            Poller.StopAsync();
        }

        public void EventEnqueue(MsgObject msg)
        {
            EventQueue.Enqueue(msg);
        }
 	}

在这个例子中展示了如何生成一个服务端,当然这边的代码并不按照官网网站上所示例的。官网上的示例代码是这个样子的:

using (var rep1 = new ResponseSocket("@tcp://*:5001"))
using (var rep2 = new ResponseSocket("@tcp://*:5002"))
using (var poller = new NetMQPoller { rep1, rep2 })
{
    // these event will be raised by the Poller
    rep1.ReceiveReady += (s, a) =>
    {
        // receive won't block as a message is ready
        string msg = a.Socket.ReceiveString();
        // send a response
        a.Socket.Send("Response");
    };
    rep2.ReceiveReady += (s, a) =>
    {
        // receive won't block as a message is ready
        string msg = a.Socket.ReceiveString();
        // send a response
        a.Socket.Send("Response");
    };
    // start polling (on this thread)
    poller.Run();
}

在官方文档上的代码写的更加简洁一点(这个例子其实是接收端的,官网上的发送端的例子居然还是TODO),直接使用了using语句,using语句是指函数在离开这个using块的时候直接dispose using括号内所占用的资源。然后官网例子上的receiveRead是直接使用了lambda表达式写一个比较简洁的函数,然后把这个函数挂载到事件钩子上。
poller是一个比较重要的点,poller的意思为轮询器,当需要一次产生多个端口的时候,则采用poller是一个比较合理的方式,根据官方文档上的说明,如果不使用poller,接收消息的时候将会永远阻塞在第一个rep1处。同时,NetMQ并非是一个线程安全的消息队列,比如你想在一个端口上接收消息,然后在另外一个端口上发送消息的话也必须使用poller。
关于NetMQMonitor我并没有在官方网站上找到相应的说明,可能是已经不再使用了,也可能是提供给我这个dll的人魔改过了源码。但是从功能来看应该是一个监视器,当端口接收或者发送消息的时候能够挂载一些其他的方法。构造函数中的第一个参数是端口,第二个参数应该是进程间通信的一个管道名,第三个则是端口的哪些事件被监听。
ReceiveHanlder则是一个接收到消息之后的钩子,在这边可以添加一个消息处理函数,根据不同的消息进行不同的处理。

消息类

在这次的项目中,单一的消息类型肯定是不能解决所有问题的,所以还需要消息分类,消息处理函数根据消息的不同类型进行不同的操作。

public class MsgObject
    {
        public string MachineName;
        public string Content;
        
        public class ShowLoginMsg
        {
        public string cardId;
        public string realName;
        public string memLevel;
        public string machineName;
        public string startTime;
        public long timeStamp = TimeHelper.ConvertToTimestamp(DateTime.Now);
        }
        public MsgObject CreatShowLoginMsg(string machineName, Customer customer)
        {
            ShowLoginMsg temp = new ShowLoginMsg();
            temp.cardId = customer.TypeId;
            temp.machineName = machineName;
            temp.memLevel = customer.MemLevel;
            temp.realName = customer.Name;
            temp.timeStamp = TimeHelper.ConvertToTimestamp(DateTime.Now);
            MsgObject msg = new MsgObject();
            msg.Content = JsonConvert.SerializeObject(temp,
                        new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
            return msg;
        }
   }

在这个示例中,MsgObject是一个总体的消息类,一共有两个属性:machineName和content,对于发送端来说,machineName标志了应该把消息发送给哪一台机器,content则是序列化后的消息类,在上面的代码片段中可以看到,我在MsgObject类中声明了一个CreatShowLoginMsg方法,在这个方法中传入一些必要的参数,然后返回一个msg。当然也可以直接把这个msg压入消息队列中。

消息处理函数

 private void HandleMsg(MsgObject msg)
        {
            var temp2 = JsonConvert.DeserializeObject(msg.Content,
                                new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
            if (temp2.GetType() == typeof(ShowMsg))
            {
                ShowMsg tempMsg = (ShowMsg)temp2;
                if (tempMsg.publickey.Equals(RSAEncryption.PublicKey))
                {
                    while (EventQueue.Count != 0)
                    {
                        try
                        {
                            MsgObject sendmsg = EventQueue.Dequeue();
                            sendmsg.Content = RSAEncryption.RSAEncrypt(sendmsg.Content);
                            sendmsg.MachineName = msg.MachineName;
                            SendMessageQueue.Enqueue(sendmsg);
                        }
                        catch(ArgumentException ex)
                        {
                            Log.WriteLog_EX(ex.ToString());
                        }
                        catch (Exception ex)
                        {
                            Log.WriteLog_EX(ex.ToString());
                        }
                    }
                }
            }
        }

在这个方法中,服务器接收到消息后,先验证一下消息中传过来的公钥是否与本地保存的公钥是否相同,如果相同的话则把本地消息事件队列中的消息读取出来,加密之后再压入消息发送方队列中(这边主要是由于需求所致,其实只需要发送消息的时候把消息内容进行加密,然后压入发送消息队列就行了)。

RSA类

 public class RSAEncryption
    {
        private static string publicKey;

        public static string PublicKey
        {
            get
            {
                return publicKey;
            }

            set
            {
                publicKey = value;
            }
        }
        /// 
        /// 生成公私钥对
        /// 
        /// 
        /// 
        public static void RSAGenerateKey(ref string privateKey, ref string publicKey)
        {
            if (!File.Exists("PrivateKey.xml"))
            {
                RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                //私钥
                using (StreamWriter writer = new StreamWriter("PrivateKey.xml"))
                {
                    privateKey = rsa.ToXmlString(true);
                    writer.WriteLine(rsa.ToXmlString(true));

                }
                //公钥
                using (StreamWriter writer = new StreamWriter("PublicKey.xml"))
                {
                    publicKey = rsa.ToXmlString(false);
                    writer.WriteLine(rsa.ToXmlString(false));

                }
            }
            else
            {
                using (StreamReader read = new StreamReader("PrivateKey.xml"))
                {
                    privateKey = read.ReadLine();
                }
                using (StreamReader read = new StreamReader("PublicKey.xml"))
                {
                    publicKey = read.ReadLine();
                }
            }
        }
        /// 
        /// 重新生成公私钥对
        /// 
        /// 
        /// 
        public static void RSAReGenerateKey(ref string privateKey, ref string publicKey)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            //私钥
            using (StreamWriter writer = new StreamWriter("PrivateKey.xml", false))
            {
                privateKey = rsa.ToXmlString(true);
                writer.WriteLine(rsa.ToXmlString(true));

            }
            //公钥
            using (StreamWriter writer = new StreamWriter("PublicKey.xml", false))
            {
                publicKey = rsa.ToXmlString(false);
                writer.WriteLine(rsa.ToXmlString(false));

            }
        }
        /// 
        /// 控制台获得公钥
        /// 
        static public void GetPublickey()
        {
            if (File.Exists("PublicKey.xml"))
            {
                using (StreamReader read = new StreamReader("PublicKey.xml"))
                {
                    PublicKey = read.ReadLine();
                }
            }else
            {
                PublicKey = "";
            }
        }
        /// 
        /// 控制台更改公钥
        /// 
        /// 
        static public void ChangePublickey(string publickey)
        {
            using (StreamWriter writer = new StreamWriter("PublicKey.xml", false))
            {
                PublicKey = publickey;
                writer.WriteLine(publickey);
            }
        }
        ///   
        /// 加密  
        ///   
        /// 所加密的内容  
        /// 加密后的内容  
        static public string RSAEncrypt(string rawInput)
        {
            if (string.IsNullOrEmpty(rawInput))
            {
                return string.Empty;
            }

            if (string.IsNullOrWhiteSpace(PublicKey))
            {
                throw new ArgumentException("Invalid Public Key");
            }

            using (var rsaProvider = new RSACryptoServiceProvider())
            {
                var inputBytes = Encoding.UTF8.GetBytes(rawInput);//有含义的字符串转化为字节流
                rsaProvider.FromXmlString(PublicKey);//载入公钥
                int bufferSize = (rsaProvider.KeySize / 8) - 11;//单块最大长度
                var buffer = new byte[bufferSize];
                using (MemoryStream inputStream = new MemoryStream(inputBytes),
                     outputStream = new MemoryStream())
                {
                    while (true)
                    { //分段加密
                        int readSize = inputStream.Read(buffer, 0, bufferSize);
                        if (readSize <= 0)
                        {
                            break;
                        }

                        var temp = new byte[readSize];
                        Array.Copy(buffer, 0, temp, 0, readSize);
                        var encryptedBytes = rsaProvider.Encrypt(temp, false);
                        outputStream.Write(encryptedBytes, 0, encryptedBytes.Length);
                    }
                    return Convert.ToBase64String(outputStream.ToArray());//转化为字节流方便传输
                }
            }
        }

        ///   
        /// 解密  
        ///   
        /// 私钥  
        /// 加密后的内容  
        /// 解密后的内容  
        static public string RSADecrypt(string privateKey, string encryptedInput)
        {
            if (string.IsNullOrEmpty(encryptedInput))
            {
                return string.Empty;
            }

            if (string.IsNullOrWhiteSpace(privateKey))
            {
                throw new ArgumentException("Invalid Private Key");
            }

            using (var rsaProvider = new RSACryptoServiceProvider())
            {
                var inputBytes = Convert.FromBase64String(encryptedInput);
                rsaProvider.FromXmlString(privateKey);
                int bufferSize = rsaProvider.KeySize / 8;
                var buffer = new byte[bufferSize];
                using (MemoryStream inputStream = new MemoryStream(inputBytes),
                     outputStream = new MemoryStream())
                {
                    while (true)
                    {
                        int readSize = inputStream.Read(buffer, 0, bufferSize);
                        if (readSize <= 0)
                        {
                            break;
                        }

                        var temp = new byte[readSize];
                        Array.Copy(buffer, 0, temp, 0, readSize);
                        var rawBytes = rsaProvider.Decrypt(temp, false);
                        outputStream.Write(rawBytes, 0, rawBytes.Length);
                    }
                    return Encoding.UTF8.GetString(outputStream.ToArray());
                }
            }
        }
    }

这个类基本没什么好说的,这里比较值得一提的是加密解密的方法,如果根据C#RSA库中的示例方法来写的话会报明文长度过长的错误,这是因为RSA的明文长度和公钥长度有关,具体详情可以查看这个博客,所以这边需要把明文分段加密,解密也是同样。

客户端

    public class NetMQClient
    {
        private DealerSocket ClientSocket;
        private NetMQPoller Poller;
        public Queue<MsgObject> SendMessageQueue;
        public IProgress<MsgObject> ReceiveHanlder;
        public bool IsRunning { get; set; }

        public string OriginalConIP1
        {
            get
            {
                return OriginalConIP;
            }

            set
            {
                OriginalConIP = value;
            }
        }

        private string OriginalConIP;
        public NetMQClient(string ConIP,string machineName,Progress<MsgObject> receiveHanlder)
        {
            SendMessageQueue = new Queue<MsgObject>();
            Poller = new NetMQPoller();
            ClientSocket = new DealerSocket($"tcp://{ConIP}:5556");
            try
            {
                ClientSocket.Options.Identity = Encoding.Default.GetBytes(machineName);
            }
            catch (Exception ex)
            {
                ClientSocket.Options.Identity = Encoding.Default.GetBytes("001");
                Log.WriteLog(ex.ToString());
            }
            ClientSocket.ReceiveReady += ServerSocket_ReceiveReady;
            ClientSocket.SendReady += ServerSocket_SendReady;
            Poller.Add(ClientSocket);

            ReceiveHanlder = receiveHanlder;
            OriginalConIP1 = ConIP;
        }

        private void ServerSocket_SendReady(object sender, NetMQSocketEventArgs e)
        {
            if (SendMessageQueue.Count > 0)
            {
                var sendMsg = SendMessageQueue.Dequeue();
                try
                {
                    if (sendMsg.Content == null)
                    {
                        return;
                    }
                    var msgFrame = ConvertToNetMessage(sendMsg);
                    ClientSocket.SendMultipartMessage(msgFrame);
                    //Log.WriteLog(DateTime.Now + "msg.content = "+msgFrame+"IP:"+OriginalConIP);
                }
                catch(Exception ex)
                {
                    Log.WriteExLog(ex.ToString());
                }

            }

            //发送数据间隔
            Thread.Sleep(10);
        }

        private NetMQMessage ConvertToNetMessage(MsgObject sendMsg)
        {
            var msgFrame = new NetMQMessage();
         //   msgFrame.Append(sendMsg.MachineName);
            msgFrame.AppendEmptyFrame();
            msgFrame.Append(sendMsg.Content,Encoding.UTF8);
            return msgFrame;
        }

        private MsgObject ConvertToMsgObj(NetMQMessage sendMsg)
        {
            if (sendMsg.FrameCount == 2)
            {
                var newMsg = new MsgObject();
                var machineName = sendMsg[0].ConvertToString();
                //var content = sendMsg[1].ConvertToString();
                var content = Encoding.UTF8.GetString(sendMsg[1].Buffer);
                newMsg.MachineName = machineName;
                newMsg.Content = content;

                return newMsg;
            }
            return null;

        }

        private void ServerSocket_ReceiveReady(object sender, NetMQSocketEventArgs e)
        {
            var msg = ConvertToMsgObj(ClientSocket.ReceiveMultipartMessage());

            //接收到消息,让handler处理
            if (msg != null)
            {
                ReceiveHanlder?.Report(msg);
            }

        }

        public void Start()
        {
            IsRunning = true;
            Poller.RunAsync();
        }



        public void Close()
        {
            try
            {
                ClientSocket.Disconnect($"tcp://{OriginalConIP1}:5556");
            }
            catch (Exception ex)
            {
                Log.WriteExLog(ex.ToString());
            }
        }

        public void Stop()
        {
            IsRunning = false;
            Poller.StopAsync();
        }

        public void ChangeIP(string ConIP, string machineName)
        {


            ClientSocket.Disconnect($"tcp://{OriginalConIP1}:5556");
            ClientSocket.Connect($"tcp://{ConIP}:5556");
            OriginalConIP1 = ConIP;


        }

        public void AddClientSocket(string ConIP, string machineName)
        {
            DealerSocket temp = new DealerSocket($"tcp://{ConIP}:5556");
            temp.Options.Identity = Encoding.Default.GetBytes(machineName);
            temp.ReceiveReady += ServerSocket_ReceiveReady;
            temp.SendReady += ServerSocket_SendReady;
            Poller.Add(temp);
        }
    }

这边代码基本和服务端的代码相差不大,添加一个需要的消息处理类就可以对传递过来的消息类进行解密,然后接可以做一些自己想做的事情了。服务器这边可以生成一个DLL然后客户端这边就可以直接调用RSA类和Msg类了。

后记

上篇博客本来说是要写一篇关于长连接的博客的,但是后来由于某人不同意我将之前的一些http请求改换为长连接的模式,所以这个地方就暂时搁置下来了。之后做了使用NetMQ来进行消息传递的功能。不得不NetMQ还是非常好用的,相比起微软提供的WCF方便了不少,不用写一些奇奇怪怪的配置文件。当然,在现在的项目中,我个人消息队列肯定是必不可少的一环,之前在使用WCF的时候,我没有使用队列模式所以经常会出现消息丢失的情况。
总的来说,这次的功能实现让我对消息队列了解更近一步,感觉自己写代码的方式比一年前稍微规范了一些。

你可能感兴趣的:(C#使用NETMQ进行通信并且使用RSA进行消息加密)