window 匿名管通道编程

放个代码片段方便我以后做事


SECURITY_ATTRIBUTES sa;
HANDLE hRead,hWrite;
  
sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
sa.lpSecurityDescriptor = NULL;   //使用系统默认的安全描述符 
sa.bInheritHandle = TRUE;   //创建的进程继承句柄

if (!CreatePipe(&hRead,&hWrite,&sa,0))   //创建匿名管道
{  
   MessageBox("CreatePipe Failed!","提示",MB_OK | MB_ICONWARNING);  
   return;
}

STARTUPINFO si; 
PROCESS_INFORMATION pi;

ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO); 
GetStartupInfo(&si); 
si.hStdError = hWrite; 
si.hStdOutput = hWrite;   //新创建进程的标准输出连在写管道一端
si.wShowWindow = SW_HIDE;   //隐藏窗口 
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;


char cmdline[200]; 
CString tmp,stredit2;
GetDlgItemText(IDC_Ecmd,stredit2);   //获取编辑框中输入的命令行
tmp.Format("cmd /C %s",stredit2);
sprintf(cmdline,"%s",tmp);

if (!CreateProcess(NULL,cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi))   //创建子进程
{
   MessageBox("CreateProcess Failed!","提示",MB_OK | MB_ICONWARNING);  
   return;
}
CloseHandle(hWrite);   //关闭管道句柄

char buffer[8096] = {0};
CString strOutput;
DWORD bytesRead;
  
while (true) 
{
   if (ReadFile(hRead,buffer,4095,&bytesRead,NULL) == NULL)   //读取管道
   break;
  
   strOutput += buffer;
   SetDlgItemText(IDC_res,strOutput);   //显示输出信息到编辑框,并刷新窗口
   UpdateWindow();
   Sleep(100);
}
CloseHandle(hRead);

---------------------------------------------------------------------------------------------------------------------------------------




// 


SECURITY_ATTRIBUTES sa;
HANDLE hRead,hWrite;
  
sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
sa.lpSecurityDescriptor = NULL;   //使用系统默认的安全描述符 
sa.bInheritHandle = TRUE;   //创建的进程继承句柄


if (!CreatePipe(&hRead,&hWrite,&sa,0))   //创建匿名管道
{  
   MessageBox("CreatePipe Failed!","提示",MB_OK | MB_ICONWARNING);  
   return;
}


STARTUPINFO si; 
PROCESS_INFORMATION pi;


ZeroMemory(&si,sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO); 
GetStartupInfo(&si); 
si.hStdError = hWrite; 
si.hStdOutput = hWrite;   //新创建进程的标准输出连在写管道一端
si.wShowWindow = SW_HIDE;   //隐藏窗口 
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;




char cmdline[200]; 
CString tmp,stredit2;
GetDlgItemText(IDC_EDIT_CMD,stredit2);   //获取编辑框中输入的命令行
tmp.Format("cmd /C %s",stredit2);
sprintf(cmdline,"%s",tmp);


if (!CreateProcess(NULL,cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi))   //创建子进程
{
   MessageBox("CreateProcess Failed!","提示",MB_OK | MB_ICONWARNING);  
   return;
}
CloseHandle(hWrite);   //关闭管道句柄


char buffer[4096] = {0};
CString strOutput;
DWORD bytesRead;
  
while (true) 
{
   if (ReadFile(hRead,buffer,4095,&bytesRead,NULL) == NULL)   //读取管道
   break;
  
   strOutput += buffer;
   SetDlgItemText(IDC_EDIT_OUTPUT,strOutput);   //显示输出信息到编辑框,并刷新窗口
   UpdateWindow();
   Sleep(100);
}
CloseHandle(hRead);
--------------------------------------------------------------------------------------------------------




不知您是否用过这样的程式,他们本身并没有解压缩的功能,而是调用DOS程式PKZIP完成ZIP包的解压缩。但是在程式运行时又没有DOS控制台的窗口出现而且一切本应该在DOS下显示的信息都出现在了那个安装程式的一个文本框里。这种设计既美观又能够防止少数眼疾手快的用户提前关了您的DOS窗口。


  现在就来讨论一下,怎样用匿名管道技术实现这个功能。


  管道技术由来已久,相信不少人对DOS命令里的管道技术最为熟悉。当我们type一个文档的时候假如想让他分页现实能够输入


  C://>type autoexec.bat|more


  这里“|”就是管道操作符。他以type输出的信息为读取端,以more的输入端为写入端建立的管道。


  Windows中使用较多的管道也是匿名管道,他通过API函数CreatePipe创建。


BOOL CreatePipe(
 PHANDLE hReadPipe, // 指向读端句柄的指针
 PHANDLE hWritePipe, // 指向写端句柄的指针
 LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性结构的指针
 DWORD nSize // 管道的容量
);


  上面几个参数中要注意hReadPipe,hWritePipe是指向句柄的指针,而不是句柄(我第一次用的时候就搞错了)。nSize一般指定为0,以便让系统自己决定管道的容量。现在来看安全属性结构,SECURITY_ATTRIBUTES。


