智能家居通用管理平台 - 进程间通信

Posted ionfox

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能家居通用管理平台 - 进程间通信相关的知识,希望对你有一定的参考价值。



    如果设备系统(如虚拟的音乐系统),运行在服务器电脑上,那么使用IPC通信是较好的办法,即使没有网络, SHP仍然可以与设备系统交互。如何设计进程间通信,需要考虑几个方面的事宜。
    一是通信的频率问题。有些数据很少需要交互。而设备的状态数据的传送,有突发性,某些时段,发送频率比较高,因此可能需要考虑使用多种通信方式。
    二是数据的丢失问题。当通信繁忙时,如果频繁触发报警条件,SHS需要时间去处理任务(目前是使用线程去处理)。要确保设备系统传来的信息不丢失,也就是说要有存储信息的功能。
    鉴于此,我们设计使用共享内存来进行进程间基本信息的交互,因为这些信息更新的频率很低,如进程退出、窗体隐藏等。设计使用消息队列来维护设备系统与SHM的信息交换。尽管微软的消息队列(MSMQ)具有平台相关性,在其他平台可以自己设计类似功能。消息队列的优点就是能存储信息,检索信息。我们使用两个消息对列来连接设备系统和SHM,见下图。SHM(设备驱动程序)发给设备系统的消息称为“通知”(Notify),设备系统发给驱动程序的消息称为“消息”(Message)。

   之所以使用两个队列,是不想花CPU时间来检索判断信息内容。设备系统使用线程只管从Notify队列取出“指令数据”来处理,而SHM只管从Message队列获取设备信息的状态数据即可。当然,SHM与SHS之间也维护着一对(不是一个)MSMQ。

    由于大量使用MSMQ,程序使用线程的方式操作队列。否则UI界面会有“阻塞”显现,或者异步方式操作队列。如果设备系统使用TCP/IP与监控程序SHM通信,那么在驱动程序和设备系统程序中,都不需要操作队列即可。
    为方便查看IPC的内容,SHM提供了查看共享内存和消息队列内容的UI,见下图。

    进程间通信对象,封装在ShareMemory对象中,见下面的完整代码。共享内存使用互斥来同步数据访问。消息队列的访问,由windows的队列服务维护,无需开发者操心。
