进程与进程间通信

时间:2020-12-19 19:02:23

多线程开发扫盲系列第一编:进程与进程间通信

 

1. 操作系统的进程与线程管理   

2. 进程的启动和终止   

3. 进程通信   

 3.1 通过剪贴版进程交换信息   

 3.2 FileSystemWatch实现进程同步   

 3.3 使用内存映射文件实现进程通信   

 3.4 进程间的通知机制   

 

1.进程与线程管理

进程(process)是一个具有独立功能的程序在一个数据集合上的一次动态执行过程。这个定义太理论化了,用一句通俗的话取代它:进程可以简单理解为一个正在运行的程序。
程序与进程的区别可以用图形像地表达出来。

进程与进程间通信


    Window设计了两种代码运行环境,用户模式(User Mode)和核心模式(kernel Mode),普通的应用程序运行于用户模式中,而操作系统的关键代码(比如负责分配与回收内存、创建和销毁进程等功能的代码)运行于核心模式下。 在windows中,”系统调用”主要指win32API中的特定函数,所以,windows应用程序通过调用win32API函数来实现从”用户模式”到”核心模式”的转换
   

    句柄与系统核心对像
    位于操作系统内核中,仅允许运行于”核心模式”下的代码访问的数据被称为”核心对像”,操作系统在运行时,会在系统核心不断地创建和销毁”核心对像”,为了便于跟踪和访问这些对像,操作系统为这些对像分配了标识,这是一个32位的整数,被称为”句柄”。许多win32 API函数通过句柄来定位所要访问的系统核心对像。在.NET托管环境中,.NET应用程序对”普通对像”和”核心对像”不加区分,使用New关键字就可以创建任何一种类型的对像,而对像的销毁工作邮CLR负责。
   

    Windows操作系统使用线程作为CPU调度的基本单位,一个进程可以划分多个线程,也可以只有一个线程。它拥有一个线程标识(ThreadID),一组CPU寄存器,两个堆栈和一个专有的线程局部存储区(Thread Local Storage,TLS)。属于同一个进程的线程共享进程所拥有的资源。
进程是系统分配各种资源(比如内存)的单位,而线程则是操作系统分配CPU(即处理机调度)的基本单位。

 

2.进程的启动与终止

    .NET应用程序控制进程的核心是Process类,Process类继承自Component类,通常又称为Process组件。Process组件代表一个托管进程,底层封装的是操作系统的本地进程。另一个重要的类是ProcessStartInfo类,这个类封装了进程启动时的各种控制参数。

如下继承结构图

进程与进程间通信
使用Process.Start方法启动进程
Process.Start(“IExplore.exe”)
Process.Start(“IExplore.exe”,”www.baidu.com”)
有时候我们希望向进程传送一些控制信息,比如此进程打开一个网页时最小化,可以这么来做
ProcessStartInfo info = new ProcessStartInfo("IExplore.exe");
info.WindowStyle=ProcessWindowStyle.Minimized;  //自动最小化
info.Arguments="www.sina.cn";  //自动访问新浪网
Process.Start(info);  //启动进程   


通过调用CloseMainWindow方法发出的结束进程运行的请求不会强制应用程序立即退出,它相当于用户直接点击主窗口上的关闭按钮。应用程序可以在退出前请求用户确认,也可以拒绝退出。

Kill方法强制关闭一个进程,与CloseMainWindow方法不同,Kill方法实际上是请求操作系统直接结束进程,它不给要关闭的进程保存数据的机会,因此除非要保存的进程没有任何数据需保存,否则不要采用Kill方法直接结束某个进程。

 

3.进程通信

3.1 通过剪贴版进程交换信息

所谓进程通信,是指正在运行的进程之间相互交换信息。
每个进程都拥有自己的地址空间,其他进程不能直接访问,因此通常需要通过一个第三方媒介间接地在进程之间交换信息。
剪贴板是最常用的进程间交换信息的媒介之一。

剪贴版相当于一个"物品临时寄存器",一次只能保存一个"物品",而且这个"物品"是大家共享的,比如使用work复制了一段文本在剪贴板上,现在又使用"画图"程序将一幅图放在剪贴板上,则图片数据将替换掉文本数据。再比如使用画图程序将一幅画放在剪贴板上,则work,写字板,photoshop等其它应用程序都可以从剪贴板中获取这些数据。

