原文:【操作系统】进程间通信(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#)的更多相关文章

  1. 反应堆模式最牛的那篇论文--由solidmango执笔翻译

    The Reactor:An Object-Oriented Wrapper for Event-Driven Port Monitoring and Service Demultiplexing 反 ...

  2. 转自52 梦回凉亭的她 Java常见问题,面试题

    收集整理分享# 相关概念## 面向对象的三个特征封装,继承,多态.这个应该是人人皆知.有时候也会加上抽象.## 多态的好处允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不 ...

  3. 【Java】留下没有基础眼泪的面试题

    前言 只有光头才能变强 本文力求简单讲清每个知识点,希望大家看完能有所收获 一.如何减少线程上下文切换 使用多线程时,不是多线程能提升程序的执行速度,使用多线程是为了更好地利用CPU资源! 程序在执行 ...

  4. 面试题(转载csdn)

    转自https://blog.csdn.net/linzhiqiang0316/article/details/80473906 相关概念 面向对象的三个特征 封装,继承,多态,这个应该是人人皆知,有 ...

  5. Keep面经汇总

    目录 一.Java 线程如何终止 如何用一个cancel方法停止两个线程 泛型原理.使用场景.优缺点 手写代码,设计parseInt hashmap是怎么实现的,是线程安全的吗 知道hashmap的扩 ...

  6. 031 一次全面的java复习

    一:相关概念 1.面向对象的三个特征 封装,继承,多态,这个应该是人人皆知,有时候也会加上抽象. 2.多态的好处 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性.简单的说 ...

  7. Java多线程面试题整理

    部分一:多线程部分: 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速. ...

  8. java面试题汇总(有的题无视即可,没什么实际用途)

    相关概念 面向对象的三个特征 封装,继承,多态,这个应该是人人皆知,有时候也会加上抽象. 多态的好处 允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消 ...

  9. 史上最全Java面试题(带全部答案)

    今天要谈的主题是关于求职,求职是在每个技术人员的生涯中都要经历多次.对于我们大部分人而言,在进入自己心仪的公司之前少不了准备工作,有一份全面细致面试题将帮助我们减少许多麻烦.在跳槽季来临之前,特地做这 ...

  10. 献给java求职路上的你们

    为了更好的树立知识体系,我附加了相关的思维导图,分为pdf版和mindnote版.比如java相关的导图如下: 由于时间仓促,有些地方未写完,后面会继续补充.如有不妥之处,欢迎及时与我沟通. 相关概念 ...

随机推荐

  1. js学习之道:js防止表单重复提交

    第一种:用flag标识,下面的代码设置checkSubmitFlg标志: <script language="”javascript”"> var checkSubmi ...

  2. ERP售前顾问形象寓意

    几个做销售的朋友一起去X山旅游,一群爱侃的人凑在一起,一顿云山雾罩的神吹是少不了的.突然看到一栋平房前面伸出一个大大的幌子:铁嘴铜牙.下面一行小字:不灵不要钱(管理专家:提供专业解决方案). 销售都是 ...

  3. Beanstalkd介绍

    特征 优先级:任务 (job) 可以有 0~2^32 个优先级, 0 代表最高优先级,beanstalkd 采用最大最小堆 (Min-max heap) 处理任务优先级排序, 任何时刻调用 reser ...

  4. Oracle Spool详解

    转自:http://blog.sina.com.cn/s/blog_6bccf0360101hzsh.html 1.spool的作用是什么? spool的作用可以用一句话来描述:在sqlplus中用来 ...

  5. 爬虫学习-使用CrawlSpider

    使用scrapy中的CrawlSpider类来进行爬行 一直用的是BaseSpider,回调函数的方式,有一个问题是title,date在一个页面,author,detail在另一个页面时,怎么把这些 ...

  6. Java向上转型注意事项

    继承.接口:Java子类中如果含有父类中不包含的变量与方法,子类对象向上转型时就是丢失这些变量和方法. interface SuperClass{ int i = 2; void f() ; } cl ...

  7. html(三)

    今天自己画了个安卓机器人,之前听徐大大讲过一次,查手册去动手的时候其实发觉不是很难,这种规则的图像还是很好画的,主要是用<div>标签和<span>标签去做的,通过CSS添加样 ...

  8. jsPlumb开发入门教程(实现html5拖拽连线)

    jsPlumb是一个强大的JavaScript连线库,它可以将html中的元素用箭头.曲线.直线等连接起来,适用于开发Web上的图表.建模工具等.它同时支持jQuery+jQuery UI.MooTo ...

  9. gcc -L -l的使用

    -l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so ...

  10. ibatis中的resultClass,parameterClass,resultMap,resultType的使用与区别

    parameterClass 是参数类.指定了参数的完整类名(包括包路径).可通过别名避免每次重复书写冗长的类名. resultClass 是结果类, 二.resultClass取值 1.result ...