Windows系統程式設計之程序間通信
作者:北極星2003
來源:看雪論壇(www.pediy.com)
Windows 的IPC(程序間通信)機制主要是異步管道和命名管道。(至于其他的IPC方式,例如記憶體映射、郵槽等這裡就不介紹了)
管道(pipe)是用于程序間通信的共享記憶體區域。建立管道的程序稱為管道伺服器,而連接配接到這個管道的程序稱為管道用戶端。一個程序向管道寫入資訊,而另外一個程序從管道讀取資訊。
異步管道是基于字元和半雙工的(即單向),一般用于程式輸入輸出的重定向;命名管道則強大地多,它們是面向消息和全雙工的,同時還允許網絡通信,用于建立用戶端/伺服器系統。
一、異步管道(實作比較簡單,直接通過執行個體來講解)
實驗目标:目前有sample.cpp, sample.exe, sample.in這三個檔案,sample.exe為sample.cpp的執行程式,sample.cpp隻是一個簡單的程式示例(簡單求和),如下:
代碼:
[cpp] view plain copy
- #include <iostream.h>
- int main()
- {
- int a, b ;
- while ( cin >> a >> b && ( a || b ) )
- cout << a + b << endl ;
- return 0;
- }
Sample.in檔案是輸入檔案,内容:
32 433
542 657
0 0
要求根據sample.exe和它的輸入資料,把輸出資料重定向到sample.out
流程分析:實際這個實驗中包含兩個部分,把輸入資料重定向到sample.exe 和把輸出資料重定向到sample.out。在指令行下可以很簡單的實作這個功能“sample <sample.in >sample.out”,這個指令也是利用管道特性實作的,現在我們就根據異步管道的實作原理自己來實作這個功能。
管道是基于半雙工(單向)的,這裡有兩個重定向的過程,顯然需要建立兩個管道,下面給出流程圖:
異步管道實作的流程圖說明:
1)。父程序是我們需要實作的,其中需要建立管道A,管道B,和子程序,整個實作流程分為4個操作。
2)。管道A:輸入管道
3)。管道B:輸出管道
4)。操作A:把輸入檔案sample.in的資料寫入輸入管道(管道A)
5)。操作B:子程序從輸入管道中讀取資料,作為該程序的加工原料。通常,程式的輸入資料由标準的輸入裝置輸入,這裡實作輸入重定向,即把輸入管道作為輸入裝置。
6)。操作C:子程序把加工後的成品(輸出資料)輸出到輸出管道。通常,程式的輸出資料會輸出到标準的輸出裝置,一般為螢幕,這裡實作輸出重定向,即把輸出管道作為輸出裝置。
7)。操作D:把輸出管道的資料寫入輸出檔案
需要注意的是,管道的本質隻是一個共享的記憶體區域。這個實驗中,管道區域處于父程序的位址空間中,父程序的作用是提供環境和資源,并協調子程序進行加工。
程式源碼:
代碼:
[cpp] view plain copy
- #include <windows.h>
- #include <iostream.h>
- const int BUFSIZE = 4096 ;
- HANDLE hChildStdinRd, hChildStdinWr, hChildStdinWrDup,
- hChildStdoutRd,hChildStdoutWr,hChildStdoutRdDup,
- hSaveStdin, hSaveStdout;
- BOOL CreateChildProcess(LPTSTR);
- VOID WriteToPipe(LPTSTR);
- VOID ReadFromPipe(LPTSTR);
- VOID ErrorExit(LPTSTR);
- VOID ErrMsg(LPTSTR, BOOL);
- void main( int argc, char *argv[] )
- {
- // 處理輸入參數
- if ( argc != 4 )
- return ;
- // 分别用來儲存指令行,輸入檔案名(CPP/C),輸出檔案名(儲存編譯資訊)
- LPTSTR lpProgram = new char[ strlen(argv[1]) ] ;
- strcpy ( lpProgram, argv[1] ) ;
- LPTSTR lpInputFile = new char[ strlen(argv[2]) ];
- strcpy ( lpInputFile, argv[2] ) ;
- LPTSTR lpOutputFile = new char[ strlen(argv[3]) ] ;
- strcpy ( lpOutputFile, argv[3] ) ;
- SECURITY_ATTRIBUTES saAttr;
- saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
- saAttr.bInheritHandle = TRUE;
- saAttr.lpSecurityDescriptor = NULL;
- /************************************************
- * redirecting child process's STDOUT *
- ************************************************/
- hSaveStdout = GetStdHandle(STD_OUTPUT_HANDLE);
- if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
- ErrorExit("Stdout pipe creation failed/n");
- if (! SetStdHandle(STD_OUTPUT_HANDLE, hChildStdoutWr))
- ErrorExit("Redirecting STDOUT failed");
- BOOL fSuccess = DuplicateHandle(
- GetCurrentProcess(),
- hChildStdoutRd,
- GetCurrentProcess(),
- &hChildStdoutRdDup ,
- 0,
- FALSE,
- DUPLICATE_SAME_ACCESS);
- if( !fSuccess )
- ErrorExit("DuplicateHandle failed");
- CloseHandle(hChildStdoutRd);
- /************************************************
- * redirecting child process's STDIN *
- ************************************************/
- hSaveStdin = GetStdHandle(STD_INPUT_HANDLE);
- if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
- ErrorExit("Stdin pipe creation failed/n");
- if (! SetStdHandle(STD_INPUT_HANDLE, hChildStdinRd))
- ErrorExit("Redirecting Stdin failed");
- fSuccess = DuplicateHandle(
- GetCurrentProcess(),
- hChildStdinWr,
- GetCurrentProcess(),
- &hChildStdinWrDup,
- 0,
- FALSE,
- DUPLICATE_SAME_ACCESS);
- if (! fSuccess)
- ErrorExit("DuplicateHandle failed");
- CloseHandle(hChildStdinWr);
- /************************************************
- * 建立子程序(即啟動SAMPLE.EXE) *
- ************************************************/
- fSuccess = CreateChildProcess( lpProgram );
- if ( !fSuccess )
- ErrorExit("Create process failed");
- // 父程序輸入輸出流的還原設定
- if (! SetStdHandle(STD_INPUT_HANDLE, hSaveStdin))
- ErrorExit("Re-redirecting Stdin failed/n");
- if (! SetStdHandle(STD_OUTPUT_HANDLE, hSaveStdout))
- ErrorExit("Re-redirecting Stdout failed/n");
- WriteToPipe( lpInputFile ) ;
- ReadFromPipe( lpOutputFile );
- delete lpProgram ;
- delete lpInputFile ;
- delete lpOutputFile ;
- }
- BOOL CreateChildProcess( LPTSTR lpProgram )
- {
- PROCESS_INFORMATION piProcInfo;
- STARTUPINFO siStartInfo;
- BOOL bFuncRetn = FALSE;
- ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
- ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
- siStartInfo.cb = sizeof(STARTUPINFO);
- bFuncRetn = CreateProcess ( NULL, lpProgram, NULL, NULL, TRUE, /
- 0, NULL, NULL, &siStartInfo, &piProcInfo);
- if (bFuncRetn == 0)
- {
- ErrorExit("CreateProcess failed/n");
- return 0;
- }
- else
- {
- CloseHandle(piProcInfo.hProcess);
- CloseHandle(piProcInfo.hThread);
- return bFuncRetn;
- }
- }
- VOID WriteToPipe( LPTSTR lpInputFile )
- {
- HANDLE hInputFile = CreateFile(lpInputFile, GENERIC_READ, 0, NULL,
- OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
- if (hInputFile == INVALID_HANDLE_VALUE)
- return ;
- BOOL fSuccess ;
- DWORD dwRead, dwWritten;
- CHAR chBuf[BUFSIZE] = {0} ;
- for (;;)
- {
- fSuccess = ReadFile( hInputFile, chBuf, BUFSIZE, &dwRead, NULL) ;
- if ( !fSuccess || dwRead == 0)
- break;
- fSuccess = WriteFile( hChildStdinWrDup, chBuf, dwRead, &dwWritten, NULL) ;
- if ( !fSuccess )
- break;
- }
- if (! CloseHandle(hChildStdinWrDup))
- ErrorExit("Close pipe failed/n");
- CloseHandle ( hInputFile ) ;
- }
- VOID ReadFromPipe( LPTSTR lpOutputFile )
- {
- HANDLE hOutputFile = CreateFile( lpOutputFile, GENERIC_READ|GENERIC_WRITE,
- FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- if (hOutputFile == INVALID_HANDLE_VALUE)
- return ;
- BOOL fSuccess ;
- DWORD dwRead, dwWritten;
- CHAR chBuf[BUFSIZE] = { 0 };
- if (!CloseHandle(hChildStdoutWr))
- ErrorExit("Closing handle failed");
- for (;;)
- {
- fSuccess = ReadFile( hChildStdoutRdDup, chBuf, BUFSIZE, &dwRead, NULL) ;
- if( !fSuccess || dwRead == 0)
- {
- break;
- }
- fSuccess = WriteFile( hOutputFile, chBuf, dwRead, &dwWritten, NULL) ;
- if ( !fSuccess )
- break;
- }
- CloseHandle ( hOutputFile ) ;
- }
- VOID ErrorExit (LPTSTR lpszMessage)
- {
- MessageBox( 0, lpszMessage, 0, 0 );
- }
二、命名管道
命名管道具有以下幾個特征:
(1)命名管道是雙向的,是以兩個程序可以通過同一管道進行互動。
(2)命名管道不但可以面向位元組流,還可以面向消息,是以讀取程序可以讀取寫程序發送的不同長度的消息。
(3)多個獨立的管道執行個體可以用一個名稱來命名。例如幾個用戶端可以使用名稱相同的管道與同一個伺服器進行并發通信。
(4)命名管道可以用于網絡間兩個程序的通信,而其實作的過程與本地程序通信完全一緻。
實驗目标:在用戶端輸入資料a和b,然後發送到伺服器并計算a+b,然後把計算結果發送到用戶端。可以多個用戶端與同一個伺服器并行通信。
界面設計:
難點所在:
實作的過程比較簡單,但有一個難點。原本當服務端使用ConnectNamedPipe函數後,如果有用戶端連接配接,就可以直接進行互動。原來我在實作過程中,當管道空閑時,管道的線程函數會無限(INFINITE)阻塞。若現在需要停止服務,就必須結束所有的線程,TernimateThread可以作為一個結束線程的方法,但我基本不用這個函數。一旦使用這個函數之後,目标線程就會立即結束,但如果此時的目标線程正在操作互斥資源、核心調用、或者是操作共享DLL的全局變量,可能會出現互斥資源無法釋放、核心異常等現象。這裡我用重疊I/0來解決這個問題,在建立PIPE時使用FILE_FLAG_OVERLAPPED标志,這樣使用ConnectNamedPipe後會立即傳回,但線程的阻塞由等待函數WaitForSingleObject來實作,等待OVERLAPPED結構的事件對象被設定。
用戶端主要代碼:
代碼:
[cpp] view plain copy
- void CMyDlg::OnSubmit()
- {
- // 打開管道
- HANDLE hPipe = CreateFile("////.//Pipe//NamedPipe", GENERIC_READ | GENERIC_WRITE, /
- 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ;
- if ( hPipe == INVALID_HANDLE_VALUE )
- {
- this->MessageBox ( "打開管道失敗,伺服器尚未啟動,或者用戶端數量過多" ) ;
- return ;
- }
- DWORD nReadByte, nWriteByte ;
- char szBuf[1024] = {0} ;
- // 把兩個整數(a,b)格式化為字元串
- sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ;
- // 把資料寫入管道
- WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
- memset ( szBuf, 0, sizeof(szBuf) ) ;
- // 讀取伺服器的回報資訊
- ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ;
- // 把傳回資訊格式化為整數
- sscanf ( szBuf, "%d", &(this->nResValue) ) ;
- this->UpdateData ( false ) ;
- CloseHandle ( hPipe ) ;
- }
服務端主要代碼:
代碼:
[cpp] view plain copy
- // 啟動服務
- void CMyDlg::OnStart()
- {
- CString lpPipeName = "////.//Pipe//NamedPipe" ;
- for ( UINT i = 0; i < nMaxConn; i++ )
- {
- // 建立管道執行個體
- PipeInst[i].hPipe = CreateNamedPipe ( lpPipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, /
- PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, nMaxConn, 0, 0, 1000, NULL ) ;
- if ( PipeInst[i].hPipe == INVALID_HANDLE_VALUE )
- {
- DWORD dwErrorCode = GetLastError () ;
- this->MessageBox ( "建立管道錯誤!" ) ;
- return ;
- }
- // 為每個管道執行個體建立一個事件對象,用于實作重疊IO
- PipeInst[i].hEvent = CreateEvent ( NULL, false, false, false ) ;
- // 為每個管道執行個體配置設定一個線程,用于響應用戶端的請求
- PipeInst[i].hTread = AfxBeginThread ( ServerThread, &PipeInst[i], THREAD_PRIORITY_NORMAL ) ;
- }
- this->SetWindowText ( "命名管道執行個體之伺服器(運作)" ) ;
- this->MessageBox ( "服務啟動成功" ) ;
- }
- // 停止服務
- void CMyDlg::OnStop()
- {
- DWORD dwNewMode = PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_NOWAIT ;
- for ( UINT i = 0; i < nMaxConn; i++ )
- {
- SetEvent ( PipeInst[i].hEvent ) ;
- CloseHandle ( PipeInst[i].hTread ) ;
- CloseHandle ( PipeInst[i].hPipe ) ;
- }
- this->SetWindowText ( "命名管道執行個體之伺服器" ) ;
- this->MessageBox ( "停止啟動成功" ) ;
- }
- // 線程服務函數
- UINT ServerThread ( LPVOID lpParameter )
- {
- DWORD nReadByte = 0, nWriteByte = 0, dwByte = 0 ;
- char szBuf[MAX_BUFFER_SIZE] = {0} ;
- PIPE_INSTRUCT CurPipeInst = *(PIPE_INSTRUCT*)lpParameter ;
- OVERLAPPED OverLapStruct = { 0, 0, 0, 0, CurPipeInst.hEvent } ;
- while ( true )
- {
- memset ( szBuf, 0, sizeof(szBuf) ) ;
- // 命名管道的連接配接函數,等待用戶端的連接配接(隻針對NT)
- ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ;
- // 實作重疊I/0,等待OVERLAPPED結構的事件對象
- WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ;
- // 檢測I/0是否已經完成,如果未完成,意味着該事件對象是人工設定,即服務需要停止
- if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) )
- break ;
- // 從管道中讀取用戶端的請求資訊
- if ( !ReadFile ( CurPipeInst.hPipe, szBuf, MAX_BUFFER_SIZE, &nReadByte, NULL ) )
- {
- MessageBox ( 0, "讀取管道錯誤!", 0, 0 ) ;
- break ;
- }
- int a, b ;
- sscanf ( szBuf, "%d %d", &a, &b ) ;
- pMyDlg->nFirst = a ;
- pMyDlg->nSecond = b ;
- pMyDlg->nResValue = a + b ;
- memset ( szBuf, 0, sizeof(szBuf) ) ;
- sprintf ( szBuf, "%d", pMyDlg->nResValue ) ;
- // 把回報資訊寫入管道
- WriteFile ( CurPipeInst.hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
- pMyDlg->SetDlgItemInt ( IDC_FIRST, a, true ) ;
- pMyDlg->SetDlgItemInt ( IDC_SECOND, b, true ) ;
- pMyDlg->SetDlgItemInt ( IDC_RESULT, pMyDlg->nResValue, true ) ;
- // 斷開用戶端的連接配接,以便等待下一客戶的到來
- DisconnectNamedPipe ( CurPipeInst.hPipe ) ;
- }
- return 0 ;
- }
最後特别說明一下,此文章是看雪WIN32安全程式設計闆塊的斑竹北極星的,大家可以多多關注一下他的技術文章,看了幾篇都認為很不錯的。
鍊 接: http://bbs.pediy.com/showthread.php?t=26252
http://blog.csdn.net/yiruirui0507/article/details/6457806