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

原文:【操作系统】进程间通信(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.查阅有关进程间通信的资料

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

服务器端设计:

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

客户端设计:
【操作系统】进程间通信(C#)

参考网址: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;
}

【实验结果】

服务器端输出截图:

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

客户端截图:

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

【实验中遇到的问题】

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

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

网上查阅了一些资料,了解信号灯也是进程间通信的一种方式。“信号灯与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统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

上一篇:redis学习(1)--- NoSQL介绍


下一篇:window下mysql安装步骤