Windows 的IPC(進(jìn)程間通信)機(jī)制主要是異步管道和命名管道。(至于其他的IPC方式,例如內(nèi)存映射、郵槽等這里就不介紹了)
管道(pipe)是用于進(jìn)程間通信的共享內(nèi)存區(qū)域。創(chuàng)建管道的進(jìn)程稱(chēng)為管道服務(wù)器,而連接到這個(gè)管道的進(jìn)程稱(chēng)為管道客戶(hù)端。一個(gè)進(jìn)程向管道寫(xiě)入信息,而另外一個(gè)進(jìn)程從管道讀取信息。
異步管道是基于字符和半雙工的(即單向),一般用于程序輸入輸出的重定向;命名管道則強(qiáng)大地多,它們是面向消息和全雙工的,同時(shí)還允許網(wǎng)絡(luò)通信,用于創(chuàng)建客戶(hù)端/服務(wù)器系統(tǒng)。
一、異步管道(實(shí)現(xiàn)比較簡(jiǎn)單,直接通過(guò)實(shí)例來(lái)講解)
實(shí)驗(yàn)?zāi)繕?biāo):當(dāng)前有sample.cpp, sample.exe, sample.in這三個(gè)文件,sample.exe為sample.cpp的執(zhí)行程序,sample.cpp只是一個(gè)簡(jiǎn)單的程序示例(簡(jiǎn)單求和),如下:
代碼:
#include <iostream.h>
int main()
{
int a, b ;
while ( cin >> a >> b && ( a || b ) )
cout << a + b << endl ;
return 0;
}
Sample.in文件是輸入文件,內(nèi)容:
32 433
542 657
0 0
要求根據(jù)sample.exe和它的輸入數(shù)據(jù),把輸出數(shù)據(jù)重定向到sample.out
流程分析:實(shí)際這個(gè)實(shí)驗(yàn)中包含兩個(gè)部分,把輸入數(shù)據(jù)重定向到sample.exe 和把輸出數(shù)據(jù)重定向到sample.out。在命令行下可以很簡(jiǎn)單的實(shí)現(xiàn)這個(gè)功能“sample <sample.in >sample.out”,這個(gè)命令也是利用管道特性實(shí)現(xiàn)的,現(xiàn)在我們就根據(jù)異步管道的實(shí)現(xiàn)原理自己來(lái)實(shí)現(xiàn)這個(gè)功能。
管道是基于半雙工(單向)的,這里有兩個(gè)重定向的過(guò)程,顯然需要?jiǎng)?chuàng)建兩個(gè)管道,下面給出流程圖:
異步管道實(shí)現(xiàn)的流程圖說(shuō)明:
1)。父進(jìn)程是我們需要實(shí)現(xiàn)的,其中需要?jiǎng)?chuàng)建管道A,管道B,和子進(jìn)程,整個(gè)實(shí)現(xiàn)流程分為4個(gè)操作。
2)。管道A:輸入管道
3)。管道B:輸出管道
4)。操作A:把輸入文件sample.in的數(shù)據(jù)寫(xiě)入輸入管道(管道A)
5)。操作B:子進(jìn)程從輸入管道中讀取數(shù)據(jù),作為該進(jìn)程的加工原料。通常,程序的輸入數(shù)據(jù)由標(biāo)準(zhǔn)的輸入設(shè)備輸入,這里實(shí)現(xiàn)輸入重定向,即把輸入管道作為輸入設(shè)備。
6)。操作C:子進(jìn)程把加工后的成品(輸出數(shù)據(jù))輸出到輸出管道。通常,程序的輸出數(shù)據(jù)會(huì)輸出到標(biāo)準(zhǔn)的輸出設(shè)備,一般為屏幕,這里實(shí)現(xiàn)輸出重定向,即把輸出管道作為輸出設(shè)備。
7)。操作D:把輸出管道的數(shù)據(jù)寫(xiě)入輸出文件
需要注意的是,管道的本質(zhì)只是一個(gè)共享的內(nèi)存區(qū)域。這個(gè)實(shí)驗(yàn)中,管道區(qū)域處于父進(jìn)程的地址空間中,父進(jìn)程的作用是提供環(huán)境和資源,并協(xié)調(diào)子進(jìn)程進(jìn)行加工。
程序源碼:
代碼:
#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[] )
{
// 處理輸入?yún)?shù)
if ( argc != 4 )
return ;
// 分別用來(lái)保存命令行,輸入文件名(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);
/************************************************
* 創(chuàng)建子進(jìn)程(即啟動(dòng)SAMPLE.EXE) *
************************************************/
fSuccess = CreateChildProcess( lpProgram );
if ( !fSuccess )
ErrorExit("Create process failed");
// 父進(jìn)程輸入輸出流的還原設(shè)置
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)命名管道不但可以面向字節(jié)流,還可以面向消息,所以讀取進(jìn)程可以讀取寫(xiě)進(jìn)程發(fā)送的不同長(zhǎng)度的消息。
(3)多個(gè)獨(dú)立的管道實(shí)例可以用一個(gè)名稱(chēng)來(lái)命名。例如幾個(gè)客戶(hù)端可以使用名稱(chēng)相同的管道與同一個(gè)服務(wù)器進(jìn)行并發(fā)通信。
(4)命名管道可以用于網(wǎng)絡(luò)間兩個(gè)進(jìn)程的通信,而其實(shí)現(xiàn)的過(guò)程與本地進(jìn)程通信完全一致。
實(shí)驗(yàn)?zāi)繕?biāo):在客戶(hù)端輸入數(shù)據(jù)a和b,然后發(fā)送到服務(wù)器并計(jì)算a+b,然后把計(jì)算結(jié)果發(fā)送到客戶(hù)端。可以多個(gè)客戶(hù)端與同一個(gè)服務(wù)器并行通信。
界面設(shè)計(jì):
難點(diǎn)所在:
實(shí)現(xiàn)的過(guò)程比較簡(jiǎn)單,但有一個(gè)難點(diǎn)。原本當(dāng)服務(wù)端使用ConnectNamedPipe函數(shù)后,如果有客戶(hù)端連接,就可以直接進(jìn)行交互。原來(lái)我在實(shí)現(xiàn)過(guò)程中,當(dāng)管道空閑時(shí),管道的線程函數(shù)會(huì)無(wú)限(INFINITE)阻塞。若現(xiàn)在需要停止服務(wù),就必須結(jié)束所有的線程,TernimateThread可以作為一個(gè)結(jié)束線程的方法,但我基本不用這個(gè)函數(shù)。一旦使用這個(gè)函數(shù)之后,目標(biāo)線程就會(huì)立即結(jié)束,但如果此時(shí)的目標(biāo)線程正在操作互斥資源、內(nèi)核調(diào)用、或者是操作共享DLL的全局變量,可能會(huì)出現(xiàn)互斥資源無(wú)法釋放、內(nèi)核異常等現(xiàn)象。這里我用重疊I/0來(lái)解決這個(gè)問(wèn)題,在創(chuàng)建PIPE時(shí)使用FILE_FLAG_OVERLAPPED標(biāo)志,這樣使用ConnectNamedPipe后會(huì)立即返回,但線程的阻塞由等待函數(shù)WaitForSingleObject來(lái)實(shí)現(xiàn),等待OVERLAPPED結(jié)構(gòu)的事件對(duì)象被設(shè)置。
客戶(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ù)端數(shù)量過(guò)多" ) ;
return ;
}
DWORD nReadByte, nWriteByte ;
char szBuf[1024] = {0} ;
// 把兩個(gè)整數(shù)(a,b)格式化為字符串
sprintf ( szBuf, "%d %d", this->nFirst, this->nSecond ) ;
// 把數(shù)據(jù)寫(xiě)入管道
WriteFile ( hPipe, szBuf, strlen(szBuf), &nWriteByte, NULL ) ;
memset ( szBuf, 0, sizeof(szBuf) ) ;
// 讀取服務(wù)器的反饋信息
ReadFile ( hPipe, szBuf, 1024, &nReadByte, NULL ) ;
// 把返回信息格式化為整數(shù)
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 < nMaxConn; i++ )
{
// 創(chuàng)建管道實(shí)例
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 ( "創(chuàng)建管道錯(cuò)誤!" ) ;
return ;
}
// 為每個(gè)管道實(shí)例創(chuàng)建一個(gè)事件對(duì)象,用于實(shí)現(xiàn)重疊IO
PipeInst[i].hEvent = CreateEvent ( NULL, false, false, false ) ;
// 為每個(gè)管道實(shí)例分配一個(gè)線程,用于響應(yīng)客戶(hù)端的請(qǐng)求
PipeInst[i].hTread = AfxBeginThread ( ServerThread, &PipeInst[i], THREAD_PRIORITY_NORMAL ) ;
}
this->SetWindowText ( "命名管道實(shí)例之服務(wù)器(運(yùn)行)" ) ;
this->MessageBox ( "服務(wù)啟動(dòng)成功" ) ;
}
// 停止服務(wù)
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 ( "命名管道實(shí)例之服務(wù)器" ) ;
this->MessageBox ( "停止啟動(dòng)成功" ) ;
}
// 線程服務(wù)函數(shù)
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) ) ;
// 命名管道的連接函數(shù),等待客戶(hù)端的連接(只針對(duì)NT)
ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ;
// 實(shí)現(xiàn)重疊I/0,等待OVERLAPPED結(jié)構(gòu)的事件對(duì)象
WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ;
// 檢測(cè)I/0是否已經(jīng)完成,如果未完成,意味著該事件對(duì)象是人工設(shè)置,即服務(wù)需要停止
if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) )
break ;
// 從管道中讀取客戶(hù)端的請(qǐng)求信息
if ( !ReadFile ( CurPipeInst.hPipe, szBuf, MAX_BUFFER_SIZE, &nReadByte, NULL ) )
{
MessageBox ( 0, "讀取管道錯(cuò)誤!", 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 ;
}