管道是一種用于在程序間共享資料的機制,其實質是一段共享記憶體。Windows系統為這段共享的記憶體設計采用資料流I/0的方式來通路。由一個程序讀、另一個程序寫,類似于一個管道兩端,是以這種程序間的通信方式稱作“管道”。
管道分為匿名管道和命名管道。
匿名管道隻能在父子程序間進行通信,不能在網絡間通信,而且資料傳輸是單向的,隻能一端寫,另一端讀。
指令管道可以在任意程序間通信,通信是雙向的,任意一端都可讀可寫,但是在同一時間隻能有一端讀、一端寫。
一、注意點
1、常用API
Pipes[2]
在[3,4]中也對這一部分進行了介紹。
2、示例
1)伺服器端
建立管道 >> 監聽 >> 讀寫 >> 關閉
CreateNamedPipe
ConnectNamedPipe
ReadFile/WriteFile
DisconnectNamedPipe
示例代碼
通過pipe程序間通信-伺服器端
通過pipe程序間通信
**************************************/
/* 頭檔案 */
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
/* 常量 */
#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096
/* 結構定義 */
typedef struct
{
OVERLAPPED oOverlap;
HANDLE hPipeInst;
TCHAR chRequest[BUFSIZE];
DWORD cbRead;
TCHAR chReply[BUFSIZE];
DWORD cbToWrite;
} PIPEINST, *LPPIPEINST;
/* 函數聲明 */
VOID DisconnectAndClose(LPPIPEINST);
BOOL CreateAndConnectInstance(LPOVERLAPPED);
BOOL ConnectToNewClient(HANDLE, LPOVERLAPPED);
VOID GetAnswerToRequest(LPPIPEINST);
VOID WINAPI CompletedWriteRoutine(DWORD, DWORD, LPOVERLAPPED);
VOID WINAPI CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED);
/* 全局變量 */
HANDLE hPipe;
/* ************************************
* int main(VOID)
* 功能 pipe 通信服務端主函數
int main(VOID)
HANDLE hConnectEvent;
OVERLAPPED oConnect;
LPPIPEINST lpPipeInst;
DWORD dwWait, cbRet;
BOOL fSuccess, fPendingIO;
// 用于連接配接操作的事件對象
hConnectEvent = CreateEvent(
NULL, // 預設屬性
TRUE, // 手工reset
TRUE, // 初始狀态 signaled
NULL); // 未命名
if (hConnectEvent == NULL)
printf("CreateEvent failed with %d.\n", GetLastError());
return 0;
}
// OVERLAPPED 事件
oConnect.hEvent = hConnectEvent;
// 建立連接配接執行個體,等待連接配接
fPendingIO = CreateAndConnectInstance(&oConnect);
while (1)
// 等待用戶端連接配接或讀寫操作完成
dwWait = WaitForSingleObjectEx(
hConnectEvent, // 等待的事件
INFINITE, // 無限等待
TRUE);
switch (dwWait)
case 0:
// pending
if (fPendingIO)
// 擷取 Overlapped I/O 的結果
fSuccess = GetOverlappedResult(
hPipe, // pipe 句柄
&oConnect, // OVERLAPPED 結構
&cbRet, // 已經傳送的資料量
FALSE); // 不等待
if (!fSuccess)
printf("ConnectNamedPipe (%d)\n", GetLastError());
// 配置設定記憶體
lpPipeInst = (LPPIPEINST) HeapAlloc(GetProcessHeap(),0,sizeof(PIPEINST));
if (lpPipeInst == NULL)
printf("GlobalAlloc failed (%d)\n", GetLastError());
lpPipeInst->hPipeInst = hPipe;
// 讀和寫,注意CompletedWriteRoutine和CompletedReadRoutine的互相調用
lpPipeInst->cbToWrite = 0;
CompletedWriteRoutine(0, 0, (LPOVERLAPPED) lpPipeInst);
// 再建立一個連接配接執行個體,以響應下一個用戶端的連接配接
fPendingIO = CreateAndConnectInstance(
&oConnect);
break;
// 讀寫完成
case WAIT_IO_COMPLETION:
default:
printf("WaitForSingleObjectEx (%d)\n", GetLastError());
* CompletedWriteRoutine
* 寫入pipe操作的完成函數
* 接口參見FileIOCompletionRoutine回調函數定義
*
* 當寫操作完成時被調用,開始讀另外一個用戶端的請求
VOID WINAPI CompletedWriteRoutine(
DWORD dwErr,
DWORD cbWritten,
LPOVERLAPPED lpOverLap)
BOOL fRead = FALSE;
// 儲存overlap執行個體
lpPipeInst = (LPPIPEINST) lpOverLap;
// 如果沒有錯誤
if ((dwErr == 0) && (cbWritten == lpPipeInst->cbToWrite))
fRead = ReadFileEx(
lpPipeInst->hPipeInst,
lpPipeInst->chRequest,
BUFSIZE*sizeof(TCHAR),
(LPOVERLAPPED) lpPipeInst,
// 寫讀操作完成後,調用CompletedReadRoutine
(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedReadRoutine);
if (! fRead)
// 出錯,斷開連接配接
DisconnectAndClose(lpPipeInst);
* CompletedReadRoutine
* 讀取pipe操作的完成函數
* 當讀操作完成時被調用,寫入回複
VOID WINAPI CompletedReadRoutine(
DWORD cbBytesRead,
BOOL fWrite = FALSE;
if ((dwErr == 0) && (cbBytesRead != 0))
// 根據用戶端的請求,生成回複
GetAnswerToRequest(lpPipeInst);
// 将回複寫入到pipe
fWrite = WriteFileEx(
lpPipeInst->chReply, //将響應寫入pipe
lpPipeInst->cbToWrite,
// 寫入完成後,調用CompletedWriteRoutine
(LPOVERLAPPED_COMPLETION_ROUTINE) CompletedWriteRoutine);
if (! fWrite)
* VOID DisconnectAndClose(LPPIPEINST lpPipeInst)
* 功能 斷開一個連接配接的執行個體
* 參數 lpPipeInst,斷開并關閉的執行個體句柄
VOID DisconnectAndClose(LPPIPEINST lpPipeInst)
// 關閉連接配接執行個體
if (! DisconnectNamedPipe(lpPipeInst->hPipeInst) )
printf("DisconnectNamedPipe failed with %d.\n", GetLastError());
// 關閉 pipe 執行個體的句柄
CloseHandle(lpPipeInst->hPipeInst);
// 釋放
if (lpPipeInst != NULL)
HeapFree(GetProcessHeap(),0, lpPipeInst);
* BOOL CreateAndConnectInstance(LPOVERLAPPED lpoOverlap)
* 功能 建立連接配接執行個體
* 參數 lpoOverlap,用于overlapped IO的結構
* 傳回值 是否成功
BOOL CreateAndConnectInstance(LPOVERLAPPED lpoOverlap)
LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\samplenamedpipe");
// 建立named pipe
hPipe = CreateNamedPipe(
lpszPipename, // pipe 名
PIPE_ACCESS_DUPLEX | // 可讀可寫
FILE_FLAG_OVERLAPPED, // overlapped 模式
// pipe模式
PIPE_TYPE_MESSAGE | // 消息類型 pipe
PIPE_READMODE_MESSAGE | // 消息讀模式
PIPE_WAIT, // 阻塞模式
PIPE_UNLIMITED_INSTANCES, // 無限制執行個體
BUFSIZE*sizeof(TCHAR), // 輸出緩存大小
BUFSIZE*sizeof(TCHAR), // 輸入緩存大小
PIPE_TIMEOUT, // 用戶端逾時
NULL); // 預設安全屬性
if (hPipe == INVALID_HANDLE_VALUE)
printf("CreateNamedPipe failed with %d.\n", GetLastError());
// 連接配接到新的用戶端
return ConnectToNewClient(hPipe, lpoOverlap);
* BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo)
BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo)
BOOL fConnected, fPendingIO = FALSE;
// 開始一個 overlapped 連接配接
fConnected = ConnectNamedPipe(hPipe, lpo);
if (fConnected)
printf("ConnectNamedPipe failed with %d.\n", GetLastError());
switch (GetLastError())
// overlapped連接配接進行中.
case ERROR_IO_PENDING:
fPendingIO = TRUE;
// 已經連接配接,是以Event未置位
case ERROR_PIPE_CONNECTED:
if (SetEvent(lpo->hEvent))
// error
return fPendingIO;
// TODO根據用戶端的請求,給出響應
VOID GetAnswerToRequest(LPPIPEINST pipe)
_tprintf( TEXT("[%d] %s\n"), pipe->hPipeInst, pipe->chRequest);
lstrcpyn( pipe->chReply, TEXT("Default answer from server") ,BUFSIZE);
pipe->cbToWrite = (lstrlen(pipe->chReply)+1)*sizeof(TCHAR);
2)用戶端
打開指令管道,獲得句柄 >> 寫入資料 >> 等待回複
WaitNamedPipe
SetNamedPipeHandleState
通過pipe程序間通信-用戶端
#include <conio.h>
#define BUFSIZE 512
int main(int argc, TCHAR *argv[])
LPTSTR lpvMessage=TEXT("Default message from client");
TCHAR chBuf[BUFSIZE];
BOOL fSuccess;
DWORD cbRead, cbWritten, dwMode;
if( argc > 1 ) // 如果輸入了參數,則使用輸入的參數
lpvMessage = argv[1];
// 打開一個命名pipe
hPipe = CreateFile(
lpszPipename, // pipe 名
GENERIC_READ | GENERIC_WRITE, // 可讀可寫
0, // 不共享
NULL, // 預設安全屬性
OPEN_EXISTING, // 已經存在(由服務端建立)
0, // 預設屬性
NULL);
if (hPipe != INVALID_HANDLE_VALUE)
// 如果不是 ERROR_PIPE_BUSY 錯誤,直接退出
if (GetLastError() != ERROR_PIPE_BUSY)
printf("Could not open pipe");
// 如果所有pipe執行個體都處于繁忙狀态,等待2秒。
if (!WaitNamedPipe(lpszPipename, 2000))
// pipe已經連接配接,設定為消息讀狀态
dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
hPipe, // 句柄
&dwMode, // 新狀态
NULL, // 不設定最大緩存
NULL); // 不設定最長時間
printf("SetNamedPipeHandleState failed");
// 寫入pipe
fSuccess = WriteFile(
hPipe, // 句柄
lpvMessage, // 寫入的内容
(lstrlen(lpvMessage)+1)*sizeof(TCHAR), // 寫入内容的長度
&cbWritten, // 實際寫的内容
NULL); // 非 overlapped
printf("WriteFile failed");
do
// 讀回複
fSuccess = ReadFile(
chBuf, // 讀取内容的緩存
BUFSIZE*sizeof(TCHAR), // 緩存大小
&cbRead, // 實際讀的位元組
NULL); // 非 overlapped
if (! fSuccess && GetLastError() != ERROR_MORE_DATA)
break; //失敗,退出
_tprintf( TEXT("%s\n"), chBuf ); // 列印讀的結果
} while (!fSuccess); // ERROR_MORE_DATA 或者成功則循環
getch();//任意鍵退出
// 關閉句柄
CloseHandle(hPipe);
3、I/O簡介
I/O模式不僅在程序間通信時使用,任何具有資料流形式的輸入輸出(包括檔案輸入輸出、核心通信、網絡輸入輸出等)都涉及I/O模式。
異步( Asynchronous)和同步(Synchronous) I/O是兩種基本的I/O模式。
同步I/O
所謂同步I/O是指在調用ReadFile、WriteFile等函數進行輸入輸出操作時,系統完成了輸入輸出ReadFile、WriteFile才傳回。在作業系統進行I/O操作的過程上,使用者态線程不能執行,是以在同步I/O時,如果需要在I/O時進行其他操作就隻能再開啟線程。
異步I/O
異步I/O是在調用ReadFile、WriteFile等函數後,函數立即傳回,線程可以進行其他操作。剩下的I/O操作在系統核心中自動完成。那麼在系統核心完成輸入輸出後,程式如何知道I/O是否已完成?
一種方法,稱作完成函數(Completion Routines),如果使用ReadFileEx、WriteFileEx等進行I/O,可以指定完成函數,所謂完成函數是指核心在完成I/O後,核心會回調這個函數。當完成函數被調用時,就指明核心已經完成了I/O,程式可以在這個函數中進行一個I/O完成後需要的操作(例如釋放記憶體)。
參考
[1] 精通Windows API 函數、接口、程式設計執行個體