剪贴板中可以保存多种类型数据,.NET定义了一个DataFormats类,定义了剪贴板中可以存放的数据类型,如下图

字段名称

说明

Bitmap

Windows位图格式

Dib

Windows与设备无关的位图(DIB)格式

EnhancedMetafile

Windows增强型图元文件格式

Html

由Html数据组成的文本

MetafilePict

Windows图元文件格式,Windows客体不直接使用此格式

OemText

标准Windows原始设备制造商(OEM)文本格式

Palette

Windows调色板格式

Rtf

由Rich Text Format(RTF)数据组成的文本

Serializable

可序列化的对像

StringFormat

Windows窗体字符串类格式,Windows窗体使用此格式存储字符串对像

Text

标准ANSI文本格式

UnicodeText

标准Windows Unicode文本格式

 如下示例,复制几个文件或图片,点击剪贴板上有什么按钮,看结果

实现代码:

        private void btnShowBoard_Click(object sender, EventArgs e)
        {
            IDataObject data = Clipboard.GetDataObject();  //获取剪贴板上的数据
            richTextBox1.Text = "";
            if (data == null)
                return;
            string[] str = data.GetFormats();   //获取剪贴板上数据类型
            foreach (string s in str)
            {
                richTextBox1.AppendText(s+"\n");
            }
        }

 进程与进程间通信

 

如下示例,即基于剪贴板交换数据,打开两个此程序,程序A点装入图片-复制到剪贴板,程序B点从剪贴板粘贴。即可看到数据在两个进程间交换

核心代码:

    //装入图片
    private void btnLoadImage_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                bmp = new Bitmap(openFileDialog1.FileName);
            }
        }

     //保存到剪贴板       
      private void btnCopyToBoard_Click(object sender, EventArgs e)
        {
            MyPic mypic = new MyPic { image = bmp, info = info };
            IDataObject dataobj = new DataObject(mypic);
            dataobj.SetData(DataFormats.UnicodeText, info);
            dataobj.SetData(DataFormats.Bitmap, bmp);
            Clipboard.SetDataObject(dataobj, true);   
        }

    //从剪贴板粘贴
        private void btnPasteFromBoard_Click(object sender, EventArgs e)
        {
            if (Clipboard.ContainsData("UseClipboard.MyPic") == false)
                return;

            IDataObject clipobj = Clipboard.GetDataObject();
            //将数据转换为需要的类型
            MyPic mypicobj = clipobj.GetData("UseClipboard.MyPic") as MyPic;
            //从数据对象中分解出需要的数据
            info = mypicobj.info;
            pictureBox1.Image = mypicobj.image;
        }

进程与进程间通信

剪贴板用起来非常方便,但它有个缺点,它没法通知其他进程数据已放到剪贴板上了,除非在等待接收数据的进程中设计一个辅助线程定时监控剪贴板,在数据来时主动从剪贴板中获取数据,但这并不是最佳方式。

3.2 FileSystemWatch实现进程同步

FileSystemWatcher是.Net Framework所提供的一个组件,它可以监控特定的文件夹或文件,比如在此文件夹中某文件被删除或内容被改变时引发对应的事件。如下所示两个程序一个用于读,一个用于写,当在frmwrite修改了文件点保存时,frmreader会同步显示文件更新后的内容

进程与进程间通信

文件写入的方法
 using (StreamWriter sw = new StreamWriter(new FileStream(FileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read), Encoding.Default))
   {
       sw.Write(richTextBox1.Text);
   }

文件监控的代码

namespace FileReader
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        string FileName;

       //载入文件
        public void LoadFile()
        {
            try
            {
                using (StreamReader sr = new StreamReader(new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.Default))
                {
                    richTextBox1.Text = sr.ReadToEnd();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        public void SetupFileSystemWatch()
        {
            fileSystemWatcher1.Filter = Path.GetFileName(FileName);   //监控的文件
            fileSystemWatcher1.Path = Path.GetDirectoryName(FileName);  //监控的文件路径
            fileSystemWatcher1.NotifyFilter = NotifyFilters.Size;   //当文件大小改变时,触发事件
        }

        private void btnCheck_Click(object sender, EventArgs e)
        {
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                FileName = openFileDialog1.FileName;
                LoadFile();
                SetupFileSystemWatch();
            }
        }

        private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
        {
            LoadFile();
        }

    }
}

FileSystemWatcher组件的常用事件

Changed

