C和C#进程之间通过命名管道通信精.docx
- 文档编号:27392883
- 上传时间:2023-06-30
- 格式:DOCX
- 页数:30
- 大小:29.57KB
C和C#进程之间通过命名管道通信精.docx
《C和C#进程之间通过命名管道通信精.docx》由会员分享,可在线阅读,更多相关《C和C#进程之间通过命名管道通信精.docx(30页珍藏版)》请在冰豆网上搜索。
C和C#进程之间通过命名管道通信精
C++和C#进程之间通过命名管道通信
“命名管道”是一种简单的进程间通信(IPC)机制。
命名管道可在同一台计算机的不同进程之间,或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。
用命名管道来设计应用程序实际非常简单,并不需要事先深入掌握基层网络传送协议(如TCP/IP或IPX)的知识。
因为命名管道利用了微软网络提供者(MSNP)重定向器,通过一个网络,在各进程间建立通信。
这样一来,应用程序便不必关心网络协议的细节。
命令管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统”(NamedPipeFileSystem,NPFS)接口。
因此,客户机和服务器应用可利用标准的Win32文件系统API函数(如ReadFile和WriteFile)来进行数据的收发。
通过这些API函数,应用程序便可直接利用Win32文件系统命名规范,以及WindowsNT/Windows2000文件系统的安全机制。
NPFS依赖于MSNP重定向器在网上进行命名管道数据的发送和接收。
这样一来,便可实现接口的“与协议无关”特性:
若在自己开发的应用程序中使用命名管道在网上不同的进程间建立通信,程序员不必关心基层网络传送协议(如TCP和IPX等等)的细节。
客户机和服务器
命名管道最大的特点便是建立一个简单的客户机/服务器程序设计体系。
在这个体系结构中,在客户机与服务器之间,数据既可单向传递,亦可双向流动。
对命名管道服务器和客户机来说,两者的区别在于:
服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。
对一个客户机应用来说,它只能同一个现成的命名管道服务器建立连接。
在客户机应用和服务器应用之间,一旦建好连接,两个进程都能对标准的Win32函数,在管道上进行数据的读取与写入。
这些函数包括ReadFile和WriteFile等等。
服务器
要想实现一个命名管道服务器,要求必须开发一个应用程序,通过它创建命名管道的一个或多个“实例”,再由客户机进行访问。
对服务器来说,管道实例实际就是一个句柄,用于从本地或远程客户机应用接受一个连接请求。
按下述步骤行事,便可写出一个最基本的服务器应用:
1使用API函数CreateNamedPipe,创建一个命名管道实例句柄。
2使用API函数ConnectNamedPipe,在命名管道实例上监听客户机连接请求。
3使用API函数ReadFile,从客户机接收数据;使用API函数WriteFile,将数据发给客户机。
4使用API函数DisconnectNamedPipe,关闭命名管道连接。
5使用API函数CloseHandle,关闭命名管道实例句柄。
首先,我们的服务器进程需要使用CreateNamedPipe这个API调用,创建一个命名管道实例。
下面给出CreateNamedPipe()的函数原型:
HANDLECreateNamedPipe( LPCTSTRlpName,//指向管道名称的指针 DWORDdwOpenMode,//管道打开模式 DWORDdwPipeMode,//管道模式 DWORDnMaxInstances,//最大实例数 DWORDnOutBufferSize,//输出缓存大小 DWORDnInBufferSize,//输入缓存大小 DWORDnDefaultTimeOut,//超时设置 LPSECURITY_ATTRIBUTESlpSecurityAttributes//安全属性指针;
其中几个主要参数如下:
lpName是管道的名称,命名管道的命名采用如下格式:
//ServerName/Pipe/PipeName
其中,第一部分//ServerName指定了服务器的名字,命名管道服务即在此服务器创建,而且要由它对进入的连接请求进行“监听”,其字串部分可表示为一个小数点”.”(表示本机)、星号(当前网络字段)、域名或是一个真正的服务;第二部分/Pipe是一个不可变化的硬编码字串,以指出该文件是从属于NPFS,不区分大小写;第三部分/PipeName则是应用程序可以唯一定义及标识一个命名管道的名字,而且可以设置多级目录。
dwOpenMode参数用于指示一个管道创建好之后,它的传输方向、I/O控制以及安全模式。
PIPE_ACCESS_标志决定了在客户机与服务器之间,数据在管道上的流动方向。
可用PIPE_ACCESS_DUPLEX标志以双向传输方式打开一个管道。
也就是说,在客户机与服务器之间,数据可以双向传输。
除此以外,亦可使用PIPE_ACCESS_INBOUND或者PIPE_ACCESS_OUTBOUND标志,以单向传输方式打开一个管道。
也就是说,数据只能从客户机传向服务器,或从服务器传向客户机。
dwPipeMod命名管道提供了两种基本的通信模式:
字节模式(PIPE_TYPE_BYTE)和消息模式(PIPE_TYPE_MESSAGE)。
在字节模式中,信息以连续字节流的形式在客户与服务器之间流动,这也就意味着,对于客户机应用和服务器应用,在任何一个特定的时间段内,都无法准确知道有多少字节从管道中读出或写入。
在这种通信模式中,一方在向管道写入某个数量的字节后,并不能保证管道另一方能读出等量的字节。
对于消息模式,客户机和服务器则是通过一系列不连续的数据包进行数据的收发。
从管道发出的每一条消息都必须作为一条完整的消息读入。
其他的函数及其参数含义详见msdn,此处不一一讲解。
服务器调用该函数,如果在已定义超时值变为零以前,有一个实例管道可以使用,则创建成功并返回管道句柄,以此侦听来自客户机的连接请求。
客户机
实现一个命名管道客户机时,要求开发一个应用程序,令其建立与某个命名管道服务器的连接。
注意客户机不可创建命名管道实例。
然而,客户机可打开来自服务器的、现成的实例。
下述步骤讲解了如何编写一个基本的客户机应用:
1用API函数WaitNamedPipe,等候一个命名管道实例可供自己使用。
2用API函数CreateFile,建立与命名管道的连接。
3用API函数WriteFile和ReadFile,分别向服务器发送数据,或从中接收数据。
4用API函数CloseHandle,关闭打开的命名管道会话。
建立一个连接之前,客户机需要用WaitNamedPipe函数,检查是否存在一个现成的命名管道实例。
WaitNamedPipe成功完成后,客户机需要用CreateFile这个API函数,打开指向服务器命名管道实例的一个句柄。
简单说来,服务器调用CreateNamedPipe来创建管道,如果在已定义超时值变为零以前,有一个实例管道可以使用,则创建成功并返回管道句柄,以此侦听来自客户机的连接请求。
另一方面,客户机通过函数WaitNamedPipe()使服务器进程等待来自客户的实例连接。
如果在超时值变为零以前,有一个管道可供连接使用,则函数将成功返回,并通过调用CreateFile()或CallNamedPipe()来呼叫对服务器的连接。
此时服务器将接受客户的连接请求,成功建立连接,服务器调用的等待客户机建立连接的ConnectNamedPipe()函数也将成功返回。
然后,管道两端就可以进行通信了。
从调用时序上看,首先是客户机通过WaitNamedPipe()使服务器的CreateFile()在限时时间内创建实例成功,然后双方通过ConnectNamedPipe()和CreateFile()成功连接,在返回用以通信的文件句柄后,客户、服务双方即可进行通信。
在建立了连接后,客户机与服务器即可通过ReadFile()和WriteFile()并利用得到的管道句柄,以文件读写的形式彼此间进行信息交换。
当客户与服务器的通信结束,或是由于某种原因一方需要断开时,由客户机调用CloseFile()函数关闭打开的管道句柄,服务器随即调用DisconnectNamedPipe()函数。
当然,服务器也可以通过单方面调用DisconnectNamedPipe()来终止连接。
在终止连接后调用函数CloseHandle()来关闭此管道。
下篇将给出的程序清单即是按照上述方法实现的命名管道服务器和客户机进行通信的程序实现代码:
接上篇:
我采用的是C#开发的一个windows应用程序(pipe_server_csharp)作为服务器端,而MFC开发的应用程序(NamedPipeClient_vc)作为客户端。
客户端和服务器端要进行频繁的大量的通信,常见的是文本信息和曲线数据,例如,一共有10条曲线,每条曲线有1000000条double数据。
服务器端:
服务器端是用在VS2005中用C#开发的一个名为pipe_server_csharp的应用程序,只有一个名为frmServer的主界面。
由于管道的相关API函数都是属于kernel32.dll函数,C#中不能直接调用,所以必须将所要用到的API函数全部封装在一个类NamedPipeNative中。
至于如何调用这些API函数,有兴趣的朋友可以上网搜索,或者看我的另一篇文章《C#中怎么调用命名管道的WinAPI》。
NamedPipeNative类中几个主要函数如下(大家可以对比这些重写的API函数和原来的函数有什么变化):
[DllImport("kernel32.dll",SetLastError=true]
publicstaticexternIntPtrCreateNamedPipe
(
StringlpName,//pipename
uintdwOpenMode,//pipeopenmode
uintdwPipeMode,//pipe-specificmodes
uintnMaxInstances,//maximumnumberofinstances
uintnOutBufferSize,//outputbuffersize
uintnInBufferSize,//inputbuffersize
uintnDefaultTimeOut,//time-outinterval
IntPtrpipeSecurityDescriptor//SD
;
[DllImport("kernel32.dll",SetLastError=true]
publicstaticexternboolConnectNamedPipe
(
IntPtrhHandle,//handletonamedpipe
OverlappedlpOverlapped//overlappedstructure
;
[DllImport("kernel32.dll",SetLastError=true]
publicstaticexternIntPtrCreateFile
(
StringlpFileName,//filename
uintdwDesiredAccess,//accessmode
uintdwShareMode,//sharemode
SecurityAttributesattr,//SD
uintdwCreationDisposition,//howtocreate
uintdwFlagsAndAttributes,//fileattributes
uinthTemplateFile;//handletotemplatefile
[DllImport("kernel32.dll",SetLastError=true]
publicstaticexternboolReadFile
(
IntPtrhHandle,//handletofile
byte[]lpBuffer,//databuffer字节流
uintnNumberOfBytesToRead,//numberofbytestoread
byte[]lpNumberOfBytesRead,//numberofbytesread
uintlpOverlapped//overlappedbuffer
;
[DllImport("kernel32.dll",SetLastError=true]
publicstaticexternboolWriteFile
(
IntPtrhHandle,//handletofile
byte[]lpBuffer,//databuffer字节流
uintnNumberOfBytesToWrite,//numberofbytestowrite
byte[]lpNumberOfBytesWritten,//numberofbyteswritten
uintlpOverlapped//overlappedbuffer
;
还有其他一些常量:
publicconstuintPIPE_ACCESS_DUPLEX=0x00000003;
publicconstuintPIPE_ACCESS_OUTBOUND=0x00000002;
publicconstuintPIPE_TYPE_BYTE=0x00000000;
publicconstuintPIPE_TYPE_MESSAGE=0x00000004;
在此不一一列举了。
封装之后,在C#中就可以直接调用这些函数和常量了。
另外,创建了一个类CMyPipe,封装了服务器管道的属性和方法,如创建管道,读写数据等:
namespacepipe_server_csharp
{
publicclassCMyPipe
{
privatestringm_PipeName;//管道名全称
publicstringPipeName
{
get{returnm_PipeName;}
set{m_PipeName=value;}
}
privateIntPtrm_HPipe;//管道句柄
publicIntPtrHPipe
{
get{returnm_HPipe;}
set{m_HPipe=value;}
}
publicCMyPipe(//无参构造函数
{
m_HPipe=(IntPtrNamedPipeNative.INVALID_HANDLE_VALUE;
m_PipeName="\\\\.\\pipe\\robinTest";
}
publicCMyPipe(stringpipeName//有参构造函数
{
m_HPipe=(IntPtrNamedPipeNative.INVALID_HANDLE_VALUE;
m_PipeName=pipeName;
}
~CMyPipe(//析构函数
{
Dispose(;
}
publicvoidDispose(
{
lock(this
{
if(m_HPipe!
=(IntPtrNamedPipeNative.INVALID_HANDLE_VALUE
{
NamedPipeNative.CloseHandle(m_HPipe;
m_HPipe=(IntPtrNamedPipeNative.INVALID_HANDLE_VALUE;
}
}
}
publicvoidCreatePipe(//创建管道
{
m_HPipe=NamedPipeNative.CreateNamedPipe(m_PipeName,
NamedPipeNative.PIPE_ACCESS_DUPLEX,//数据双工通信
NamedPipeNative.PIPE_TYPE_MESSAGE|NamedPipeNative.PIPE_WAIT,100,//最大实例个数
128,//流出数据缓冲大小
128,//流入数据缓冲大小
150,//超时,毫秒
IntPtr.Zero//安全信息
;
if(m_HPipe.ToInt32(==NamedPipeNative.INVALID_HANDLE_VALUE
{
frmServer.ActivityRef.AppendText("创建命名管道失败";
frmServer.ActivityRef.AppendText(Environment.NewLine;
return;
}
frmServer.ActivityRef.AppendText("创建命名管道完毕";
frmServer.ActivityRef.AppendText(Environment.NewLine;
}
publicvoidReadCurveData(//读取曲线数据
{
intnCurvesToRead=0;
nCurvesToRead=ReadInt(HPipe;//先读取需要读取的曲线条数,如
frmServer.CurveDataList.Clear(;
for(intj=1;j<=nCurvesToRead;j++
{
stringcurveName=ReadString(HPipe;//读取该曲线名称
intnCount=0;
nCount=ReadInt(HPipe;//读取该曲线的数据总数(数组大小)
double[]readData=newdouble[nCount];
for(inti=0;i { readData[i]=ReadDouble(HPipe;//顺序读取曲线的数据 } CCurvenewCurve=newCCurve(; newCurve.CurveName=curveName; newCurve.CurveData=readData; frmServer.CurveDataList.Add(newCurve; } } publicvoidReadTextInfo(//读取文本信息 { stringtextInfo=ReadString(HPipe;//读取该曲线名称 frmServer.ActivityRef.AppendText(textInfo+Environment.NewLine; frmServer.ActivityRef.AppendText(Environment.NewLine; } publicvoidReadData( { //readdata intflag=-1; flag=ReadInt(HPipe;//获取当前要读取的数据的信息 if(flag==0//flag==0表示读取曲线数据 { ReadCurveData(; } elseif(flag==1//flag==1表示读取文本信息 { ReadTextInfo(; } } //写字符串,由于字符串的大小不同,所以先将字符串的大小写入,然后写字符串内容,对应的, //在c++端,读字符串时先读取字符数组大小,从而给字符数组开辟相应的空间,然后读取字符串内容。 publicboolWriteString(IntPtrHPipe,stringstr { byte[]Val=System.Text.Encoding.UTF8.GetBytes(str; byte[]dwBytesWrite=newbyte[4]; intlen=Val.Length; byte[]lenBytes=System.BitConverter.GetBytes(len; if(NamedPipeNative.WriteFile(HPipe,lenBytes,4,dwBytesWrite,0 return(NamedPipeNative.WriteFile(HPipe,Val,(uintlen,dwBytesWrite,0; else returnfalse; } publicstringReadString(IntPtrHPipe { stringVal=""; byte[]bytes=ReadBytes(HPipe; if(bytes! =null { Val=System.Text.Encoding.UTF8.GetString(bytes; } returnVal; } publicbyte[]ReadBytes(IntPtrHPipe { //传字节流 byte[]szMsg=null; byte[]dwBytesRead=newbyte[4]; byte[]lenBytes=newbyte[4]; intlen; if(NamedPipeNative.ReadFile(HPipe,lenBytes,4,dwBytesRead,0//先读大小 { len=System.BitConverter.ToInt32(lenBytes,0; szMsg=newbyte[len]; if(! NamedPipeNative.ReadFile(HPipe,szMsg,(uintlen,dwBytesRead,0 { //frmServer.ActivityRef.AppendText("读取数据失败"; returnnull; } } returnszMsg; } publicfloatReadFloat(IntPtrHPipe { floatVal=0; byte[]bytes=newbyte[4];//单精度需4个字节存储 byte[]dwBytesRead=newbyte[4]; if(! NamedPipeNative.ReadFile(HPipe,bytes,4,dwBytesRead,0 { //frmServer.ActivityRef.AppendText("读取数据失败"; return0; } Val=System.BitConverter.ToSingle(bytes,0; returnVal; } publicdoubleReadDouble(IntPtrHPipe { doubleVal=0; byte[]bytes=newbyte[8];//双精度需8个字节存储 byte[]dwBytesRead=newbyte[4]; if(! NamedPipeNative.ReadFile(HPipe,bytes,8,dwBytesRead,0 { //frmServer.ActivityRef.AppendText("读取数据失败"; return0; } Val=System.BitConverter.ToDouble(bytes,0; returnVal; } publicintReadInt(IntPtrHPipe { intVal=0; byte[]bytes=newbyte[4];//整型需4个字节存储
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C# 进程 之间 通过 命名 管道 通信