天天看點

匿名管道 與 命名管道

參考一:

管道(PIPE)實際是用于程序間通信的一段共享記憶體,建立管道的程序稱為管道伺服器,連接配接到一個管道的程序為管道客戶機.一個程序在向管道寫入資料後,另一程序就可以從管道的另一端将其讀出來.管道分為兩種:匿名管道和命名管道.

匿名管道是在父程序和子程序間單向傳輸資料的一種未命名管道,隻能在本地計算機中使用,而不能用于網絡間的通信.

匿名通道由CreatePipe()函數建立,該函數在建立匿名管道的同時傳回兩個句柄:讀句柄和寫句柄.其原型如下:

BOOL CreatePipe(

   PHANDLE hReadPipe,    //指向讀句柄的指針

   PHANDLE hWritePipe,   //指向寫句柄的指針

   LPSECURITY_ATTRIBUTES lpPipeAttributes,    //指向安全屬性的指針

   DWORD nSize    //管道大小,若為0則由系統決定

};

匿名管道不支援異步讀寫操作.

命名管道是在管道是在管道伺服器和一台或多台管道客戶機之間進行單向或雙向通信的一種命名的管道.一個命名管道的所有執行個體共享同一個管道名,但是每一個執行個體均擁有獨立的緩存和句柄,并且為客戶-服務通信提供一個分離的管道.

命名管道可以在同一台計算機的不同程序之間或在跨越一個網絡的不同計算機的不同程序間進行有連接配接的可靠資料通信,如果連接配接中斷,連接配接雙方都能立即收到連接配接斷開的資訊。

每一個命名管道都有一個唯一的名字,以區分存在于系統的命名對象清單中的其他命名管道.管道伺服器在調用CreateNamedPipe()函數建立命名管道的一個或多個執行個體時為其指定了名稱.對于管道客戶機,則是在調用CreateFile()或CallNamedPipe()函數以連接配接一個命名管道執行個體時對管道名進行指定.命名管道對其辨別采用UNC格式:

\\Server\\Pipe\[Path]Name

其中,第一部分\\Server指定了伺服器的名字,命名管道服務就在此伺服器建立,其字串部分可以為一個小數點(表示本機)、星号(目前網絡字段)、域名或是一個真正的服務;第二部分是一個不可變化的的寫死字串;第三部分\[Path]Name則使應用程式可以唯一定義及辨別一個命名管道的名字,而且可以設定多級目錄.

管道伺服器首次調用CreateNamedPipe()函數時,使用nMaxInstance參數指定了能同時存在的管道執行個體的最大數目.伺服器可以重複調用CreateNamedPipe()函數去建立管道新的執行個體,直至達到設定的最大執行個體數.下面給出CreateNamedPipe()的函數原型:

HANDLE CreateNamedPipe(

   LPCTSTR lpName,    //指向管道名稱的指針

   DWORD dwOpenMode,  //管道打開模式

   DWORD dwPipeMode,  //管道模式

   DWORD nMaxInstance,    //最大執行個體數

   DWORD nOutBufferSize,  //輸出緩存大小

   DWORD nInBufferSize,   //輸入緩存大小

   DWORD nDefaultTimeOut,    //逾時設定

   LPSECURITY_ATTRIBUTES lpSecurityAttributes    //安全屬性指針

};

其中,dwOpenMode參數用來訓示管道在建立好之後,它的傳輸方向、I/O控制以及安全模式。

命名管道:可用于網絡通信;可通過名稱引用;支援多用戶端連接配接;支援雙向通信;支援異步重疊I/O;      
匿名管道:隻能本地使用。      

參考二:

作業系統中負責線程間通訊的東西叫管道。

