`

【操作系统】进程间通信(C#)

 
阅读更多

08年9月入学,12年7月毕业,结束了我在软件学院愉快丰富的大学生活。此系列是对四年专业课程学习的回顾,索引参见:http://blog.csdn.net/xiaowei_cqu/article/details/7747205

进程间通信

命名管道

进程间通信的一种方式,Pipes:管道,分为无名管道:在父子进程间交换数据;有名管道:可在不同主机间交换数据,分为服务器方和客户方,在Win9X下只支持有名管道客户。

命名管道的命名

命名管道是一个有名字的,单向或双向的通信管道。管道的名称有两部分组成:计算机名和管道名,例如\\[host_name]\pipe\[pipe_name]\(括号内为参数)。对于同一主机来讲允许有多个同一命名管道的实例并且可以由不同的进程打开,但是不同的管道都有属于自己的管道缓冲区而且有自己的通讯环境互不影响,并且命名管道可以支持多个客户端连接一个服务器端。命名管道客户端不但可以与本机上的服务器通讯也可以同其他主机上的服务器通讯。

命名管道的连接

在服务器端第一次创建命名管道后等待连接,当客户端连接成功后服务器端的命名管道就用作通讯用途。如果需要再次等待连接,服务器端就需要再次打开命名管道(创建一个命名管道的实例)并等待连接。

对于客户端每次打开命名管道后建立与服务器间的连接,然后就可以利用命名管道进行通信,如果需要建立第二个连接则需要再次打开管道和再次建立连接。

创建命名管道时需要指定一个主机名和管道名,对于客户端来说可以是如下格式:\\[host_name]\pipe\[pipe_name]\也可以是\\.\pipe\pipe_name\其中.表示本机。而服务器端只能够在指定本机作为主机名,即只能使用下面的格式:\\.\pipe_name\。此外需要记住,在同一主机上管道名称是唯一的,一个命名管道一旦被创建就不允许相同名称的管道再被创建。

主要函数

管道服务器首次调用CreateNamedPipe()函数时,使用nMaxInstance参数指定了能同时存在的管道实例的最大数目。服务器可以重复调用CreateNamedPipe()函数去创建管道新的实例,直至达到设定的最大实例数。

服务器方通过该函数创建命名管道和打开已经存在的命名管道,其中lpName为管道名称,dwOpenMode为创建方式,可以是下面值的组合:

PIPE_ACCESS_INBOUND:管道只能用作接收数据。

PIPE_ACCESS_OUTBOUND:管道只能用作发送数据。

PIPE_ACCESS_DUPLEX:管道既可以发送也可以接收数据。(上面这三个值只能够取其中一个)

FILE_FLAG_WRITE_THROUGH:管道用于同步发送和接收数据,只有在数据被发送到目标地址时发送函数才会返回,如果不设置这个参数那么在系统内部对于命名管道的处理上可

能会因为减少网络附和而在数据积累到一定量时才发送,并且对于发送函数的调用会马上返回。

管道的连接管理,客户方在调用CreateFile后立即就能够建立服务器的连接,而服务器方一旦管道打开或创建后可以用

BOOL ConnectNamedPipe(
HANDLE hNamedPipe, // handle to named pipe
LPOVERLAPPED lpOverlapped // overlapped structure
);

来等待客户端的连接建立。如果希望在服务器方检测是否有连接到达,可以调用

BOOL WaitNamedPipe(
LPCTSTR lpNamedPipeName, // pipe name
DWORD nTimeOut // time-out interval
);

这里的lpNamePipeName直接使用创建管道时的名称,如果在服务器方希望关闭连接则调用

BOOL DisconnectNamedPipe(
HANDLE hNamedPipe // handle to named pipe
);
一旦连接被关闭,服务器方可以再次调用ConnectNamedPipe来建立连接。如果要关闭管道则直接调用CloseHandle。请注意这里提到的关闭管道和关闭连接是不同的意思,在同一个管道上可以依次反复建立连接,而且可以减小系统的负荷。而且如果指定了管道最大数量限制那么在打开的管道达到最大限制后如果不关闭旧管道就无法打开新管道。 对于客户方则无法关闭连接,而只能直接调用CloseHandle关闭管道。

数据的发送,不论是服务器还是客户方都可以通过ReadFile和WriteFile进行管道读写来达到通讯的目的。


【实验说明】

第一次实验的基础上,进行进程通信实验,用管道方式在两个进程间进行通信,要求能传递一个数据结构,结构如下:

struct
{
     int [9][9];
     byte[16];
     string;
}

不管用什么方式进行编码和解码,需要两个进程能够相互传递


【实验步骤】

1.查阅有关进程间通信的资料

用管道实现进程间通信,需要编写服务器及客户端。

服务器端设计:


客户端设计:

参考网址:http://www.cnblogs.com/xinhaijulan/archive/2010/07/28/1786535.html

http://www.cnblogs.com/wangyonghui/archive/2010/01/24/1655390.html

2.定义要传递的数据结构

public struct PassStruct
        {
            public int[,] arrayInt;   
            public byte[] arrayByte;
            public string ss;
            public PassStruct(int[,] i, byte[] j, string str)
            {
                arrayInt = i;
                arrayByte =j;
                ss = str;
            }
        }
3.服务器端代码
namespace MyPipe
{
    class Program
    {
        static int numThreads = 2;
        static void Main(string[] args)
        {
            Thread newThread = new Thread(ServerThread);
            newThread.Start();
            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }
        private static void ServerThread(object data)
        {
            NamedPipeServerStream pipeServer =
                new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads);
            Console.WriteLine("NamedPipeServerStream thread created.");

            //等待客户端连接
            pipeServer.WaitForConnection();

            Console.WriteLine("Client connected.");
            try
            {
                StreamReader sr = new StreamReader(pipeServer);
                StreamWriter sw = new StreamWriter(pipeServer);
                sw.AutoFlush = true;
                //客户端通过此消息进行确认
                sw.WriteLine("My Server!");

                // Obtain the filename from the connected client.
                string content = sr.ReadLine();
                Console.WriteLine("Reading {0}", content);
                pipeServer.Disconnect();
            }
            catch (IOException e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
            }
            pipeServer.Close();
        } 
    } 
}

3.客户端代码

在实验一的基础上,客户端使用两个线程,一个线程中实例一个客户端,使用管道通信向客户端发送数据(数据结构转为string类型);另一个线程中接收服务器端管道中的数据,再将string转为定义的数据结构。

//定义要传递的数据结构
       PassStruct myPass = new PassStruct(
           new int[,]{
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                    {1,2,3,4,5,6,7,8,9},
                     {1,2,3,4,5,6,7,8,9}
                }, 
                new byte[16] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7 },
                "Authror:小魏");
private void 打开aToolStripMenuItem_Click(object sender, EventArgs e)
        {
            A = new Thread(new ThreadStart(exePa));
            A.IsBackground = true; 
            A.Start();
        }

        //声明一个委托,用以解决控件绑定到特定线程抛出的InvalidOperationException异常
        delegate void setRtbHandler(string s);
        private void setRtb(string s)
        {
            tabPage2.Controls[0].Text += s;
        }

        /// <summary>
        /// 进程Pa
        /// </summary>
        private void exePa()
        {
            ////原本用作测试的
            //this.tabPage2.Controls[0].Text = "aaa";
            info += "线程A(写入线程)打开\n";
            try{
                //这里第一个参数是我的计算机名
                NamedPipeClientStream pipeClientA =
                new NamedPipeClientStream("WEI-THINKPAD", "testpipe",    
                PipeDirection.InOut, PipeOptions.None,
                TokenImpersonationLevel.Impersonation);
                StreamWriter sw = new StreamWriter(pipeClientA);
                StreamReader sr = new StreamReader(pipeClientA);
                pipeClientA.Connect();
                sw.AutoFlush = true;
                
                //确认服务器连接
                if (sr.ReadLine() == "My Server!")
                {
                    //向管道中写入数据(先转化为字符串)
                    toWrite = StructToString(myPass);
                    sw.Write(toWrite);
                }
                else
                {
                    info +="Server could not be verified.\n";
                }
                //关闭客户端
                pipeClientA.Close();
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

       /// <summary>
       /// 关闭A
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
        private void 关闭AToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (A.IsAlive)
            {
                info += "线程A(写入线程)已关闭\n";
                A.Abort();
            }
        }

        private void 打开aToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            B = new Thread(new ThreadStart(exePb));
            B.IsBackground = true;
            B.Start();
        }

        /// <summary>
        /// 线程B
        /// </summary>
        private void exePb()
        {
            info += "线程B(读出线程)打开\n";
            try
            {
                NamedPipeClientStream pipeClientB =
                new NamedPipeClientStream("WEI-THINKPAD", "testpipe",
                PipeDirection.InOut, PipeOptions.None,
                TokenImpersonationLevel.Impersonation);
                StreamWriter sw = new StreamWriter(pipeClientB);
                StreamReader sr = new StreamReader(pipeClientB);
                pipeClientB.Connect();
                sw.AutoFlush = true;
                if (sr.ReadLine() == "My Server!")
                {

                    PassStruct getPass = StringToStruct(toWrite);
                    string structToShow="";
                    
                    //将读到的数据结构以一定的格式显示到屏幕上
                    for (int i = 0; i < 9; i++)
                {
                        for (int j = 0; j < 9; j++)
                        {
                            structToShow += getPass.arrayInt[i, j].ToString() + " ";
                        }
                        structToShow += "\n";
                    }
                    for (int k = 0; k < 16; k++)
                        structToShow += getPass.arrayByte[k].ToString() + " ";
                    structToShow += "\n";
                    structToShow += getPass.ss;
                    structToShow += "\n";

                    info += structToShow;
                }
                else
                {
                    info += "Server could not be verified.\n";
                }
                pipeClientB.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

        }

        /// <summary>
        ///关系线程B
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void 关?闭À?ToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            if (B.IsAlive)
            {
                B.Abort();
                A.Abort();
                info += "线程B(读出线程)已关闭\n";
                this.tabPage2.Controls[0].Text = info;
            }
        }

4.数据结构与string相互转化的函数

//数据结构转为字符串(所有的数字变为字符串,用“,”隔开)
        public string StructToString(PassStruct ps)
        {
            string s = "";
            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 9; j++)
                    s =s+(ps.arrayInt[i, j]).ToString() + ",";
            for (int k = 0; k < 16; k++)
                s = s + (ps.arrayByte[k]).ToString() + ",";
            s = s + ps.ss;
            return s;
        }

        //将string转为定义的数据结构
        public PassStruct StringToStruct(string s)
        {
            int[,] x = new int[9, 9];
            for (int i = 0; i < 9; i++)
            {
                for (int j = 0; j < 9; j++)
                {
                    int p = s.IndexOf(',');// 通过‘,’找到分割
                    string tmp = s.Substring(0, p);// 截取‘,’之前的部分转为int
                    x[i, j] = int.Parse(tmp);
                    s = s.Remove(0, p + 1);// 通过Remove移除已转为int的部分
                }
            }
            //同样的方法得到byte部分
            byte[] y = new byte[16];
            for (int k = 0; k < 16; k++)
            {
                int p = s.IndexOf(',');
                string tmp = s.Substring(0, p);
                y[k] = byte.Parse(tmp);
                s = s.Remove(0, p + 1);
            }
            //剩下的部分为结构中字符串的部分
            PassStruct getPass = new PassStruct(x, y, s);
            return getPass;
        }


【实验结果】

服务器端输出截图:

客户端截图:



【实验中遇到的问题】

实验的大部分地方都是用try catch来处理异常,catch中通过MessageBox显示可以很快看到错误问题。实验中遇到:


网上查阅了一些资料,了解信号灯也是进程间通信的一种方式。“信号灯与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统V信号灯实际上是一个信号灯的集合,可用于多种共享资源的进程间同步。每个信号灯都有一个值,可以用来表示当前该信号灯代表的共享资源可用(available)数量,如果一个进程要申请共享资源,那么就从信号灯值中减去要申请的数目,如果当前没有足够的可用资源,进程可以睡眠等待,也可以立即返回。”(http://www.cnblogs.com/thinkingworld/articles/1861739.html

不少人遇到过这个故障(数据库中或硬盘读盘),但最终还是没有读懂,不知道自己为什么会遇到这个问题。猜测可能还是不同线程向窗口写入东西(改变空间属性)引起的问题。于是将输出的信息改为一次性输出,暂时没有再出现问题。(具体见代码中注释)

转载请注明出处:http://blog.csdn.net/xiaowei_cqu/article/details/7041212

程序源码及实验报告文档下载:http://download.csdn.net/detail/xiaowei_cqu/3880949



分享到:
评论

相关推荐

    C#进程间通信

    操作系统实验之进程间通信(管道方式)。 内涵实验源码及报告文档。 更详尽内容请参考小魏博客:http://blog.csdn.net/xiaowei_cqu/article/details/7041212

    C#与C++进程间通信

    命名管道是一种在操作系统中提供半双工或全双工通信的机制,适用于在同一台计算机上的进程间通信,也可用于网络通信。在Windows系统中,命名管道被广泛用于不同进程间的通信,尤其是跨语言的通信场景,如C#和C++。 ...

    C#进程间通信之共享内存

    本文将重点探讨在C#环境中如何利用共享内存进行进程间通信,这对于理解和优化多进程应用的性能至关重要。 共享内存是一种高效的IPC机制,它允许多个进程直接读写同一块内存区域,而无需通过任何中间媒介。在C#中,...

    C# IPC 之 Socket 进程间通信 源码

    标题"**C# IPC 之 Socket 进程间通信 源码**"指的是使用C#语言通过Socket实现的进程间通信的源代码示例。Socket在IPC中的作用相当于一个通信通道,它允许两个程序通过网络接口进行数据传输,即使这两个程序位于同一...

    操作系统—进程管理c#

    8. **进程间通信(IPC)**:进程间通信是不同进程间交换信息的方式,C#支持多种IPC方式,如管道(Pipe)、命名管道(NamedPipe)、内存映射(MemoryMappedFile)、套接字(Socket)等。 9. **异常处理**:在进行进程管理时...

    C#进程间通信-共享内存代码实例

    在C#中,实现进程间通信有多种方法,如管道、套接字、消息队列等,而共享内存是其中一种高效且直接的方式。本篇文章将深入探讨C#中的共享内存实现,并通过一个具体的代码实例来阐述其工作原理。 共享内存是一种让多...

    c# Remoting 实现进程间互相调用 并传递消息

    C# Remoting是.NET Framework提供的一种跨进程通信技术,它允许不同进程间的对象互相调用方法,实现分布式系统中的组件交互。在这个场景中,我们关注的是如何利用C# Remoting来实现在不同进程之间传递消息并进行互...

    C#进程间通信-消息队列代码实例

    本实例以C#语言为基础,详细讲解如何利用消息队列进行进程间通信。 首先,我们需要理解消息队列的基本概念。消息队列是一种存储和转发机制,它将消息从一个进程发送到另一个进程,而无需两者同时在线。消息队列的...

    c#源码 进程间通讯

    本压缩包文件“c#进程间通讯”包含了使用C# 2005实现进程间通信的源代码示例,特别关注了通过API中的`PostMessage`函数来发送消息的方式。 `PostMessage`是Windows API中的一部分,用于异步地向指定窗口发送一个...

    C#进程间通信-管道代码实例

    本文将深入探讨C#中的管道进程间通信,并通过一个具体的代码实例进行解析。 首先,管道通信是一种半双工(即数据只能单向流动)的数据通信方式,可以用于同一台机器上的不同进程之间,甚至在不同的网络节点之间。C#...

    c#进程之间通信,c#exe 之间发消息,c#exe 相互通信

    在C#编程中,进程间通信(IPC,Inter-Process Communication)是实现不同应用程序或同一计算机上多个执行单元之间共享数据、同步操作的关键技术。在VS2005环境下,开发者可以利用各种IPC机制实现C# EXE之间的消息...

    QT之进程和进程间通信(IPC)

    进程是操作系统的基础之一。一个进程可以认为是一个正在执行的程序。我们可以把进程当做计算机运行时的一个基础单位。关于进程的讨论已经超出了本章的范畴,现在我们假定你是了解这个概念的。 在 Qt 中,我们使用...

    C# WM_消息实现进程间通信实例 VS2008

    在Windows环境下,C#语言提供了多种实现进程间通信的方法,其中WM_消息机制是一种常用的方式。本实例将通过VS2008开发环境,详细介绍如何利用WM_消息来实现C#中的进程间通信。 WM_消息是Windows操作系统内核定义的...

    操作系统 进程管理 C# VS2005

    在C#编程语言中,尽管它主要用于构建基于.NET Framework的应用程序,但通过.NET Framework提供的System.Diagnostics命名空间,我们可以对操作系统进程进行一些基本的管理和控制。Visual Studio 2005(VS2005)是这个...

    VC+C# 实现的进程间通讯

    进程间通信(IPC,Inter-Process Communication)是操作系统中一种重要的技术,允许不同进程之间交换数据和信息。在这个“VC+C# 实现的进程间通讯”项目中,开发者使用了两种常见的IPC方法:共享内存和消息传递。接...

    C# 进程间通信 命名通道

    在.NET框架中,C#提供了一种进程间通信(IPC,Inter-Process Communication)的方式——命名管道(Named Pipe)。命名管道是一种高效的通信机制,允许不同的进程在同一台计算机上相互通信,甚至可以在不同用户会话...

    C#使用SendMessage实现进程间通信的方法

    在C#编程中,进程间通信(IPC,Inter-Process Communication)是一种允许不同进程之间交换数据的技术。在本文中,我们将探讨如何使用Windows API中的`SendMessage`函数在两个C#进程中实现这一通信方式。 `...

    C# 进程间通信:命名管道方式例子

    本文将深入探讨C#中使用命名管道进行进程间通信(IPC)的技术细节,并通过一个具体的实例来演示其实现。 命名管道是一种在操作系统内核中创建的特殊文件,允许不同进程之间通过读写操作进行通信。在Windows系统中,...

    socket进程间通信的一个一对多例子

    在IT领域,进程间通信(IPC,Inter-Process Communication)是一种关键的技术,使得不同进程能够共享数据、协调工作。在本示例中,"socket进程间通信的一个一对多例子"着重于利用socket来实现这样的通信模式。Socket...

    通过Windows窗口消息实现 QT进程间通信

    在Windows操作系统环境下,利用Windows窗口消息机制进行进程间通信是一种常见的方法,尤其当涉及到跨语言或者跨框架的交互时。本篇文章将详细探讨如何在QT框架下利用Windows窗口消息来实现进程间通信。 首先,让...

Global site tag (gtag.js) - Google Analytics