Windows系統編程之進(jìn)程間通信 作者:北極星2003 來(lái)源:看雪論壇(www.pediy.com) Windows 的IPC(進(jìn)程間通信)機制主要是異步管道和命名管道。(至于其他的IPC方式,例如內存映射、郵槽等這里就不介紹了) 管道(pipe)是用于進(jìn)程間通信的共享內存區域。創(chuàng )建管道的進(jìn)程稱(chēng)為管道服務(wù)器,而連接到這個(gè)管道的進(jìn)程稱(chēng)為管道客戶(hù)端。一個(gè)進(jìn)程向管道寫(xiě)入信息,而另外一個(gè)進(jìn)程從管道讀取信息。 異步管道是基于字符和半雙工的(即單向),一般用于程序輸入輸出的重定向;命名管道則強大地多,它們是面向消息和全雙工的,同時(shí)還允許網(wǎng)絡(luò )通信,用于創(chuàng )建客戶(hù)端/服務(wù)器系統。 一、異步管道(實(shí)現比較簡(jiǎn)單,直接通過(guò)實(shí)例來(lái)講解) 實(shí)驗目標:當前有sample.cpp, sample.exe, sample.in這三個(gè)文件,sample.exe為sample.cpp的執行程序,sample.cpp只是一個(gè)簡(jiǎn)單的程序示例(簡(jiǎn)單求和),如下: 代碼: #include 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 流程分析:實(shí)際這個(gè)實(shí)驗中包含兩個(gè)部分,把輸入數據重定向到sample.exe 和把輸出數據重定向到sample.out。在命令行下可以很簡(jiǎn)單的實(shí)現這個(gè)功能“sample <sample.in >sample.out”,這個(gè)命令也是利用管道特性實(shí)現的,現在我們就根據異步管道的實(shí)現原理自己來(lái)實(shí)現這個(gè)功能。 管道是基于半雙工(單向)的,這里有兩個(gè)重定向的過(guò)程,顯然需要創(chuàng )建兩個(gè)管道,下面給出流程圖: 異步管道實(shí)現的流程圖說(shuō)明: 1)。父進(jìn)程是我們需要實(shí)現的,其中需要創(chuàng )建管道A,管道B,和子進(jìn)程,整個(gè)實(shí)現流程分為4個(gè)操作。 2)。管道A:輸入管道 3)。管道B:輸出管道 4)。操作A:把輸入文件sample.in的數據寫(xiě)入輸入管道(管道A) 5)。操作B:子進(jìn)程從輸入管道中讀取數據,作為該進(jìn)程的加工原料。通常,程序的輸入數據由標準的輸入設備輸入,這里實(shí)現輸入重定向,即把輸入管道作為輸入設備。 6)。操作C:子進(jìn)程把加工后的成品(輸出數據)輸出到輸出管道。通常,程序的輸出數據會(huì )輸出到標準的輸出設備,一般為屏幕,這里實(shí)現輸出重定向,即把輸出管道作為輸出設備。 7)。操作D:把輸出管道的數據寫(xiě)入輸出文件 需要注意的是,管道的本質(zhì)只是一個(gè)共享的內存區域。這個(gè)實(shí)驗中,管道區域處于父進(jìn)程的地址空間中,父進(jìn)程的作用是提供環(huán)境和資源,并協(xié)調子進(jìn)程進(jìn)行加工。 程序源碼: 代碼: #include #include 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 ; // 分別用來(lái)保存命令行,輸入文件名(CPP/C),輸出文件名(保存編譯信息) LPTSTR lpProgram = new char[ sizeof(argv[1]) ] ; strcpy ( lpProgram, argv[1] ) ; LPTSTR lpInputFile = new char[ sizeof(argv[2]) ]; strcpy ( lpInputFile, argv[2] ) ; LPTSTR lpOutputFile = new char[ sizeof(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); /************************************************ * 創(chuàng )建子進(jìn)程(即啟動(dòng)SAMPLE.EXE) * ************************************************/ fSuccess = CreateChildProcess( lpProgram ); if ( !fSuccess ) ErrorExit("Create process failed"); // 父進(jìn)程輸入輸出流的還原設置 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 ); }
二、命名管道 命名管道具有以下幾個(gè)特征: (1)命名管道是雙向的,所以?xún)蓚€(gè)進(jìn)程可以通過(guò)同一管道進(jìn)行交互。 (2)命名管道不但可以面向字節流,還可以面向消息,所以讀取進(jìn)程可以讀取寫(xiě)進(jìn)程發(fā)送的不同長(cháng)度的消息。 (3)多個(gè)獨立的管道實(shí)例可以用一個(gè)名稱(chēng)來(lái)命名。例如幾個(gè)客戶(hù)端可以使用名稱(chēng)相同的管道與同一個(gè)服務(wù)器進(jìn)行并發(fā)通信。 (4)命名管道可以用于網(wǎng)絡(luò )間兩個(gè)進(jìn)程的通信,而其實(shí)現的過(guò)程與本地進(jìn)程通信完全一致。 實(shí)驗目標:在客戶(hù)端輸入數據a和b,然后發(fā)送到服務(wù)器并計算a+b,然后把計算結果發(fā)送到客戶(hù)端??梢远鄠€(gè)客戶(hù)端與同一個(gè)服務(wù)器并行通信。 界面設計: 難點(diǎn)所在: 實(shí)現的過(guò)程比較簡(jiǎn)單,但有一個(gè)難點(diǎn)。原本當服務(wù)端使用ConnectNamedPipe函數后,如果有客戶(hù)端連接,就可以直接進(jìn)行交互。原來(lái)我在實(shí)現過(guò)程中,當管道空閑時(shí),管道的線(xiàn)程函數會(huì )無(wú)限(INFINITE)阻塞。若現在需要停止服務(wù),就必須結束所有的線(xiàn)程,TernimateThread可以作為一個(gè)結束線(xiàn)程的方法,但我基本不用這個(gè)函數。一旦使用這個(gè)函數之后,目標線(xiàn)程就會(huì )立即結束,但如果此時(shí)的目標線(xiàn)程正在操作互斥資源、內核調用、或者是操作共享DLL的全局變量,可能會(huì )出現互斥資源無(wú)法釋放、內核異常等現象。這里我用重疊I/0來(lái)解決這個(gè)問(wèn)題,在創(chuàng )建PIPE時(shí)使用FILE_FLAG_OVERLAPPED標志,這樣使用ConnectNamedPipe后會(huì )立即返回,但線(xiàn)程的阻塞由等待函數WaitForSingleObject來(lái)實(shí)現,等待OVERLAPPED結構的事件對象被設置。 客戶(hù)端主要代碼: 代碼: void CMyDlg::OnSubmit() { // 打開(kāi)管道 HANDLE hPipe = CreateFile("\\\\.\\Pipe\\NamedPipe", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ; if ( hPipe == INVALID_HANDLE_VALUE ) { this->MessageBox ( "打開(kāi)管道失敗,服務(wù)器尚未啟動(dòng),或者客戶(hù)端數量過(guò)多" ) ; return ; } DWORD nReadByte, nWriteByte ; char szBuf[1024] = {0} ; // 把兩個(gè)整數(a,b)格式化為字符串 sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ; // 把數據寫(xiě)入管道 WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ; memset ( szBuf, 0, sizeof(szBuf) ) ; // 讀取服務(wù)器的反饋信息 ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ; // 把返回信息格式化為整數 sscanf ( szBuf, "%d", &(this->nResValue) ) ; this->UpdateData ( false ) ; CloseHandle ( hPipe ) ; }
服務(wù)端主要代碼: 代碼: // 啟動(dòng)服務(wù) void CMyDlg::OnStart() { CString lpPipeName = "\\\\.\\Pipe\\NamedPipe" ; for ( UINT i = 0; i MessageBox ( "創(chuàng )建管道錯誤!" ) ; return ; } // 為每個(gè)管道實(shí)例創(chuàng )建一個(gè)事件對象,用于實(shí)現重疊IO PipeInst[i].hEvent = CreateEvent ( NULL, false, false, false ) ; // 為每個(gè)管道實(shí)例分配一個(gè)線(xiàn)程,用于響應客戶(hù)端的請求 PipeInst[i].hTread = AfxBeginThread ( ServerThread, &PipeInst[i], THREAD_PRIORITY_NORMAL ) ; } this->SetWindowText ( "命名管道實(shí)例之服務(wù)器(運行)" ) ; this->MessageBox ( "服務(wù)啟動(dòng)成功" ) ; } // 停止服務(wù) void CMyDlg::OnStop() { DWORD dwNewMode = PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_NOWAIT ; for ( UINT i = 0; i SetWindowText ( "命名管道實(shí)例之服務(wù)器" ) ; this->MessageBox ( "停止啟動(dòng)成功" ) ; } // 線(xiàn)程服務(wù)函數 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) ) ; // 命名管道的連接函數,等待客戶(hù)端的連接(只針對NT) ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ; // 實(shí)現重疊I/0,等待OVERLAPPED結構的事件對象 WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ; // 檢測I/0是否已經(jīng)完成,如果未完成,意味著(zhù)該事件對象是人工設置,即服務(wù)需要停止 if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) ) break ; // 從管道中讀取客戶(hù)端的請求信息 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 ) ; // 把反饋信息寫(xiě)入管道 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 ) ; // 斷開(kāi)客戶(hù)端的連接,以便等待下一客戶(hù)的到來(lái) DisconnectNamedPipe ( CurPipeInst.hPipe ) ; } return 0 ; }
HOHO....學(xué)完之后提點(diǎn)建議
LPTSTR lpProgram = new char( sizeof(argv[1]) ) ; strcpy ( lpProgram, argv[1] ) ; LPTSTR lpInputFile = new char( sizeof(argv[2]) ) ; strcpy ( lpInputFile, argv[2] ) ; LPTSTR lpOutputFile = new char( sizeof(argv[3]) ) ; strcpy ( lpOutputFile, argv[3] ) ;
這段內存沒(méi)有被釋放,另外這個(gè)寫(xiě)法好像也有點(diǎn)問(wèn)題(雖然可以正常運行),不知這樣改一下如何: LPTSTR lpProgram = new char[ sizeof(argv[1]) ] ; strcpy ( lpProgram, argv[1] ) ; LPTSTR lpInputFile = new char[ sizeof(argv[2]) ] ; strcpy ( lpInputFile, argv[2] ) ; LPTSTR lpOutputFile = new char[ sizeof(argv[3]) ] ; strcpy ( lpOutputFile, argv[3] ) ;
吹毛求疵之舉,兄弟不要見(jiàn)怪 |