管道(pipe)是程序用來通訊的共享記憶體區域。一個程序往管道中寫入資訊,而其它的程序可以從管道中讀出資訊。如其名,管道是程序間資料交流的通道。郵路(Mailslots)的功能與管道類似,也是程序間通訊(interprocess communications,IPC)的媒介,隻不過其具體實作方式與管道有些差别。一個基于Win32的應用程式可以在郵路中儲存消息,這些消息通常通過網絡發往一個指定的計算機或某域名(域是共享一個組名的一組工作站或伺服器。)下的所有計算機。你也可以使用命名管道代替郵路來進行程序間通信。命名管道最适合用來兩個程序間的消息傳遞,郵路則更适合一個程序向多個程序廣播消息。郵路具有一個重要的特點,它使用資料包廣播消息。廣播(broadcast)是網絡傳輸中使用的術語,它意味着接收方收到資料後不發送确認消息通知發送方。而管道(這裡的管道指命名管道,有關命名管道以下詳解。)則不同,它更類似于打電話,你隻對一個當事人說話,但是你卻非常清楚你的話都被對方聽到。郵路和管道一樣,也是一個虛拟檔案,它儲存在記憶體中,但是你卻必須使用普通的Win32檔案函數通路它,比如CreateFile、ReadFile、WriteFile等。郵路中儲存的資料可以是任何形式的,唯一的要求是不得超過64K。與磁盤檔案不同的是,郵路是一個臨時的對象,當某個郵路所有的句柄都關閉的時候,該郵路及其中的資料就被删除。

管道的類型有兩種:匿名管道和命名管道。匿名管道是不命名的,它最初用于在本地系統中父程序與它啟動的子程序之間的通信。命名管道更進階,它由一個名字來辨別,以使用戶端和服務端應用程式可以通過它進行彼此通信。而且,Win32命名管道甚至可以在不同系統的程序間使用,這使它成為許多客戶/伺服器應用程式的理想之選。

就像水管連接配接兩個地方并輸送水一樣,軟體的管道連接配接兩個程序并輸送資料。一個一個管道一旦被建立,它就可以象檔案一樣被通路,并且可以使用許多與檔案操作同樣的函數。可以使用CreateFile函數擷取一個已打開的管道的句柄,或者由另一個程序提供一個句柄。使用WriteFile函數向管道寫入資料,之後這些資料可以被另外的程序用ReadFile函數讀取。管道是系統對象,是以管道的句柄在不需要時必須使用CloseHandle函數關閉。

匿名管道隻能單向傳送資料,而命名管道可以雙向傳送。管道可以以比特流形式傳送任意數量的資料。命名管道還可以将資料集合到稱為消息的資料塊中。命名管道甚至具有通過網絡連接配接多程序的能力。但遺憾的是Windows9X不支援建立命名管道,它隻能在WindowsNT系列(如Windows NT,Windows 2000,Windows XP)的作業系統上建立。

當讨論管道時,通常涉及到兩個程序:客戶程序和服務程序。服務程序負責建立管道。客戶程序連接配接到管道。服務程序可以建立一個管道的多個執行個體,以此支援多個客戶程序。

參考三:

一、概述

  管道(Pipe)實際是用于程序間通信的一段共享記憶體,建立管道的程序稱為管道伺服器,連接配接到一個管道的程序為管道客戶機。一個程序在向管道寫入資料後,另一程序就可以從管道的另一端将其讀取出來。匿名管道(Anonymous Pipes)是在父程序和子程序間單向傳輸資料的一種沒有名字的管道,隻能在本地計算機中使用,而不可用于網絡間的通信。

二、匿名管道

  匿名管道由CreatePipe()函數建立,該函數在建立匿名管道的同時傳回兩個句柄:管道讀句柄和管道寫句柄。CreatePipe()的函數原型為: 

 

BOOL CreatePipe(PHANDLE hReadPipe,                      // 指向讀句柄的指針

  PHANDLE hWritePipe,                     // 指向寫句柄的指針

  LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全屬性的指針

  DWORD nSize                             // 管道大小

  );

如果函數成功傳回TRUE,否則傳回FALSE,可以使用GetLastError()得到錯誤值。

