目錄
(本章節中例子都是用 VS2005 編譯調試的)
進程還可以通過套接字進行通信
郵槽
通信流程:
- 服務器
- 客戶端
注意:
- 郵槽是基於廣播通信體系設計出來的,它采用無連接的不可靠的數據傳輸
- 郵槽可以實現一對多的單向通信,我們可以利用這個特點編寫一個網絡會議通知系統,而且實現這一的系統所需要編寫的代碼非常少.如果讀者是項目經理,就可以給你手下每一位員工的機器上安裝上這個系統中的郵槽服務器端程序,在你自己的機器上安裝油槽的客戶端程序,這樣,當你想通知員工開會,就可以通過自己安裝的郵槽客戶端程序.將開會這個消息發送出去,因為機器上都安裝了郵槽服務器端的程序,所以他們都能同時收到你發出的會議通知.采用郵槽實現這一的程序非常簡單的,如果采用Sockets來實現這一的通信,代碼會比較復雜
- 郵槽是一種單向通信機制,創建郵槽的服務器進程只能讀取數據,打開郵槽的客戶機進程只能寫入數據
- 為保證郵槽在各種Windows平台下都能夠正常工作,我們傳輸消息的時候,應將消息的長度限制在424字節以下
CreateMailslot函數詳解
函數原型:
HANDLE CreateMailslot( LPCTSTR lpName, // mailslot name DWORD nMaxMessageSize, // maximum message size DWORD lReadTimeout, // read time-out interval LPSECURITY_ATTRIBUTES lpSecurityAttributes // inheritance option );
參數說明:
- lpName
指向一個空終止字符串的指針,該字符串指定了油槽的名稱,該名稱的格式必須是:"\\.\mailslot\[path]name ",其中前兩個反斜杠之后的字符表示服務器所在機器的名稱,圓點表示是主機;接着是硬編碼的字符串:"mailslot",這個字符串不能改變,但大小寫無所謂;最后是油槽的名稱([path]name)由程序員起名 - nMaxMessageSize
用來指定可以被寫入到油槽的單一消息的最大尺寸,為了可以發送任意大小的消息,卡伊將該參數設置為0 - lReadTimeout
指定讀寫操作的超時間間隔,以ms為單位,讀取操作在超時之前可以等待一個消息被寫入到這個油槽之中.
- 如果這個值設置為0,那么若沒有消息可用,該函數立即返回;
- 如果這個值設置為MAILSOT_WAIT_FOREVER,則函數一直等待,直到有消息可用
- lpSecurityAttributes
指向一個SECURITY_ATTRIBUTES結構的指針,可以簡單地給這個參數傳遞NULL值,讓系統為所創建的油槽賦予默認的安全描述符
代碼樣例:
服務器端源碼:

#include<windows.h> #include<cstdlib> #include<iostream> using namespace std; void main() { HANDLE hMailslot; char buf[100]; DWORD dwRead; //創建郵槽 hMailslot=CreateMailslot("\\\\.\\mailslot\\Communication",0, MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { cout<<"創建郵槽失敗!"<<endl; system("pause"); return; } //等待用戶寫入數據然后讀取出數據 if(!ReadFile(hMailslot,buf,100,&dwRead,NULL)) { cout<<"讀取數據失敗!"<<endl; CloseHandle(hMailslot); system("pause"); return; } cout<<buf<<endl; //關閉郵槽 CloseHandle(hMailslot); system("pause"); }
客戶端源碼:

#include<windows.h> #include<cstdlib> #include<iostream> using namespace std; void main() { HANDLE hMailslot; char buf[]="this is message"; //打開郵槽 hMailslot=CreateFile("\\\\.\\mailslot\\Communication",GENERIC_WRITE, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hMailslot) { cout<<"打開郵槽失敗!"<<endl; system("pause"); return; } //向郵槽寫數據 DWORD dwWrite; if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL)) { cout<<"寫入數據失敗!"<<endl; CloseHandle(hMailslot); system("pause"); return; } //關閉郵槽 CloseHandle(hMailslot); system("pause"); }
運行結果(先運行服務器端程序,然后在運行客戶端程序):
匿名管道
說明:
匿名管道是一個未命名的,單向管道,通常用來在一個父進程和一個子進程之間傳輸數據,匿名管道只能實現在本機上的兩個進程通信,而不能實現跨網絡的通信
通信過程
- 父進程讀寫數據:
- 子進程讀寫數據:
相關函數
CreatePipe 管道創建
函數原型
BOOL CreatePipe( PHANDLE hReadPipe, // pointer to read handle PHANDLE hWritePipe, // pointer to write handle LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes DWORD nSize // pipe size );
參數說明:
- hReadPipe 作為返回類型使用,返回管道讀取句柄
- hWritePipe 作為返回類型使用,返回管道寫入句柄
- lpPipeAttributes 指向SECURITY_ATTRIBUTES結構體的指針,檢測返回句柄是否能被子進程繼承,如果此參數為NULL,則句柄不能被繼承
- nSize 指定管道的緩沖區大小,改大小只是個建議值,系統將用這個值來計算一個適當的緩存區大小,如果此參數是0,系統會使用默認的緩沖區大小
返回值
若函數成功返回非零值
若函數失敗返回0,詳細消息可以調用GetLastError函數獲得
代碼樣例:
工程目錄結構:
匿名管道
|-- child
| `-- debug
| `-- child.exe
` -- parent
`-- debug
`-- parent.exe
父進程源碼:

#include<windows.h> #include<iostream> #include<cstdlib> using namespace std; void main() { SECURITY_ATTRIBUTES sa; STARTUPINFO sui; PROCESS_INFORMATION pi; HANDLE hRead,hWrite; char rebuf[100]; DWORD dwRead; //創建匿名管道 sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL; sa.nLength=sizeof(SECURITY_ATTRIBUTES); if(!CreatePipe(&hRead,&hWrite,&sa,0)) { cout<<"創建匿名管道失敗!"<<endl; system("pause"); return; } //創建子進程並對相關子進程相關數據進行初始化 (用匿名管道的讀取寫入句柄賦予子進程的輸入輸出句柄) ZeroMemory(&sui,sizeof(STARTUPINFO)); sui.cb=sizeof(STARTUPINFO); sui.dwFlags=STARTF_USESTDHANDLES; sui.hStdInput=hRead; sui.hStdOutput=hWrite; sui.hStdError=GetStdHandle(STD_ERROR_HANDLE); if(!CreateProcess("..\\..\\child\\debug\\child.exe",NULL,NULL,NULL,true,CREATE_NEW_CONSOLE,NULL,NULL,&sui,&pi)) { cout<<"創建子進程失敗!"<<endl; system("pause"); return; } else { //關閉子進程相關句柄(進行句柄,進程主線程句柄) CloseHandle(pi.hProcess); CloseHandle(pi.hThread); Sleep(2000); //讀取數據 if(!ReadFile(hRead,rebuf,100,&dwRead,NULL)) { cout<<"讀取數據失敗!"<<endl; system("pause"); return; } cout<<rebuf<<endl; } system("pause"); }
子進程源碼:

#include<windows.h> #include<iostream> #include<cstdlib> using namespace std; void main() { HANDLE hRead,hWrite; //獲得匿名管道輸入輸出句柄 hRead=GetStdHandle(STD_INPUT_HANDLE); hWrite=GetStdHandle(STD_OUTPUT_HANDLE); char sebuf[]=" 子進程寫入管道成功"; char rebuf[100]; DWORD dwWrite; //寫入數據 if(!WriteFile(hWrite,sebuf,strlen(sebuf)+1,&dwWrite,NULL)) { cout<<"寫入數據失敗!"<<endl; system("pause"); return; } Sleep(500); system("pause"); }
命名管道
作用
命名管道不僅可以實現在本機上兩個進程間的通信,還可以跨網絡實現兩個進程間的通信
兩種通信模式
- 字節模式: 在字節模式下,數據以一個連續的字節流的形式在客戶機和服務器之間流動
- 消息模式: 在消息模式下,客戶機和服務器則通過一系列不連續的數據單位,進行數據收發,每次在管道上發一條消息后,它必須作為一條完整的消息讀入
通信流程
- 服務器
- 客戶端

[CreateNamePine 創建命名管道][ConnectNamePipe 創建連接命名管道][WaitNamedPipe 進行命名管道連接]
函數原型:
HANDLE CreateNamedPipe( LPCTSTR lpName, // pipe name DWORD dwOpenMode, // pipe open mode DWORD dwPipeMode, // pipe-specific modes DWORD nMaxInstances, // maximum number of instances DWORD nOutBufferSize, // output buffer size DWORD nInBufferSize, // input buffer size DWORD nDefaultTimeOut, // time-out interval LPSECURITY_ATTRIBUTES lpSecurityAttributes // SD );
參數說明:
- lpName
一個指向空終止的字符串,該字符串的格式必須是:"\\.\pine\pinename"其中該字符串開始是兩個連續的反斜杠,其后的原點表示是本地機器,如果想要與遠程的服務器建立連接連接,那么在原點這個位置應該指定這個遠程服務器的名稱.接下來是"pine"這個固定的字符串,也就是說這個字符串的內容不能修改,但其大小寫是無所謂的,最后是所創建的命名管道的名稱
- dwOpenMode
指定管道的訪問方式,重疊方式.寫直通方式,還有管道句柄的安全訪問方式()
用來指定管道的訪問方式的標志取值如下(下面這三個值只能夠取其中一個),並且管道的每一個實例都必須具有同樣的訪問方式-
- PIPE_ACCESS_INBOUND 管道只能用作接收數據(服務器只能讀數據,客戶端只能寫數據),相當於在CreateFile中指定了GENERIC_READ
- PIPE_ACCESS_OUTBOUND 管道只能用作發送數據(服務器只能寫數據,客戶端只能讀數據),相當於在CreateFile中指定了GENERIC_WRITE
- PIPE_ACCESS_DUPLEX 管道既可以發送也可以接收數據,相當於在CreateFile中指定了GENERIC_READ | GENERIC_WRITE
用來指定寫直通方式和重疊方式的標志,取值可以是一下一個或多個組合
-
- FILE_FLAG_WRITE_THROUGH 管道用於同步發送和接收數據,只有在數據被發送到目標地址時發送函數才會返回,如果不設置這個參數那么在系統內部對於命名管道的處理上可能會因為減少網絡附和而在數據積累到一定量時才發送,並且對於發送函數的調用會馬上返回
- FILE_FLAG_OVERLAPPED 管道可以用於異步輸入和輸出,異步讀寫的有關方法和文件異步讀寫是相同的
用來指定管道安全訪問方式的標志,取值可以是一下一個或多個組合
-
- WRITE_DAC 調用者對命名管道的任意范圍控制列表(ACL)都可以進行寫入訪問
- WRITE_OWNER 調用者對命名管道的所有者可以進行寫入訪問
- ACCESS_SYSTEM_SECURITY 調用者對命名管道打安全范圍控制列表(SACL)可以進行寫入訪問
-
- dwPipeMode
指定管道類型,,讀取和等待方式可以是下面值的組合(0為字節寫字節讀阻塞方式)
用於指定管道句柄的寫入的標志-
- PIPE_TYPE_BYTE 數據在通過管道發送時作為字節流發送,不能與PIPE_READMODE_MESSAGE共用
- PIPE_TYPE_MESSAGE 數據在通過管道發送時作為消息發送,不能與PIPE_READMODE_BYTE共用
用於指定管道句柄的讀取方式
-
- PIPE_READMODE_BYTE 在接收數據時接收字節流該方式在PIPE_TYPE_BYTE和PIPE_TYPE_MESSAGE類型均可以使用
- PIPE_READMODE_MESSAGE 在接收數據時接收消息該方式只用在PIPE_TYPE_MESSAGE類型下才可以使用
用於指定管道句柄的等待方式(同一管道的不同實例可以采取不同的等待方式)
-
- PIPE_WAIT 使用等待模式(阻塞方式),在讀,寫和建立連接時都需要管道的另一方完成相應動作后才會返回
- PIPE_NOWAIT 使用非等待模式(非阻塞方式),在讀,寫和建立連接時不需要管道的另一方完成相應動作后就會立即返回
-
- nMaxInstances
為管道的的最大數量,在第一次建立服務器方管道時這個參數表明該管道可以同時存在的數量。PIPE_UNLIMITED_INSTANCES表明不對數量進行限制
- nOutBufferSize
表示輸出緩沖區的大小
- nInBufferSize
表示輸入緩沖區的大小
- nDefaultTimeOut
表示在等待連接時最長的等待時間(以毫秒為單位),如果在創建時設置為NMPWAIT_USE_DEFAULT_WAIT表明無限制的等待,而以后服務器方的其他管道實例也需要設置相同的值
- lpSecurityAttributes
為安全屬性,一般設置為NULL。如果創建或打開失敗則返回INVALID_HANDLE_VALUE。可以通過GetLastError得到錯誤
返回值
- 若函數成功,返回值是一個命名通道實例的句柄,如果命名通道已經存在則返回一個以存在的命名通道的句柄,並調用GetLastError函數的返回值為 ERROR_ALREADY_EXISTS
- 若函數失敗,返回值為INVALID_HANDLE_VALUE若想獲得更多信息調用GetLastError函數獲得
函數原型
BOOL ConnectNamedPipe( HANDLE hNamedPipe, // handle to named pipe LPOVERLAPPED lpOverlapped // overlapped structure );
參數說明:
- lpNamedPipe : 指向一個命名管道實例的服務的句柄,該句柄由CreateNamedPipe函數返回
- lpOverlapped: 指向一個OVERLAPPED結構的指針,如果hNamedPipe參數所標識的管道是用FILE_FLAG_OVERLAPPED標志打開的,則這個參數不能是NULL,必須是一個有效的指向一個OVERLAPPED的結構指針;否則函數則會錯誤的執行.如果hNampdPipe參數標志的管道用FILE_FLAG_OVERLAPPED標志打開的,並且這個參數不是NULL,則這個OVERLAPPED結構體必須包含人工重置對象句柄.
返回值
如果函數成功返回非零值如果失敗返回0詳細消息可以調用GetLastError函數獲得
函數原型
BOOL WaitNamedPipe( LPCTSTR lpNamedPipeName, // pipe name DWORD nTimeOut // time-out interval );
參數說明:
- lpNamedPipeName
用來指定管道的名稱,這個名稱必須包括創建該命名管道的服務器進程所在的機器的名稱,該名稱的格式必須是"\\.\pine\pinename".如果在同一台機器上編寫的命名管道的服務器端程序和客戶端程序,則應該指定這個名稱時,在開始的兩個反斜桿后可以設置一個圓點,表示服務器進程在本地機器上運行;如果是跨網絡通信,則在這個圓點位置處應該指定服務器端程序所在的主機名
- nTimeOut
指定超時間隔.
-
- NMPWAIT_USE_DEFAULT_WAIT 超時間隔就是服務器端創建該命名管道時指定的超時值
- NWPWAIT_WAIT_FOREVER 一直等待,直到出現了一個可用的命名管道的實例
也就是說,如果這個參數的值是NMPWAIT_USE_DEFAULT_WAIT,並且在服務器端調用CreateNamedPipe函數創建命名管道時,設置的超時間隔為1000ms,那么一個命名管道的所有實例來說,它們必須使用同樣的超時間隔
-
返回值
如果函數成功返回非零值如果失敗返回0詳細消息可以調用GetLastError函數獲得
服務器:

#include<windows.h> #include<cstdlib> #include<iostream> using namespace std; void main() { HANDLE hPipe,hEvent;; DWORD dwRead,dwWrite; OVERLAPPED ovlap; char sebuf[]="this is sever!"; char rebuf[100]; /*創建命名連接*****************************************************/ hPipe=CreateNamedPipe("\\\\.\\pipe\\Communication", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0,1,1024,1024,0,NULL); if(INVALID_HANDLE_VALUE==hPipe) { cout<<"創建命名管道失敗!"<<endl; hPipe=NULL; system("pause"); return; } /*創建命名管道連接*************************************************/ hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { cout<<"創建事件對象失敗!"<<endl; CloseHandle(hPipe); hPipe=NULL; system("pause"); return; } ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent=hEvent; //創建管道連接 if(!ConnectNamedPipe(hPipe,&ovlap)) { if(ERROR_IO_PENDING!=GetLastError()) { cout<<"等待客戶端連接失敗!"<<endl; CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; system("pause"); return; } } //等待客戶端連接 if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { cout<<"等待對象失敗!"<<endl; CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; system("pause"); return; } CloseHandle(hEvent); /*讀寫管道數據*****************************************************/ //寫入數據 if(!ReadFile(hPipe,rebuf,100,&dwRead,NULL)) { cout<<"讀取數據失敗!"<<endl; system("pause"); return; } cout<<rebuf<<endl; //寫入數據 if(!WriteFile(hPipe,sebuf,strlen(sebuf)+1,&dwWrite,NULL)) { cout<<"寫入數據失敗!"<<endl; system("pause"); return; } system("pause"); }
客戶端:

#include<windows.h> #include<cstdlib> #include<iostream> using namespace std; void main() { HANDLE hPipe; DWORD dwRead,dwWrite; char sebuf[]="this is client!"; char rebuf[100]; /*連接管道連接*****************************************************/ if(!WaitNamedPipe("\\\\.\\pipe\\Communication",NMPWAIT_WAIT_FOREVER)) { cout<<"當前沒有可利用的命名管道實例!"<<endl; system("pause"); return; } /*打開管道連接*****************************************************/ hPipe=CreateFile("\\\\.\\pipe\\Communication",GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hPipe) { cout<<"打開命名管道失敗!"<<endl; hPipe=NULL; system("pause"); return; } /*讀寫管道數據*****************************************************/ //寫入數據 if(!WriteFile(hPipe,sebuf,strlen(sebuf)+1,&dwWrite,NULL)) { cout<<"寫入數據失敗!"<<endl; system("pause"); return; } //讀取數據 if(!ReadFile(hPipe,rebuf,100,&dwRead,NULL)) { cout<<"讀取數據失敗!"<<endl; system("pause"); return; } cout<<rebuf<<endl; system("pause"); }
剪貼板
通信流程
- 接收數據
- 發送數據

[打開/關閉剪貼板][清空剪貼板][向剪貼板寫入數據][從剪貼板讀取數據][判斷剪貼板數據格式]
函數原型
//打開剪貼板 BOOL OpenClipboard(); //關閉剪貼板 BOOL CloseClipboard();
函數原型
BOOL EmptyClipboard();
說明
只有調用了EmptyClipboard函數后,打開剪貼板的當前窗口才擁有剪貼板.EmptyClipboard函數將清空剪貼板,並釋放剪貼板中的句柄,然后剪貼板的所有權分配給當前窗口.
函數原型
HANDLE SetClipboardData(UINT uFormat,HANDLE hMem);
參數說明
- uFormat 指定剪貼板的格式,這個格式可以是以注冊的格式,或者是任一種標准的形式的剪貼板詳細參見MSDN
- hMem 具有指定格式的句柄.該參數可以為NULL,指示調用窗口直到有對剪貼板數據的請求時候才提供指定的剪貼板格式的數據.如果窗口采用延時提交技術,則該窗口必須處理WM_RENDERFORMAT和WM_RENDERALLFORMATS消息
返回值
如果函數成功返回的是數據句柄
如果函數失敗返回的是NULL,詳細消息可以調用GetLastError函數獲得
說明 當前調用的SetClipboardData函數的窗口必須是剪貼板的擁有着,而且在這個之前,該程序已經調用了OpenClipboard函數打開剪貼板
當一個提供的進程創建了剪貼板數據之后,知道其他進程獲取剪貼板數據前,這些數據都是要占據內存空間的,如果在剪貼板上放置的數據過大,就會浪費內存空間,降低資源利用率.為了避免這種浪費,就可以采用延遲提交技術,也就是有數據提供進程先提供一個指定格式的空剪貼板數據塊,即把SetClipboardData函數的hMem參數設置為NULL.當需要獲取數據的進程想要從剪貼板上得到數據時,操作系統會向數據提供進程發送WM_RENDERFORMAT消息,而數據提供進程可以響應這個消息,並在此消息的響應函數中,再一次調用SetClipboardData函數,將實際的數據放到剪貼板上,當再次調用SetClipboardData函數時就不需要調用OpenClipboard函數,也不需要調用EmptyClipboard函數.也就是說為了提高資源利用率,避免浪費內存空間,可以采用延遲提交技術.第一次調用SetClipboard函數時,將其hMem參數設置為NULL,在剪貼板上以指定的剪貼板放置一個空剪貼板數據塊
函數原型
HANDLE GetClipboardData( UINT uFormat );
參數說明
- uFormat 指定返回數據的句柄格式這個格式可以是以注冊的格式,或者是任一種標准的形式的剪貼板詳細參見MSDN
返回值
若函數成功返回的是剪貼板數據內容的指定格式的句柄
若函數失敗返回值是NULL,詳細消息可以調用GetLastError函數獲得
IsClipboardFormatAvailable 判斷剪貼板的數據格式
函數原型
BOOL IsClipboardFormatAvailable(UINT format);
參數說明
- uFormat 判斷剪貼板里的數據的句柄格式這個格式可以是以注冊的格式,或者是任一種標准的形式的剪貼板詳細參見MSDN
返回值
若剪貼板中的數據格式句柄為uFormat格式返回非零值
若剪貼板中的數據格式句柄不為uFormat格式返回零

#include<windows.h> #include<cstdlib> #include<iostream> #include<string> using namespace std; void main() { HANDLE hClip; char *pBuf; string str="this is message"; /*向剪貼板寫入數據************************************************************************/ //打開剪貼板 if(OpenClipboard(NULL)) { //清空剪貼板 EmptyClipboard(); //想剪貼板寫入數據 hClip=GlobalAlloc(GMEM_MOVEABLE,str.size()+1); pBuf=(char*)GlobalLock(hClip); strcpy(pBuf,str.c_str()); GlobalUnlock(hClip); SetClipboardData(CF_TEXT,hClip); //釋放剪貼板 CloseClipboard(); } /*從剪貼板讀取數據************************************************************************/ //打開剪貼板 if(OpenClipboard(NULL)) { //檢查剪貼板中的數據格式 if(IsClipboardFormatAvailable(CF_TEXT)) { //接收數據 hClip=GetClipboardData(CF_TEXT); pBuf=(char*)GlobalLock(hClip); GlobalUnlock(hClip); cout<<pBuf<<endl; //釋放剪貼板 CloseClipboard(); } } system("pause"); }
運行結果: