匿名管道 管道(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
王海光 閱讀(6620)
評論(0) 編輯 收藏 引用 所屬分類:
MFC