天天看點

21、Windows API 程序間通信,管道(Pipe)

    管道是一種用于在程序間共享資料的機制,其實質是一段共享記憶體。Windows系統為這段共享的記憶體設計采用資料流I/0的方式來通路。由一個程序讀、另一個程序寫,類似于一個管道兩端,是以這種程序間的通信方式稱作“管道”。

    管道分為匿名管道和命名管道。

    匿名管道隻能在父子程序間進行通信,不能在網絡間通信,而且資料傳輸是單向的,隻能一端寫,另一端讀。

    指令管道可以在任意程序間通信,通信是雙向的,任意一端都可讀可寫,但是在同一時間隻能有一端讀、一端寫。

一、注意點

1、常用API

Pipes[2]

在[3,4]中也對這一部分進行了介紹。

2、示例

1)伺服器端

建立管道 >> 監聽 >> 讀寫 >> 關閉

CreateNamedPipe

ConnectNamedPipe

ReadFile/WriteFile

DisconnectNamedPipe

示例代碼

21、Windows API 程式間通信,管道(Pipe)
21、Windows API 程式間通信,管道(Pipe)

通過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

21、Windows API 程式間通信,管道(Pipe)
21、Windows API 程式間通信,管道(Pipe)

通過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 函數、接口、程式設計執行個體

繼續閱讀