当更改指定文件夹中的文件和目录时发生

Created

当在指定文件夹中创建文件和目录时发生

Deleted

删除指定文件夹或目录时发生

Renamed

重命名指定文件夹中 或目录时发生

3.3 使用内存映射文件实现进程通信

所谓内存映射就是在内存中开辟出一块存放数据的专用区域,这区域往往与硬盘上特定的文件相对应。进程将这块内存区域映射到自己的地址空间中,访问它就像是访问普通的内存一样,.NET中使用MemoryappedFile对像表示一个内存映射文件,通过它的CreateFromFile方法根据磁盘现有文件创建内存映射文件,MemoryMappedFile对像创建之后,不能直接对其读写,还须通过一个MemoryMappedViewAccessor对像来访问。

如下示例:输入两个数,保存到内存取映射文件,然后再打开一个程序点击提取即可把内存映射中的数据提取出来

进程与进程间通信

代码如下:

    private int FileSize = 1024 * 1024;  //设为映射文件大小
        private MemoryMappedFile file = null;  
        private MemoryMappedViewAccessor accor = null;
        private void Init()
        {
            file = MemoryMappedFile.CreateOrOpen("UseMMFBetweenProcess", FileSize);  //创建内存取映射文件
            accor = file.CreateViewAccessor();  //创建映射文件视图
            toolStripStatusLabel1.Text = "内存文件映射或创建成功";
        }
        private MyStructure data;
        private void btnSave_Click(object sender, EventArgs e)
        {
            data.IntValue = Convert.ToInt32(txtInt.Text);
            data.FloatValue =float.Parse(txtFloat.Text);
            accor.Write<MyStructure>(0,ref data);   //将结构对像保存到映射文件中
            toolStripStatusLabel1.Text = "数据已保存到内文件中";

        }

        private void btnGet_Click(object sender, EventArgs e)
        {
            accor.Read<MyStructure>(0, out data);  //从映射文件中取出结构对像
            txtInt.Text = data.IntValue.ToString(); ;
            txtFloat.Text = data.FloatValue.ToString();
            toolStripStatusLabel1.Text = "成功从内存中提取了数据";
        }

3.4 进程间的通知机制

 进程之间的数据传送有多种方式,但大多数进程通信手段都缺乏一种通知机制,本节介绍一种比较简便的.NET线程同步对像Mutext和EventWaitHandle实现进程通知机制的方法

示例如下:点击发送端程序中的的click me,接收端窗体会记录点击次数

进程与进程间通信

 

 //发送端代码

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private EventWaitHandle handle;
        private const string ProcessSynchronizeEventName = "ProcessSynchronizeEvent";
        private void button1_Click(object sender, EventArgs e)
        {
            handle.Set();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                handle = EventWaitHandle.OpenExisting(ProcessSynchronizeEventName);
                if (handle != null)
                {
                    MessageBox.Show("只能运行一个实例");
                    handle = null;
                    Close();
                }
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                handle = new EventWaitHandle(false, EventResetMode.ManualReset, ProcessSynchronizeEventName);
                labInfo.Text = "eventhandle已创建";
            }

        }
    }

 

//接收端代码

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private EventWaitHandle hEvent = null;
        private const string MyProcess = "ProcessSynchronizeEventResponsor";
        private const string ProcessSynchronizeEventName = "ProcessSynchronizeEvent";

        private void GetEventHandle()
        {
            try
            {
                hEvent = EventWaitHandle.OpenExisting(ProcessSynchronizeEventName);
                if (hEvent != null)
                {
                    Thread th = new Thread(WaitForHandle);
                    th.IsBackground = true;
                    th.Start();
                  
                }
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                MessageBox.Show("请先运行程序ProcessSynchronizeEventSource的一个实例");
                Close();
            }
        }
        private int count;
        void WaitForHandle()
        {
            while (true)
            {
                hEvent.WaitOne();
                count++;
                string info="服务端进程点击了" + count+"次";
                Action<string> showinfo = delegate(string a)
                {
                    labInfo.Text = a;
                };
                this.Invoke(showinfo, info);
                hEvent.Reset();
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                Mutex m = Mutex.OpenExisting(MyProcess);
                if (m != null)
                {
                    MessageBox.Show("已有一个实例在运行");
                    Close();
                }
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                Mutex m = new Mutex(false, MyProcess);
            }
            GetEventHandle();
        }
    }