typedef struct _SECURITY_ATTRIBUTES { // sa
 DWORD nLength;
 LPVOID lpSecurityDescriptor;
 BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;


  nLength 是结构体的大小,自然是用sizeof取得了。lpSecurityDescriptor是安全描述符(一个C-Style的字符串)。 bInheritHandle他指出了安全描述的对象能否被新创建的进程继承。先不要管他们的具体意义,使用的时候自然就知道了。


  好,现在我们来创建一个管道


HANDLE hReadPipe, hWritePipe;
SECURITY_ATTRIBUTES sa;


sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL; file://使用系统默认的安全描述符
sa.bInheritHandle = TRUE; file://一定要为TRUE,不然句柄不能被继承。
CreeatePipe(&hReadPipe,&hWritePipe,&sa,0);


  我们的管道建好了。当然这不是最终目的,我们的目的是把DOS上的一个程式输出的东西重定向到一个Windows程式的Edit控件。所以我们还需要先启动一个DOS的程式,而且还不能出现DOS控制台的窗口(不然不就露馅了吗)。我们用CreateProcess创建一个DOS程式的进程。


BOOL CreateProcess(
 LPCTSTR lpApplicationName, // C-style字符串:应用程式的名称
 LPTSTR lpCommandLine, // C-style字符串:执行的命令
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全属性
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性
 BOOL bInheritHandles, // 是否继承句柄的标志
 DWORD dwCreationFlags, // 创建标志
 LPVOID lpEnvironment, // C-Style字符串:环境配置
 LPCTSTR lpCurrentDirectory, // C-Style字符串:执行目录
 LPSTARTUPINFO lpStartupInfo, // 启动信息
 LPPROCESS_INFORMATION lpProcessInformation // 进程信息
);




  先别走,参数是多了点,但是大部分要不不用自己填要不填个NULL就行了。lpApplication随便一点就行了。lpCommandLine可是您要执行的命令一定要认真写好。来,我们瞧瞧lpProcessAttributes和lpThreadAttributes怎么配置。哎?这不就是刚才那个吗。对阿,但是可比刚才简单。由于我们只是创建一个进程,他是否能在被继承不敢兴趣所以这两个值全为NULL。bInHeritHandles也是一定要配置为TRUE的,因为我们既然要让新的进程能输出信息到调用他的进程里,就必须让新的进程继承调用进程的句柄。我们对创建的新进程也没什么别的苛求,所以dwCreationFlags就为NULL了。lpEnvironment和lpCurrentDirectory根据您自己的需要是指一下就行了,一般也是NULL。接下来的lpStartupInfo可是关键,我们要认真看一下。


typedef struct _STARTUPINFO { // si
 DWORD cb;
 LPTSTR lpReserved;
 LPTSTR lpDesktop;
 LPTSTR lpTitle;
 DWORD dwX;
 DWORD dwY;
 DWORD dwXSize;
 DWORD dwYSize;
 DWORD dwXCountChars;
 DWORD dwYCountChars;
 DWORD dwFillAttribute;
 DWORD dwFlags;
 WORD wShowWindow;
 WORD cbReserved2;
 LPBYTE lpReserved2;
 HANDLE hStdInput;
 HANDLE hStdOutput;
 HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;


  这么多参数,一个一个写肯定累死了。没错,MS早就想到会累死人。所以提供救人一命的API函数GetStartupInfo。


VOID GetStartupInfo(
 LPSTARTUPINFO lpStartupInfo
);


  这个函数用来取得当前进程的StartupInfo,我们新建的进程基本根当前进程的StartupInfo差不多,就借用一下啦。然后再小小修改一下即可。


--------------------------------------------------------------------------------------------


无论是SQL Server的用户,还是PB的用户,作为C/S结构开发环境,他们在网络通信的实现上,都有一种共同的方法——命名管道。由于当前操作系统的不惟一性,各个系统都有其独自的通信协议,导致了不同系统间通信的困难。尽管TCP/IP协议目前已发展成为Internet的标准,但仍不能保证C/S应用程序的顺利进行。命名管道作为一种通信方法,有其独特的优越性,这主要表现在它不完全依赖于某一种协议,而是适用于任何协议——只要能够实现通信。




  命名管道具有很好的使用灵活性,表现在:
  1) 既可用于本地,又可用于网络。
  2) 可以通过它的名称而被引用。
  3) 支持多客户机连接。
  4) 支持双向通信。
  5) 支持异步重叠I/O操作。




  不过,当前只有Windows NT支持服务端的命名管道技术。




  一、命名管道程序设计的实现




  1.命名管道Server和Client间通信的实现流程
  (1)建立连接:服务端通过函数CreateNamedPipe创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。如果在已定义超时值变为零以前,有一个实例管道可以使用,则创建成功并返回管道句柄,并用以侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe函数实现。




  另一方面,客户端通过函数WaitNamedPipe使服务进程等待来自客户的实例连接,如果在超时值变为零以前,有一个管道可以为连接使用,则WaitNamedPipe将返回True,并通过调用CreateFile或 CallNamedPipe来呼叫对服务端的连接。此时服务端将接受客户端的连接请求,成功建立连接,服务端ConnectNamedPipe返回 True,客户端CreateFile将返回一指向管道文件的句柄。




  从时序上讲,首先是客户端通过WaitNamedPipe使服务端的CreateFile在限时时间内创建实例成功,然后双方通过ConnectNamedPipe和CreateFile成功连接,并返回用以通信的文件句柄,此时双方即可进行通信。




  (2)通信实现:建立连接之后,客户端与服务器端即可通过ReadFile和WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。




  (3)连接终止:当客户端与服务端的通信结束,或由于某种原因一方需要断开时,客户端应调用CloseFile,而服务端应接着调用 DisconnectNamedPipe。当然服务端亦可通过单方面调用DisconnectNamedPipe终止连接。最后应调用函数 CloseHandle来关闭该管道。




  2.命名管道服务器端和客户端代码实现




  (1)客户端:
  HANDLE CltHandle;
  char pipenamestr[30];
  sprintf(pipenamestr,″\\\\servername\\pipe\\pipename″)
  if (WaitNamedPipe( pipenamestr, NMPWAIT—WAIT—FOREVER)==FALSE
  // 管道名要遵循UNC,格式为\ \.\pipe\pipname,名字不分大小写。




  AfxMessageBox(″操作失败,请确定服务端正确建立管道实例!″);
  Else
  CltHandle=CreateFile(pipenamestr, GENERIC—READ|GENERIC—WRITE, FILE—SHARE—READ| FILE—SHARE—WRITE,NULL, OPEN—EXISTING,
  //为了与命名管道连接,此参数应一直为OPEN—EXISTING
  FILE—ATTRIBUTE—ARCHIVE|FILE—FLAG—WRITE—THROUGH,
  // FILE—FLAG—WRITE—THROUGH会使管道WriteFile调用处于阻塞状态,直到数据传送成功。
  NULL);
  If (CltHandle== INVALID—HANDLE—VALUE)
  AfxMessageBox(″管道连接失败″);
  Else
  DoUsertTransactInfo();
  //执行用户自定义信息交换函数——从管道读、写信息。
  ……
  (2)服务端:
  HANDLE SvrHandle;
  char pipenamestr[30];
  sprintf(pipenamestr,″\\\\.\\pipe\\pipename″)
  SvrHandle=CreateNamedPipe(pipenamestr,
  PIPE—ACCESS—DUPLEX|FILE—FLAG—WRITE—THROUGH,
  //阻塞模式,这种模式仅对″字节传输管道″操作有效。
  FILE—WAIT|PIPE—TYPE—BYTE,
  //字节模式
  PIPE—UNLIMITED—INSTANCES,
  128,128,
  NULL,NULL);
  // SECURITY—ATTRIBUTES结构指针,描述一个新管道,确定子进程的继承权,如果为NULL则该命名管道不能被继承。
  If (SvrHandle==INVALID—HANDLE—VALUE)
  AfxMessageBox(″管道创建失败,请确定客户端提供连接可能!″);
  Else
  If (ConnectNamedPipe(SvrHandle,NULL)==FALSE)
  AfxMessageBox(″建立连接失败!″);
  Else
  DoUsertTransactInfo();
  //用户自定义信息交换函数
  ……
  二、程序设计的注意事项
  1.如果命名管道客户端已打开,函数将会强迫关闭管道,用DisconnectNamedPipe关闭的管道,其客户端还必须用CloseHandle来关闭最后的管道。
  2. ReadFile和WriteFile的hFile句柄是由CreateFile及ConnectNamedPipe返回得到。
  3.一个已被某客户端连接的管道句柄在被另一客户通过ConnectNamedPipe建立连接之前,服务端必须用 DisconnectNamedPipe函数对已存在的连接进行强行拆离。服务端拆离管道会造成管道中数据的丢失,用FlushFileBuffers函数可以保证数据不被丢失。
  4.命名管道服务端可以通过新创建的管道句柄或已被连接过其他客户的管道句柄来使用 ConnectNamedPipe函数,但在连接新的客户端之前,服务端必须用函数DisconnectNamedPipe切断之前的客户句柄,否则 ConnectNamedPipe 将会返回False。
  5.阻塞模式,这种模式仅对“字节传输管道"操作有效,并且要求客户端与服务端不在同一机器上。如果用这种模式,则只有当函数通过网络向远端计算机管道缓冲器写数据成功时,才能有效返回。如果不用这种模式,系统会运行缺省方式以提高网络的工作效率。
  6.用户必须用FILE—CREATE—PIPE—INSTANCE 来访问命名管道对象。新的命名管道建立后,来自安全参数的访问控制列表定义了访问该命名管道的权限。所有命名管道实例必须使用统一的管道传输方式、管道模式等参数。客户端未启动,管道服务端不能执行阻塞读操作,否则会发生空等的阻塞状态。当最后的命名管道实例的最后一个句柄被关闭时,就应该删除该命名管道。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值