分享
 
 
 

在.NET中使用命名管道完成进程间通信

王朝c#·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

你曾经需要在同一台机器的两个.NET应用程序间进行数据交换吗?例如,一个Web站点和一个Windows服务?.NET框架提供了几种好的选择来完成进程间通信(IPC):Web Service,Remoting。最快的是Remoting,因为它使用TCP通道和二进制格式。

然而,假如需要频繁地从一个应用程序调用另外一个应用程序,并且你主要关心的是性能,Remoting还是显得慢了一点。让Remoting变慢的,不是协议,而是序列化。

通常来说,Remoting是很不错的,但假如仅限于本地机器的两个进程间相互通信,其处理机制增加了不必要的开销。所以要考虑一些别的选择,比较好的是命名管道(Named Pipes),不会进行二进制序列化,所以提供了更快的IPC。

要记住,这个解决方案最有效的使用是在一个应用程序需要和另一个应用程序进行非常频繁的、短文本的消息通信的情况下,并且是在同一台机器或在同一局域网内部。对于结构化的数据交换,这些文本消息也可以是XML文档或序列化的.NET对象。通信时没有安全层,因为命名管道最多只能在局域网中运行,所以假定安全问题由别的层进行处理。

一、实现命名管道

以下是.NET命名管道解决方案中几个主要的类。

. NamedPipeNative:这个类和kernal32.dll联系实现命名管道的通信,其中包含一些常用方法和常量。

. NamedPipeWrapper :这个类是NamedPipeNative的一个包装。

. ApipeConnection:这是一个抽象类,定义了命名管道连接、读、写数据的方法。这个类是从ClientPipeConnection 和ServerPipeConnection 继续的,分别在客户端和服务器端应用程序中使用。

. ClientPipeConnection:被客户端应用程序使用,使用命名管道和服务器通信。

. ServerPipeConnection:答应命名管道服务器创建连接,和客户端进行通信。

. PipeHandle:保存操作系统的本地句柄,以及管道连接的当前状态。

了解上述的类之后,需要了解一下命名管道的操作。

二、创建一个服务器端命名管道

服务器端管道名的语法是:\\.\pipe\PipeName。“PipeName”.. 部分是管道的具体名字。要连接管道,客户端应用程序需要创建一个同样名称的客户端命名管道。假如客户端在不同的机器上,服务器端管道的名称应该是\\SERVER\pipe\PipeName。下面的代码是NamedPipeWrapper的一个静态方法,被用来实例化一个服务器端命名管道。