namespace HomeLibrary.ShareMemory

    public class ShareMemory
   
        #region 共享内存操作函数的外部声明
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr CreateFileMapping(int hFile, IntPtr lpAttributes, uint flProtect,
                               uint dwMaxSizeHi, uint dwMaxSizeLow, string lpName);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr OpenFileMapping(int dwDesiredAccess,
                                [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, string lpName);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr MapViewOfFile(IntPtr hFileMapping, uint dwDesiredAccess,
                              uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool UnmapViewOfFile(IntPtr pvBaseAddress);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport("kernel32", EntryPoint = "GetLastError")]
        public static extern int GetLastError();
        #endregion

        #region 相关常数声明
        const int ERROR_ALREADY_EXISTS = 183;
        const int FILE_MAP_COPY = 0x0001;
        const int FILE_MAP_WRITE = 0x0002;
        const int FILE_MAP_READ = 0x0004;
        const int FILE_MAP_ALL_ACCESS = 0x0002 | 0x0004;
        const int PAGE_READONLY = 0x02;
        const int PAGE_READWRITE = 0x04;
        const int PAGE_WRITECOPY = 0x08;
        const int PAGE_EXECUTE = 0x10;
        const int PAGE_EXECUTE_READ = 0x20;
        const int PAGE_EXECUTE_READWRITE = 0x40;
        const int SEC_COMMIT = 0x8000000;
        const int SEC_IMAGE = 0x1000000;
        const int SEC_NOCACHE = 0x10000000;
        const int SEC_RESERVE = 0x4000000;
        const int INVALID_HANDLE_VALUE = -1;
        #endregion

        #region 类属性声明
        private IntPtr m_hSharedMemoryFile = IntPtr.Zero;       
        private long m_MemSize = 0;

        private Mutex mutex;   //用于进程间同步
        public IntPtr m_pwData = IntPtr.Zero;
        public bool m_ok = false;          //共享内存是否可用

        //public List<stringJson> Messages;  //其他平台自己定义.....
        //public List<stringJson> Notifies;

        public MessageQueue MQNotify;   //发送通知的队列
        public MessageQueue MQMessage;  //接收消息的队列

        #endregion
        public int MaxCount;
        public int MAXITEMS
       
            get return MaxCount;
            set
           
                if (value >= 10 && value <= 1000) //最多1000保存1000条消息
                    MaxCount = value;
           
         
        public ShareMemory(Mutex mutex)
       
            this.mutex = mutex;
            //Messages = new List<stringJson>();
            //Notifies = new List<stringJson>();
            //MAXITEMS = 100;
       
        ~ShareMemory()
       
            Close();
       


        public string mqnotifyfn = "";
        public string mqmessagefn = "";
        public int Init(string strName, long longSize)
       
            if (longSize <= 0 || longSize > 0x00800000) longSize = 0x00800000;
            m_MemSize = longSize;
            mqnotifyfn = ".\\\\Private$\\\\" + strName + "Notify";
            if (!MessageQueue.Exists(mqnotifyfn))
           
                MQNotify = MessageQueue.Create(mqnotifyfn);
                MQNotify.SetPermissions("Administrators", MessageQueueAccessRights.FullControl);
           
            else
              //if MessageQueue.Exists(mqnotifyfn))
                MQNotify = new MessageQueue(mqnotifyfn);
                //MQNotify.Purge();
           
            MQNotify.Label = mqnotifyfn;
            MQNotify.Formatter = new System.Messaging.BinaryMessageFormatter();
            mqmessagefn = ".\\\\Private$\\\\" + strName + "Message";
            if (!MessageQueue.Exists(mqmessagefn))
           
                MQMessage = MessageQueue.Create(mqmessagefn);
                MQMessage.SetPermissions("Administrators", MessageQueueAccessRights.FullControl);
           
            else
           
                MQMessage = new MessageQueue(mqmessagefn);
                //MQMessage.Purge();               
           
            MQMessage.Formatter = new System.Messaging.BinaryMessageFormatter();
            MQMessage.Label = mqmessagefn;
            if (strName.Length > 0)   //创建共享内存(INVALID_HANDLE_VALUE) 
             
                m_hSharedMemoryFile = OpenFileMapping(FILE_MAP_WRITE | FILE_MAP_READ, false, strName);
                if (m_hSharedMemoryFile == IntPtr.Zero) //没有指定的共享内存,创建它
               
                    m_hSharedMemoryFile = CreateFileMapping(INVALID_HANDLE_VALUE, IntPtr.Zero,
                                           (uint)PAGE_READWRITE, 0, (uint)longSize, strName);
               
                 if (m_hSharedMemoryFile == IntPtr.Zero) return 1; //无法使用共享内存

                //通过内存映射,获得一个指针:指向该内存块
                m_pwData = MapViewOfFile(m_hSharedMemoryFile, FILE_MAP_WRITE, 0, 0, (uint)longSize);
                if (m_pwData == IntPtr.Zero)  //创建内存映射失败 
               
                    m_ok = false;
                    CloseHandle(m_hSharedMemoryFile); //关闭共享内存句柄,释放资源
                    return 2; //映射失败返回2
               
                m_ok = true;
                return 0; //创建成功 
           
            else  //参数错误返回  
                return 4;
       

        /// ﹤summary﹥ 
        /// 关闭共享内存 
        /// ﹤/summary﹥ 
        public void Close()
       
            if (m_ok)
           
                UnmapViewOfFile(m_pwData);
                CloseHandle(m_hSharedMemoryFile);
           
            //if (Messages != null) Messages.Clear();
            //if (Notifies != null) Notifies.Clear();
       

        public void DeleteNotifyMQ()
       
            if (MessageQueue.Exists(mqnotifyfn))
           
                MessageQueue.Delete(mqnotifyfn);
           
       
        public void DeleteMessageMQ()
       
            if (MessageQueue.Exists(mqmessagefn))
           
                MessageQueue.Delete(mqmessagefn);
           
       

        /// ﹤summary﹥ 
        /// 写入数据到共享内存
        /// ﹤/summary﹥ 
        public void ObjectToShareMemory(object struncture)
       
            if (m_ok)
           
                mutex.WaitOne();                
                Marshal.StructureToPtr(struncture, m_pwData, true);
                mutex.ReleaseMutex();
           
       
        /// ﹤summary﹥ 
        /// 共享内存数据转移到对象,方便处理,一般返回的对象强制转换为应用程序的某个类型
        /// ﹤/summary﹥ 
        public object ShareMemoryToObject(object struncture)
       
            if (m_ok)
           
                mutex.WaitOne();
                object ob = Marshal.PtrToStructure(m_pwData, struncture.GetType());
                mutex.ReleaseMutex();
                return ob;
           
            else return null;
       

        public bool AddMessage(byte[] msg)  //增加一个消息数据结构
       
            if (!MessageQueue.Exists(mqmessagefn)) return false;
            MQMessage = new MessageQueue(mqmessagefn);
            Message Msg = new Message();
            MQMessage.Formatter = new System.Messaging.BinaryMessageFormatter();
            MQMessage.Formatter.Write(Msg, msg);
            MQMessage.Send(Msg);
            return true;
       

        public bool AddMessage(stringJson msg) //增加一个消息数据结构
       
            if (msg == null) return false;
            return AddMessage(msg.GetBytes());
       

        public stringJson GetMessage(int ms=20) //获取一个消息数据结构
       
            if (!MessageQueue.Exists(mqmessagefn)) return null;
            MQMessage = new MessageQueue(mqmessagefn);
            MQMessage.Formatter = new System.Messaging.BinaryMessageFormatter();
            try
           
                System.Messaging.Message Msg = MQMessage.Receive(new TimeSpan(0, 0, 0, 0, ms));
                if (Msg == null) return null;
                return (stringJson.ConvertBytesTostringJson((byte[])Msg.Body, SmartHomeChannel.SHFLAG));
           
            catch
            return null;
       


        public bool AddNotify(byte[] msg)  //增加一个消息数据结构
       
            if (!MessageQueue.Exists(mqnotifyfn)) return false;
            MQNotify = new MessageQueue(mqnotifyfn);  //重新连接,有可能断开了!!!
            Message Msg = new Message();
            MQNotify.Formatter = new System.Messaging.BinaryMessageFormatter();
            MQNotify.Formatter.Write(Msg, msg);
            MQNotify.Send(Msg);
            return true;
       

        public bool AddNotify(stringJson msg) //增加一个消息数据结构
       
            if (msg == null) return false;
            return AddNotify(msg.GetBytes());
       

        public stringJson GetNotify(int ms=20) //获取一个消息数据结构
       
            if (!MessageQueue.Exists(mqnotifyfn)) return null;
            MQNotify = new MessageQueue(mqnotifyfn);
            MQNotify.Formatter = new System.Messaging.BinaryMessageFormatter();
            try
           
                 Message Msg = MQNotify.Receive(new TimeSpan(0, 0, 0, 0, ms));
                if (Msg == null) return null;
                return (stringJson.ConvertBytesTostringJson((byte[])Msg.Body, SmartHomeChannel.SHFLAG));
           
            catch
            return null;
       

        public static bool CreateMutex(string mutexName, ref Mutex mutex)  //创建互斥
       
            bool doesNotExist = false;
            bool unauthorized = false;
            bool mutexWasCreated = false;
            try  // Attempt to open the named mutex.
           
                mutex = Mutex.OpenExisting(mutexName);
           
            catch (WaitHandleCannotBeOpenedException)
           
                //Console.WriteLine("Mutex does not exist.");
                doesNotExist = true;
           
            catch (UnauthorizedAccessException)
           
                //Console.WriteLine("Unauthorized access: 0", ex.Message);
                unauthorized = true;
           

            if (doesNotExist)
           
                string user = Environment.UserDomainName + "\\\\" + Environment.UserName;
                MutexSecurity mSec = new MutexSecurity();
                MutexAccessRule rule = new MutexAccessRule(user,
                    MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Deny);
                mSec.AddAccessRule(rule);
                rule = new MutexAccessRule(user,
                    MutexRights.ReadPermissions | MutexRights.ChangePermissions, AccessControlType.Allow);
                mSec.AddAccessRule(rule);
                mutex = new Mutex(true, mutexName, out mutexWasCreated, mSec);
                if (!mutexWasCreated)
               
                    //Console.WriteLine("Unable to Created the mutex.");
                    return false;
               
                mutex.ReleaseMutex();  //新创建,释放?
           
            else if (unauthorized)
           
                try
               
                    mutex = Mutex.OpenExisting(mutexName,
                        MutexRights.ReadPermissions | MutexRights.ChangePermissions);
                    MutexSecurity mSec = mutex.GetAccessControl();
                    string user = Environment.UserDomainName + "\\\\" + Environment.UserName;
                    MutexAccessRule rule = new MutexAccessRule(user,
                         MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Deny);
                    mSec.RemoveAccessRule(rule);
                    rule = new MutexAccessRule(user,
                        MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow);
                    mSec.AddAccessRule(rule);
                    mutex.SetAccessControl(mSec);
                    //Console.WriteLine("Updated mutex security.");
                    mutex = Mutex.OpenExisting(mutexName);

               
                catch (UnauthorizedAccessException)
               
                    //Console.WriteLine("Unable to change permissions: 0", ex.Message);
                    return false;
               
           
            return true;
           
   

    共享内存中存储的数据结构,定义在SmartHomeChannel对象中。主要定义了进程的一些基本信息。参见下面的完整代码。
namespace HomeLibrary.Type

    [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public class SmartHomeChannel      //智能家居程序对象
   
        public static int SHFLAG = 0x5A5A5A5A;  //智能家居系统统一标识,便于各个厂家通信公用
        public static string sharememoryfile = "SmartHomeShareFile";
        public static string mutexname = "SmartHomeMutex";

        public int appid;              //某品牌智能家居唯一识别号

        public bool canused;           //系统能否被使用
        public bool autostart;         //自动运行 

        public bool loaded;            //监控程序SHM是否已经加载,可用于防止启动多个进程
        public bool Deviceloaded;      //协调器或虚拟设备程序是否已经加载,可用于防止启动多个进程
        public bool visible;           //启动时是否显示窗口

        public bool winvisible;        //程序窗口目前是否显示

        public int PID;                //智能家居进程号,可以启动多个进程
        public int DevicePID;          //设备程序(协调器,虚拟设备程序)进程号

        public int DeviceId;           //设备号
        public int Port;               //TCP/IP通信端口号

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string password;       //通行密码,所有指令必须带有密码
       
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string user;           //TCP通信登录SHM的用户名带有密码
       
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string name;            //系统名称:32Unicode字符
       
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string starttime;       //系统启动时间

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public string assembly;        //实现了ISmartHome接口的程序集文件名

        public override string ToString()
       
            string result = "名称=" + name + ",识别号=" + appid.ToString() + ",\\r\\n";
            result += "程序集=" + assembly + ",启动时间=" + starttime + ",\\r\\n";
            result += "可用=" + canused.ToString() + ",自启动=" + autostart.ToString() + ",显示=" + visible.ToString() + ",\\r\\n";
            result += "SHM PID=" +  PID.ToString() + ",Device PID=" + DevicePID.ToString() +",\\r\\n";
            result += "SHM加载否=" + loaded.ToString() + ",Device加载否=" +Deviceloaded.ToString() + "\\r\\n";
            return result;
       
        public stringJson TostringJson(Int32 flag)
       
            stringJson json = new stringJson(flag);
            json.AddNameValume("家居名称", name);
            json.AddNameValume("家居PID", appid.ToString());
            json.AddNameValume("程序集", assembly);
            json.AddNameValume("启动时间", starttime);
            json.AddNameValume("SHM PID", PID.ToString());
            json.AddNameValume("Device PID", DevicePID.ToString());
            json.AddNameValume("SHM 加载否", loaded.ToString());
            json.AddNameValume("Device加载否", Deviceloaded.ToString());
            return json;
       
   

   有了这些基本的对象,似乎可以编写SHS和SHM程序了。不急,需要先写一个设备驱动程序,才好在SHS中使用。下节介绍如何开发符合SHP的设备驱动程序,并用例子来描述开发过程和结果。

以上是关于智能家居通用管理平台 - 进程间通信的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出Binder进程间通信机制

智能家居通用管理平台 - 通信协议

智能家居通用管理平台-通信方式和数据结构

智能家居通用管理平台 - 即插即用机制的设计

谈谈Mac进程间通信--XPC

RK3399平台开发系列讲解(进程间通信)14.10如何查看进程调度的信息