其中,第3個參數的定義為:

typedef struct _SECURITY_ATTRIBUTES{

    DWORD  nLength;               // 本結構的位元組數

    LPVOID lpSecurityDescriptor;  // 安全描述符位址

    BOOL   bInheritHandle;        // 是否可以被子程序繼承

}SECURITY_ATTRIBUTES;

這個結構包含了一個對象的安全描述資訊,并且指定了管道是否可以被自程序繼承。

  通過hReadPipe和hWritePipe所指向的句柄可分别以隻讀、隻寫的方式去通路管道。但是在同一個程序中去讀寫管道是沒有意義的,我們常常需要的是在父子程序中傳遞資料,也就是父寫資料,子讀資料,或者子寫資料,子讀資料。我們這裡僅讨論後一種情況,一個典型是示例就是一個有視窗的程式調用控制台程式,把控制台的輸出資訊在父視窗中輸出。

    當父程序執行CreateProcess()啟動子程序時,系統會檢查父程序内可以繼承的核心對象句柄,複制到子程序空間,這樣子程序就有了和父程序一樣的匿名管道句柄,子程序對管道的寫端放入資料,父程序就可以從讀端取到資料。同樣,父程序在寫端放入資料,子程序也可以從讀端取出資料。也就是說,一個匿名管道同時擁有了兩個寫端和讀端。當父子程序任何一個關閉的時候,無論時候顯式的關閉讀寫句柄,系統都會幫程序關閉所擁有的管道句柄。正常情況下,控制台程序的輸輸入出是在控制台視窗的,但是如果我們在建立子程序的時候指定了其輸入輸出,那麼子程序就會從我們的管道讀資料,把輸出資料寫到我們指定的管道。CreateProcess()定義如下:

BOOL CreateProcess(

    LPCTSTR  lpApplicationName,                 // 執行程式的名字

    LPTSTR  lpCommandLine,                 // 指令行字元串

    LPSECURITY_ATTRIBUTES  lpProcessAttributes, // 程序的安全屬性的指針

    LPSECURITY_ATTRIBUTES  lpThreadAttributes, // 主線程安全屬性指針

    BOOL  bInheritHandles,                 // 是否繼承父程序句柄的标志

    DWORD  dwCreationFlags,                 // 建立時候的标志位

    LPVOID  lpEnvironment,                 // 環境變量塊指針

    LPCTSTR  lpCurrentDirectory,         // 指定工作目錄的字元串指針

    LPSTARTUPINFO  lpStartupInfo,         // 啟動資訊指針

    LPPROCESS_INFORMATION  lpProcessInformation // 程序資訊指針

   );

    其中我們關心的啟動資訊結構,其定義為:

typedef struct _STARTUPINFO {    // si

    DWORD   cb;                  // 本結構體位元組數,用于版本控制

    LPTSTR  lpReserved;

    LPTSTR  lpDesktop;

    LPTSTR  lpTitle;

    DWORD   dwX;

    DWORD   dwY;

    DWORD   dwXSize;

    DWORD   dwYSize;

    DWORD   dwXCountChars;

    DWORD   dwYCountChars;

    DWORD   dwFillAttribute;

    DWORD   dwFlags;

    WORD    wShowWindow;

    WORD    cbReserved2;

    LPBYTE  lpReserved2;

    HANDLE  hStdInput;          // 輸入句柄

    HANDLE  hStdOutput;         // 輸出句柄

    HANDLE  hStdError;          // 錯誤顯示句柄

} STARTUPINFO, *LPSTARTUPINFO; 

    在建立子程序時候,指定了子程序的輸出管道,我們在管道另一端讀取就可以了。那麼存在一個問題,我們什麼時候能知道子程序結束了,或者說不再寫資料了呢?ReadFile()函數會阻塞到讀到資料或者出錯的後才會傳回,也就是說當管道的所有寫端都關閉的時候,讀會出錯,能夠使函數在一個循環中傳回,那麼,我們應該在建立子程序後立即關閉父程序所擁有的寫句柄,那麼當子程序結束時候,讀到0位元組傳回。

  同樣的道理,在用WriteFile()函數向管道寫入資料時,隻有在向管道寫完指定位元組的資料後或是在有錯誤發生時函數才會傳回。如管道緩沖已滿而資料還沒有寫完,WriteFile()将要等到另一程序對管道中資料讀取以釋放出更多可用空間後才能夠傳回。管道伺服器在調用CreatePipe()建立管道時以參數nSize對管道的緩沖大小作了設定。 匿名管道并不支援異步讀、寫操作,這也就意味着不能在匿名管道中使用ReadFileEx()和WriteFileEx(),而且ReadFile()和WriteFile()中的lpOverLapped參數也将被忽略。