public static PipeHandle Create(string name,uintoutBuffer, uintinBuffer){

name = @"\.\pipe\" + name;

PipeHandle handle = new PipeHandle();

for(inti=1;i<=ATTEMPTS;i++){

handle.State=InterProcessConnectionState.Creating;

handle.Handle = NamedPipeNative.CreateNamedPipe( name,

NamedPipeNative.PIPE_Access_DUPLEX,

NamedPipeNative.PIPE_TYPE_MESSAGE

NamedPipeNative.PIPE_READMODE_MESSAGE

NamedPipeNative.PIPE_WAIT,

NamedPipeNative.PIPE_UNLIMITED_INSTANCES,

outBuffer,

inBuffer,

NamedPipeNative.NMPWAIT_WAIT_FOREVER,

IntPtr.Zero);

if(handle.Handle.ToInt32()!=NamedPipeNative.INVALID_HANDLE_VALUE){

handle.State=InterProcessConnectionState.Created;

break;

}

if (i >= ATTEMPTS) {

handle.State = InterProcessConnectionState.Error;

throw new NamedPipeIOException("Error creating named

pipe"+name+".Internalerror:"+NamedPipeNative.GetLastError().ToString(),NamedPipeNative.GetLastError());

}

}

returnhandle;

}

通过调用NamedPipeNative.CreateNamedPipe方法,上面的方法创建了一个双方互通的命名管道,并且指定管道可以有无限制的实例。常量的名称都是英语,不难看懂,就不一一解释了。

假定服务器端命名管道创建成功,它就可以开始监听客户端连接了。

三、连接到客户端管道

命名管道服务器需要设置成监听状态,以使客户端管道能够连接它。这可以由调用NamedPipeNative.ConnectNamedPipe方法完成。

调用NamedPipeNative.CreateFile方法,就可以创建一个命名管道客户端,并且连接到一个监听的服务器管道。下面的代码是NamedPipeWrapper.ConnectToPipe的一部分,可以阐释这一点。

public static PipeHandle ConnectToPipe(string pipeName, string serverName) {

PipeHandle handle = new PipeHandle();

//Buildthename ofthe pipe.

string name = @"\" + serverName + @"\pipe\" + pipeName;

for(inti=1;i<=ATTEMPTS;i++){

handle.State = InterProcessConnectionState.ConnectingToServer;

// Try to connect to the server

handle.Handle = NamedPipeNative.CreateFile(name, NamedPipeNative.GENERIC_READ NamedPipeNative.

GENERIC_WRITE, 0,null,NamedPipeNative.OPEN_EXISTING,0,0);

在创建一个PipeHandle对象并建立管道名称后,我们调用NamedPipeNative.CreateFile方法来创建一个客户端命名管道,并连接到指定的服务器端管道。在我们的例子中,客户端管道被配置为可读可写的。

假如客户端管道被成功创建,NamedPipeNative.CreateFile方法返回其对应的本地句柄,这在以后的操作中会用到。假如由于某种原因创建失败,方法会返回1, 并把NamedPipeNative设为INVALID_HANDLE_VALUE常量。

在客户端命名管道可以用来读和写之前,还要做一件事情。我们需要把handle 设为PIPE_READMODE_MESSAGE。可以调用NamedPipeNative.SetNamed-PipeHandleState 实现。

if (handle.Handle.ToInt32() != NamedPipeNative.INVALID_HANDLE_VALUE){

// The client managed to connect to the server pipe

handle.State = InterProcessConnectionState.

ConnectedToServer;

// Set the read mode of the pipe channel

uint mode = NamedPipeNative.PIPE_READMODE_MESSAGE;

if(NamedPipeNative.SetNamedPipeHandleState(handle.Handle,refmode,IntPtr.Zero,IntPtr.Zero)){

break;

}

每个客户端管道和一个服务器管道的实例通信。若服务器端的实例达到最大数目,创建客户端管道会失败。

四、读写数据

从命名管道读数据时我们不能提前知道消息的长度。我们的解决方案不需要处理很长的消息,所以使用System.Int32变量来指定消息的长度。

NamedPipeWrapper.WriteBytes 方法可以将消息写到一个命名管道,消息按UTF8编码,然后按字节数组传递。

public static void WriteBytes(PipeHandle handle, byte[]bytes) {

byte[] numReadWritten = new byte[4];

uint len;

if(bytes==null){

bytes=newbyte[0];

}

if (bytes.Length == 0) {

bytes = new byte[1];

bytes = System.Text.Encoding.UTF8.GetBytes(" ");

}

// 获取消息的长度:

len= (uint)bytes.Length;

handle.State = InterProcessConnectionState.Writing;

// 获取消息长度的字节表示,先写这四字节

if(NamedPipeNative.WriteFile(handle.Handle,BitConverter.GetBytes(len),4,numReadWritten,0)){

// 写余下的消息

if(!NamedPipeNative.WriteFile(handle.Handle,bytes,len,numReadWritten,0)){

handle.State=InterProcessConnectionState.Error;

thrownewNamedPipeIOException("Errorwritingtopipe. Internalerror:"+NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());

}

}

else{

handle.State=InterProcessConnectionState.Error;

thrownewNamedPipeIOException("Errorwritingtopipe.Internalerror:"+NamedPipeNative.GetLastError().ToString(),

NamedPipeNative.GetLastError());

}

handle.State =InterProcessConnectionState.Flushing;

// 激活管道,保证任何缓存数据都被写入管道,不会丢失:

Flush(handle);

handle.State = InterProcessConnectionState.FlushedData;

}

要从一个命名管道读数据,先要把前四个字节转化为整数以确定消息的长度。接着,就可以读余下的数据了,请看下面的NamedPipeWrapper.ReadBytes方法。

public static byte[] ReadBytes(PipeHandle handle, int maxBytes) {

byte[]numReadWritten=newbyte[4];

byte[]intBytes=newbyte[4];

byte[]msgBytes=null;

intlen;

handle.State=InterProcessConnectionState.Reading;

handle.State=InterProcessConnectionState.Flushing;

// 读前四个字节并转化为整数:

if(NamedPipeNative.ReadFile(handle.Handle, intBytes,4, numReadWritten, 0)) {

len=BitConverter.ToInt32(intBytes,0);

msgBytes=newbyte[len];

handle.State=InterProcessConnectionState.Flushing;

// 读余下的数据或抛出异常:

if(!NamedPipeNative.ReadFile(handle.Handle,msgBytes,(uint) len,numReadWritten,0)){

handle.State=InterProcessConnectionState.Error;

thrownewNamedPipeIOException("Error readingfrompipe. Internalerror:"+NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());

}

}

else {

handle.State=InterProcessConnectionState.Error;

thrownewNamedPipeIOException("Errorreadingfrompipe. Internalerror:"+NamedPipeNative.GetLastError().ToString(), NamedPipeNative.GetLastError());

}

handle.State=InterProcessConnectionState.ReadData;

if(len>maxBytes){

returnnull; }

returnmsgBytes;

}

以上就是命名管道的实现和一些主要的方法,下面介绍如何创建进行文本消息通信的命名管道服务器和客户端应用程序。

QQRead.com 推出数据恢复指南教程 数据恢复指南教程

数据恢复故障解析

常用数据恢复方案

硬盘数据恢复教程

数据保护方法

数据恢复软件

专业数据恢复服务指南

五、创建命名管道服务器

命名管道服务器是一个多线程的引擎,用来为并发的请求服务,创建新的线程和管道连接。

AppModule.NamedPipes assembly包含了一个基类ApipeConnection,是对普通命名管道操作的封装,例如创建管道、读写数据等等,这是一个抽象类。

另外,有两个从ApipeConnection继续的管道连接类ClientPipeConnection 和 ServerPipeConnection。它们重载了一些方法(例如连接和关闭)并为服务器和客户端命名管道分别提供实现。ClientPipeConnection 和ServerPipeConnection都有调用Dispose方法的析构器,

清除非管控的资源。

命名管道服务器负责创建命名管道,处理客户端连接。有两个主要的类提供了服务功能: ServerNamedPipe和PipeManager。

(1)ServerNamedPipe类

其构造器如下:..

internal ServerNamedPipe(stringname, uint outBuffer,uintinBuffer,intmaxReadBytes){

PipeConnection=newServerPipeConnection(name,outBuffer,inBuffer,maxReadBytes);

PipeThread=newThread(newThreadStart(PipeListener));

PipeThread.IsBackground=true;

PipeThread.Name ="PipeThread "+this.PipeConnection.NativeHandle.ToString();

LastAction=DateTime.Now;

}

构造器创建了一个新的ServerPipeConnection实例,并调用PipeListener方法。随后的主要部分是循环监听客户端连接,以及读写数据。

private void PipeListener() {

CheckIfDisposed();

try{

Listen=Form1.PipeManager.Listen;

Form1.ActivityRef.AppendText("Pipe"+this.PipeConnection.NativeHandle.ToString() + ": new pipe started" + Environment.NewLine);

while(Listen){

LastAction=DateTime.Now;

// 从客户端管道读取数据:

stringrequest=PipeConnection.Read();

LastAction=DateTime.Now;

if(request.Trim()!=""){

//PipeManager.HandleRequest 方法接受客户端请求处理之,

// 然后进行响应,这个响应接着就被写入管道。

PipeConnection.Write(Form1.PipeManager.HandleRequest(request));

Form1.ActivityRef.AppendText("Pipe"+this.PipeConnection.NativeHandle.ToString()+ ":requesthandled"+Environment.NewLine);

}

else{ PipeConnection.Write("Error:badrequest");}

LastAction=DateTime.Now;

// 从客户端管道断开连接

PipeConnection.Disconnect();

if(Listen){

Form1.ActivityRef.AppendText("Pipe"+this.PipeConnection. NativeHandle.ToString()+":listening"+Environment.NewLine);

// 开始监听一个新的连接:

Connect(); }

Form1.PipeManager.WakeUp();

}

}

catch(System.Threading.ThreadAbortExceptionex){}

catch(System.Threading.ThreadStateExceptionex){}

catch(Exceptionex){

//Logexception

}

finally{

this.Close();}

}

请注重不要关闭服务器管道,因为创建一个服务器管道是一个相对昂贵的操作,会引起比较昂贵的开销。

(2)PipeManager 类

PipeManager 类负责在必要的时候创建服务器管道,治理线程,并生成客户端请求的响应。下面代码中Initialize方法调用Start方法创建一个新的线程:

public void Initialize() {

Pipes=Hashtable.Synchronized(_pipes);

Mre =newManualResetEvent(false);

MainThread =newThread(newThreadStart(Start));

MainThread.IsBackground=true;

MainThread.Name = "MainPipeThread";

MainThread.Start();

Thread.Sleep(1000);

}

PipeManager类只在获得请求的时候才创建新的管道连接和线程。这意味着ServerPipeConnection对象只在没有连接存在或所有连接都忙于响应请求的时候才被创建。通常2-3个命名管道实例就能处理很大负载的并发客户端请求,但这个主要取决于处理客户端请求和生

成响应的时间。

创建ServerPipeConnection对象的引用被保存在管道哈希表中。

private void Start() {

try{

while(_listen){

int[]keys=newint[Pipes.Keys.Count];

Pipes.Keys.CopyTo(keys,0);

// 循环检验ServerPipeConnection 对象是否还是可用:

foreach(intkeyinkeys){

ServerNamedPipeserverPipe=(ServerNamedPipe)Pipes[key];

if(serverPipe!=null&& DateTime.Now.SuBTract(serverPipe.LastAction).Milliseconds>

PIPE_MAX_STUFFED_TIME && serverPipe.PipeConnection.GetState()!=InterProcessConnectionState.WaitingForClient){

serverPipe.Listen=false;

serverPipe.PipeThread.Abort();

RemoveServerChannel(serverPipe.PipeConnection.NativeHandle);

}

}

//NumberPipes 字段包含了可以在服务器上拥有的命名管道最大数目

if(numChannels<=NumberPipes){

ServerNamedPipe pipe = new ServerNamedPipe(PipeName,OutBuffer,InBuffer,MAX_READ_BYTES);

try{

//Connect 方法将新生成的管道置为监听模式。

pipe.Connect();

pipe.LastAction=DateTime.Now;

System.Threading.Interlocked.Increment(refnumChannels);

// 开始ServerPipeConnection 线程

pipe.Start();

Pipes.Add(pipe.PipeConnection.NativeHandle,pipe);

}

catch (InterProcessIOException ex) {

RemoveServerChannel(pipe.PipeConnection.NativeHandle);

pipe.Dispose();

}

}

else{ Mre.Reset(); Mre.WaitOne(1000,false); }

}

}

catch { //Logexception }

}

六、创建客户端管道连接

要使用命名管道把一个客户端应用程序连接到服务器,我们必须创建ClientPipeConnection类的一个实例,使用它的方法来读写数据。

IInterProcessConnectionclientConnection=null;

try{

clientConnection=newClientPipeConnection("MyPipe",".");

clientConnection.Connect();

clientConnection.Write(textBox1.Text);

clientConnection.Close();

}

catch{

clientConnection.Dispose();

}

管道名称“MyPipe” 必须和服务器管道的名称一样,假如命名管道服务器也在同一台机器上,ClientPipeConnection构造器的第二个参数应该是“.”。假如不在同一台机器上,第二个参数就是服务器的网络名称。

以上,我介绍了命名管道的解决方案,我再重申一下,命名管道最有效的使用是在一个应用程序需要和另一个应用程序进行非常频繁的,短文本的消息通信的情况下,并且是在同一台机器或在局域网内部。假如您碰到了这样的情况,希望我的这些代码能给你启发和参考。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有