匿名管道 管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。 匿名管道(Anonymous Pipe)是在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。 匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。命名管道 命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。 命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
匿名管道的使用
匿名管道主要用于本地父进程和子进程之间的通信,
在父进程中的话,首先是要创建一个匿名管道,
在创建匿名管道成功后,可以获取到对这个匿名管道的读写句柄,
然后父进程就可以向这个匿名管道中写入数据和读取数据了,
但是如果要实现的是父子进程通信的话,那么还必须在父进程中创建一个子进程,
同时,这个子进程必须能够继承和使用父进程的一些公开的句柄,
为什么呢?
因为在子进程中必须要使用父进程创建的匿名管道的读写句柄,
通过这个匿名管道才能实现父子进程的通信,所以必须继承父进程的公开句柄。
同时在创建子进程的时候,
必须将子进程的标准输入句柄设置为父进程中创建匿名管道时得到的读管道句柄,
将子进程的标准输出句柄设置为父进程中创建匿名管道时得到的写管道句柄。
然后在子进程就可以读写匿名管道了。
匿名管道的创建
BOOL WINAPI CreatePipe(
__out PHANDLE hReadPipe,
__out PHANDLE hWritePipe,
__in LPSECURITY_ATTRIBUTES lpPipeAttributes,
__in DWORD nSize );
参数 hReadPipe 为输出参数,该句柄代表管道的读取句柄。
参数 hWritePipe 为输出参数,该句柄代表管道的写入句柄。
参数 lpPipeAttributes 为一个输入参数,指向一个 SECURITY_ATTRIBUTES 的结构体指针,
其检测返回的句柄是否能够被子进程继承,如果此参数为 NULL ,则表明句柄不能被继承,
在匿名管道中,由于匿名管道要在父子进程之间进行通信,
而子进程如果想要获得匿名管道的读写句柄,则其只能通过从父进程继承获得,
当一个子进程从其父进程处继承了匿名管道的读写句柄以后,
子进程和父进程之间就可以通过这个匿名管道的读写句柄进行通信了。
所以在这里必须构建一个 SECURITY_ATTRIBUTES 的结构体,
并且该结构体的第三个结构成员变量 bInheritHandle 参数必须设置为 TRUE ,
从而让子进程可以继承父进程所创建的匿名管道的读写句柄。
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
参数 nSize 用来指定缓冲区的大小,
如果此参数设置为 0 ,则表明系统将使用默认的缓冲区大小。一般将该参数设置为 0 即可。
子进程的创建
BOOL CreateProcess(
LPCWSTR pszImageName, LPCWSTR pszCmdLine,
LPSECURITY_ATTRIBUTES psaProcess,
LPSECURITY_ATTRIBUTES psaThread,
BOOL fInheritHandles, DWORD fdwCreate,
LPVOID pvEnvironment, LPWSTR pszCurDir,
LPSTARTUPINFOW psiStartInfo,
LPPROCESS_INFORMATION pProcInfo );
参数 pszImageName 是一个指向 NULL 终止的字符串,用来指定可执行程序的名称。
参数 pszCmdLine 用来指定传递给新进程的命令行字符串,一般做法是在 pszImageName 中传递可执行文件的名称,
在 pszCmdLine 中传递命令行参数。
参数 psaProcess 即代表当 CreateProcess 函数创建进程时,需要给进程对象设置一个安全性。
参数 psaThread 代表当 CreateProcess 函数创建新进程后,需要给该进程的主线程对象设置一个安全性。
参数 fInheritHandles 用来指定父进程随后创建的子进程是否能够继承父进程的对象句柄,
如果该参数设置为 TRUE ,则父进程的每一个可继承的打开句柄都将被子进程所继承,
继承的句柄与原始的句柄拥有同样的访问权。
在匿名管道的使用中,因为子进程需要使用父进程中创建的匿名管道的读写句柄,
所以应该将这个参数设置为 TRUE ,从而可以让子进程继承父进程创建的匿名管道的读写句柄。
参数 fdwCreate 用来指定控件优先级类和进程创建的附加标记。
如果只是为了启动子进程,则并不需要设置它创建的标记,可以将此参数设置为 0,
对于这个参数的具体取值列表可以参考 MSDN 。
参数 pvEnvironment 代表指向环境块的指针,
如果该参数设置为 NULL ,则默认将使用父进程的环境。通常给该参数传递 NULL。
参数 pszCurDir 用来指定子进程当前的路径,
这个字符串必须是一个完整的路径名,其包括驱动器的标识符,
如果此参数设置为 NULL ,那么新的子进程将与父进程拥有相同的驱动器和目录。
参数 psiStartInfo 指向一个 StartUpInfo 的结构体的指针,用来指定新进程的主窗口如何显示。
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR 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;
} STARTUPINFOA, *LPSTARTUPINFOA;
对于 dwFlags 参数来说,如果其设置为 STARTF_USESTDHANDLES ,
则将会使用该 STARTUPINFO 结构体中的 hStdInput , hStdOutput , hStdError 成员,
来设置新创建的进程的标准输入,标准输出,标准错误句柄。
参数 pProcInfo 为一个输出参数,
指向一个 PROCESS_INFORMATION 结构体的指针,用来接收关于新进程的标识信息。
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}PROCESS_INFORMATION;
其中 hProcess 和 hThread 分别用来标识新创建的进程句柄和新创建的进程的主线程句柄。
dwProcessId 和 dwThreadId 分别是全局进程标识符和全局线程标识符。
前者可以用来标识一个进程,后者用来标识一个线程。
示例代码:
父进程:
1 void CParentView::OnPipeCreate()
2 {
3 // TODO: Add your command handler code here
4 SECURITY_ATTRIBUTES sa;
5
6 sa.bInheritHandle = TRUE;
7 sa.lpSecurityDescriptor = NULL;
8 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
9
10 if(!CreatePipe(&hRead,&hWrite,&sa,0))
11 {
12 MessageBox("创建匿名管道失败!");
13 return;
14 }
15
16 STARTUPINFO sui;
17 PROCESS_INFORMATION pi;
18
19 ZeroMemory(&sui,sizeof(STARTUPINFO));
20
21 sui.cb = sizeof(STARTUPINFO);
22 sui.dwFlags = STARTF_USESTDHANDLES;
23 sui.hStdInput = hRead;
24 sui.hStdOutput = hWrite;
25 sui.hStdError = GetStdHandle(STD_ERROR_HANDLE);
26
27 if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL,TRUE,0,NULL,NULL,&sui,&pi))
28 {
29 CloseHandle(hRead);
30 CloseHandle(hWrite);
31 hRead = NULL;
32 hWrite = NULL;
33 MessageBox("创建子进程失败!");
34 return;
35 }
36 else
37 {
38 CloseHandle(pi.hProcess);
39 CloseHandle(pi.hThread);
40 }
43 }
44
45 void CParentView::OnPipeRead()
46 {
47 // TODO: Add your command handler code here
48 char buf[100];
49 DWORD dwRead;
50 if(!ReadFile(hRead,buf,100,&dwRead,NULL))
51 {
52 MessageBox("读取数据失败!");
53 return;
54 }
55 MessageBox(buf);
56 }
57
58 void CParentView::OnPipeWrite()
59 {
60 // TODO: Add your command handler code here
61 char buf[] = "http:\\www.hit.edu.cn";
62 DWORD dwWrite;
63 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
64 {
65 MessageBox("写入数据失败!");
66 return;
67 }
68 }
子进程: 1 void CChildView::OnPipeRead()
2 {
3 // TODO: Add your command handler code here
4 char buf[100];
5 DWORD dwRead;
6 if(!ReadFile(hRead,buf,100,&dwRead,NULL))
7 {
8 MessageBox("读取数据失败!");
9 return;
10 }
11 MessageBox(buf);
12 }
13
14 void CChildView::OnPipeWrite()
15 {
16 // TODO: Add your command handler code here
17 char buf[] = "匿名管道测试程序";
18 DWORD dwWrite;
19 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
20 {
21 MessageBox("写入数据失败!");
22 return;
23 }
24 }
25
26 void CChildView::OnInitialUpdate()
27 {
28 CView::OnInitialUpdate();
29
30 // TODO: Add your specialized code here and/or call the base class
31 hRead = GetStdHandle(STD_INPUT_HANDLE);
32 hWrite = GetStdHandle(STD_OUTPUT_HANDLE);
33 }
命名管道的实现:
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 void CTestServiceDlg::OnBnClickedBtncon()
2 {
3 // TODO: 在此添加控件通知处理程序代码
4 m_strPipeName = _T("\\\\.\\Pipe\\mypipe");
5 m_hPipe = CreateNamedPipeW(
6 m_strPipeName,//管道名称
7 PIPE_ACCESS_DUPLEX,
8 PIPE_TYPE_MESSAGE| //消息流类型的管道
9 PIPE_READMODE_BYTE| //消息流读模式
10 PIPE_WAIT, //阻塞模式
11 PIPE_UNLIMITED_INSTANCES, //允许最大管道实例个数
12 BUF_SIZE,//输出缓冲
13 BUF_SIZE,//输入缓冲
14 0, //客户端超时时间
15 NULL //默认的安全属性
16 );
17 if(m_hPipe == INVALID_HANDLE_VALUE)
18 {
19 AfxMessageBox(_T("创建命名管道失败!"));
20 PostQuitMessage(-8);
21 return;
22 }
23 if (ConnectNamedPipe(m_hPipe,NULL))
24 {
25 SetDlgItemText(IDC_LblRead,"");
26 GetDlgItem(IDC_BtnRead)->EnableWindow(TRUE);
27 GetDlgItem(IDC_BtnSend)->EnableWindow(TRUE);
28 AfxMessageBox("连接成功!");
29 }
30 else
31 {
32 CloseHandle(m_hPipe);
33 m_hPipe = NULL;
34 AfxMessageBox("连接失败!");
35 PostQuitMessage(-8);
36 return;
37 }
38 }
39
40 void CTestServiceDlg::OnBnClickedBtnread()
41 {
42 TCHAR buff[BUF_SIZE];
43 DWORD dwRead;
44 if (!ReadFile(m_hPipe,buff,BUF_SIZE,&dwRead,NULL))
45 {
46 CString errMsg;
47 errMsg.Format("读取发生错误:%d",GetLastError());
48 AfxMessageBox(errMsg);
49 return;
50 }
51 else
52 {
53 buff[dwRead] = '\0';//结束符
54 CString strRead(buff);
55 SetDlgItemText(IDC_LblRead, strRead);
56 }
57 }
58
59 void CTestServiceDlg::OnBnClickedBtnsend()
60 {
61 // TODO: 在此添加控件通知处理程序代码
62 UpdateData(TRUE);
63 DWORD dwWrite;
64 bool rt = WriteFile(m_hPipe,
65 m_TxtSend,(m_TxtSend.GetLength()+1)*sizeof(TCHAR),
66 &dwWrite,NULL);
67 if(!rt)
68 {
69 CString errMsg;
70 errMsg.Format("写入文件失败,错误信息%d",::GetLastError());
71 AfxMessageBox(errMsg);
72 return;
73 }
74 else
75 {
76 SetDlgItemText(IDC_LblRead,"发送成功!");
77 m_TxtSend = L"";
78 UpdateData(FALSE);
79 }
80 }
客户端: 1 void CTestClientDlg::OnBnClickedBtnconnect()
2 {
3 // TODO: 在此添加控件通知处理程序代码
4 //查询是否有管道实例可用
5 m_strPipeName = _T("\\\\.\\Pipe\\mypipe");
6 if(!WaitNamedPipe(m_strPipeName,NMPWAIT_USE_DEFAULT_WAIT))
7 {
8 AfxMessageBox(_T("连接到服务器端失败!"));
9 return;
10 }
11 hPipe = CreateFileW(
12 m_strPipeName, //管道名称
13 GENERIC_READ| //以可读写模式打开
14 GENERIC_WRITE,
15 0, //不支持共享
16 NULL, //默认安全属性
17 OPEN_EXISTING, //只打开已存在的管道
18 0,
19 NULL);
20 if(hPipe != INVALID_HANDLE_VALUE)
21 {
22 GetDlgItem(IDC_BtnConnect)->EnableWindow(FALSE);
23 GetDlgItem(IDC_BtnRec)->EnableWindow(TRUE);
24 GetDlgItem(IDC_BtnSend)->EnableWindow(TRUE);
25 ///GetDlgItem(idc)
26 }
27 if(GetLastError()!= ERROR_PIPE_BUSY)//如果连接不是忙碌
28 {
29 AfxMessageBox(L"连接到服务端成功!");
30 return;
31 }
32 }
33
34 void CTestClientDlg::OnBnClickedBtnsend()
35 {
36 // TODO: 在此添加控件通知处理程序代码
37 UpdateData(TRUE);
38 DWORD dwWrite;
39 if (!WriteFile(hPipe,m_Send,(m_strPipeName.GetLength()+1)*sizeof(TCHAR),&dwWrite,NULL))
40 {
41 CString errMsg;
42 errMsg.Format(L"发送失败:%d",GetLastError());
43 return;
44 }
45 m_Rec = L"发送成功!";
46 UpdateData(TRUE);
47 }
48
49 void CTestClientDlg::OnBnClickedBtnrec()
50 {
51 // TODO: 在此添加控件通知处理程序代码
52 TCHAR buff[BUF_SIZE];
53 DWORD dwRead;
54 if(!ReadFile(hPipe,buff,BUF_SIZE,&dwRead,NULL))
55 {
56 CString errMsg;
57 errMsg.Format(L"读取失败,错误信息%d",GetLastError());
58 return;
59 }
60 else
61 {
62 buff[dwRead] = _T('\0');
63 CStringW strRead(buff);
64 m_Rec = strRead;
65 UpdateData(FALSE);
66 }
67 }
命名管道程序设计的注意事项 :
1.如果命名管道客户端已打开,函数将会强迫关闭管道,用DisconnectNamedPipe关闭 的管道,其客户端还必须用CloseHandle来关闭最后的管道。
2. ReadFile和WriteFile的hFile句柄是由CreateFile及ConnectNamedPipe返回得到。
3.一个已被某客户端连接的管道句柄在被另一客户通过ConnectNamedPipe建立连接之前,服务端必须用DisconnectNamedPipe函数对已存在的连接进行强行拆离。服务端拆离管道会造成管道中数据的丢失,用FlushFileBuffers函数可以保证数据不被丢失。
4.命名管道服务端可以通过新创建的管道句柄或已被连接过其他客户的管道句 柄来使用ConnectNamedPipe函数,但在连接新的客户端之前,服务端必须用函数 DisconnectNamedPipe切断之前的客户句柄,否则ConnectNamedPipe 将会返回False。
5.阻塞模式,这种模式仅对“字节传输管道"操作有效,并且要求客户端与服务端不 在同一机器上。如果用这种模式,则只有当函数通过网络向远端计算机管道缓冲器写数 据成功时,才能有效返回。如果不用这种模式,系统会运行缺省方式以提高网络的工作效率。
6.用户必须用FILE—CREATE—PIPE—INSTANCE 来访问命名管道对象。新的命名管 道建立后,来自安全参数的访问控制列表定义了访问该命名管道的权限。所有命名管道 实例必须使用统一的管道传输方式、管道模式等参数。客户端未启动,管道服务端不能 执行阻塞读操作,否则会发生空等的阻塞状态。当最后的命名管道实例的最后一个句柄 被关闭时,就应该删除该命名管道。
匿名管道示例 命名管道示例
本文相关链接:http://www.cnblogs.com/BoyXiao/archive/2011/01/01/1923828.html http://bbs.csdn.net/topics/300267497 http://wenku.baidu.com/view/09ffacd43186bceb19e8bb39.html
posted on 2012-11-20 16:35
王海光 阅读(6572)
评论(0) 编辑 收藏 引用 所属分类:
MFC