三、命名管道

命名管道作為一種通信方法,有其獨特的優越性,這主要表現在它不完全依賴于某一種協定,而是适用于任何協定——隻要能夠實作通信。

  命名管道具有很好的使用靈活性,表現在:

  1) 既可用于本地,又可用于網絡。

  2) 可以通過它的名稱而被引用。

  3) 支援多客戶機連接配接。

  4) 支援雙向通信。

  5) 支援異步重疊I/O操作。

執行個體代碼:

用戶端源碼

#include <windows.h>
#include <iostream>
using namespace std;

const TCHAR szPipeName[] = "\\\\.\\pipe\\potok";

int main(void)
{
	HANDLE hPipe = CreateFile(szPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hPipe == INVALID_HANDLE_VALUE)
	{
		printf("CreateFile return [%d]!\n", GetLastError());
		return -1;
	}

	DWORD dwRead, dwWrite;
	char szBuf[1024] = {0};

	for (int i = 0; i < 10; ++i)
	{
		sprintf(szBuf, "%d", i);
		WriteFile(hPipe, szBuf, strlen(szBuf), &dwWrite, 0);
		printf("Send: %s\n", szBuf);
		memset(szBuf, 0, sizeof(szBuf));
		ReadFile(hPipe, szBuf, sizeof(szBuf), &dwRead, 0);
		printf("Recv: %s\n", szBuf);
	}

	system("pause");
	return 0;
}
           

伺服器端源碼

#include <iostream>
#include <Windows.h>
using namespace std;

int main(void)
{
	TCHAR strPipeName[] = "\\\\.\\pipe\\potok";
	PSECURITY_DESCRIPTOR psd;
	psd = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
	if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
	{
		LocalFree((HLOCAL)psd);
		return -1;
	}
	if (!SetSecurityDescriptorDacl(psd, TRUE, (PACL)NULL, FALSE))
	{
		LocalFree((HLOCAL)psd);
		return -1;
	}

	SECURITY_ATTRIBUTES saAttr;
	saAttr.nLength =sizeof(SECURITY_ATTRIBUTES);
	saAttr.lpSecurityDescriptor = psd;
	saAttr.bInheritHandle = TRUE;
	HANDLE hIPC = CreateNamedPipe(strPipeName, 
				  PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 
				  PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 
				  1, 0, 0, 10000, &saAttr);
	if (hIPC == INVALID_HANDLE_VALUE)
	{
		printf("CreateNamedPipe return [%d]!\n", GetLastError());
		return -1;
	}

	char szBuf[1024] = {0};
	DWORD dwRead, dwWrite;
	char szWrite[] = "Get You\n";
	ConnectNamedPipe(hIPC, NULL);
	while(1)
	{
		if (!ReadFile(hIPC, szBuf, sizeof(szBuf), &dwRead, 0))
		{
			break;
		}
		printf("%s\n", szBuf);
		memset(szBuf, 0, sizeof(szBuf));

		if (!WriteFile(hIPC, szWrite, strlen(szWrite), &dwWrite, NULL))
		{
			break;
		}
	}

	return 0;
};