命名管道定義
一個命名管道是一個命名的,單向或雙面管道的管道服務器和一個或多個管道客戶端之間的通信。命名管道的所有實例共享相同的管道名稱,但每個實例都有自己的緩沖區和句柄,並為客戶端/服務器通信提供單獨的管道。實例的使用使多個管道客戶端能夠同時使用同一個命名管道。
這里要理解實例的概念:當我用CreateNamedPipe在服務器端創建一個名為pipeTest的命名管道時,即pipeTest擁有了一個實例。再次重復剛才的操作時,即創建了pipeTest的第二個實例;當用CreateNamedPipe在服務器端創建一個名為pipeTestAnother的命名管道時,則該pipeTestAnother管道擁有了第一個實例。
命名管道的使用步驟
服務器端:
首先,使用CreateNamedPipe創建屬於該管道的實例。然后等待客戶端實例的連接,服務器端可以使用ConnectNamedPipe進行阻塞同步等待客戶端實例的連接,也可以非阻塞,然后執行ReadFile不停讀取客戶端發送到管道的數據。
客戶端:
執行WaitNamedPipe(不是真正的用於連接的函數)來等待管道的出現,存在管道后,執行CreateFile來連接存在的服務器管道,獲取對應句柄后,執行WriteFile往管道發送數據。
上面是以最簡單的單向的客戶端發送數據進管道,服務器端接受管道數據。還有其他的通信選項。包括雙通道的Read&Write。Read&Write過程中的同步與異步;按字節流方式/消息的方式寫入/讀取管道數據。
CreateNamedPipe
HANDLE CreateNamedPipeA( [in] LPCSTR lpName, [in] DWORD dwOpenMode, [in] DWORD dwPipeMode, [in] DWORD nMaxInstances, [in] DWORD nOutBufferSize, [in] DWORD nInBufferSize, [in] DWORD nDefaultTimeOut, [in, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
模式 | 意義 |
PIPE_ACCESS_DUPLEX 0x00000003 |
管道是雙向的;服務器和客戶端進程都可以讀取和寫入管道。此模式為服務器提供了對管道的GENERIC_READ和GENERIC_WRITE訪問權限。當客戶端使用CreateFile函數連接到管道時,它可以指定GENERIC_READ或GENERIC_WRITE或兩者 。 |
PIPE_ACCESS_INBOUND 0x00000001 |
管道中的數據流僅從客戶端到服務器。此模式為服務器提供了對管道的GENERIC_READ訪問權限。客戶端在連接到管道時必須指定GENERIC_WRITE訪問權限。如果客戶端必須通過調用GetNamedPipeInfo或GetNamedPipeHandleState函數讀取管道設置,則客戶端在連接到管道時必須指定GENERIC_WRITE和FILE_READ_ATTRIBUTES訪問權限 |
PIPE_ACCESS_OUTBOUND 0x00000002 |
管道中的數據流僅從服務器流向客戶端。此模式為服務器提供了對管道的GENERIC_WRITE訪問權限。客戶端在連接到管道時必須指定GENERIC_READ訪問權限。如果客戶端必須通過調用SetNamedPipeHandleState函數更改管道設置,則客戶端在連接到管道時必須指定GENERIC_READ和FILE_WRITE_ATTRIBUTES訪問權限。 |
模式 | 意義 |
FILE_FLAG_FIRST_PIPE_INSTANCE 0x00080000 |
如果您嘗試使用此標志創建管道的多個實例,則第一個實例的創建成功,但下一個實例的創建失敗並顯示ERROR_ACCESS_DENIED。 |
FILE_FLAG_WRITE_THROUGH 0x80000000 |
直寫模式已啟用。此模式僅影響字節類型管道上的寫入操作,並且僅當客戶端和服務器進程位於不同的計算機上時。如果啟用此模式,則寫入命名管道的函數不會返回,直到寫入的數據通過網絡傳輸並位於遠程計算機上的管道緩沖區中。如果未啟用此模式,系統會通過緩沖數據來提高網絡操作的效率,直到累積的字節數達到最少或經過最長時間。 |
FILE_FLAG_OVERLAPPED 0x40000000 |
重疊模式已啟用。如果啟用此模式,則執行可能需要很長時間才能完成的讀取、寫入和連接操作的函數可以立即返回。這種模式使啟動操作的線程可以執行其他操作,而耗時的操作在后台執行。例如,在重疊模式下,線程可以同時處理管道的多個實例上的輸入和輸出 (I/O) 操作,或者在同一個管道句柄上同時執行讀寫操作。如果未啟用重疊模式,則對管道句柄執行讀取、寫入和連接操作的函數在操作完成之前不會返回。該 ReadFileEx和 WriteFileEx函數只能在重疊模式下與管道句柄一起使用。的 ReadFile的, WriteFile的, ConnectNamedPipe和 TransactNamedPipe功能可以執行同步或作為重疊操作。 |
模式 | 意義 |
WRITE_DAC 0x00040000L |
調用者將擁有對命名管道的自由訪問控制列表 (ACL) 的寫訪問權限。 |
WRITE_OWNER 0x00080000L |
調用者將擁有對命名管道所有者的寫訪問權限。 |
ACCESS_SYSTEM_SECURITY 0x01000000L |
調用者將擁有對命名管道的 SACL 的寫訪問權限。有關詳細信息,請參閱 訪問控制列表 (ACL)和 SACL 訪問權限。 |
dwPipeMode
管道模式。
如果dwPipeMode指定的值不是 0 或下表中列出的標志,則該函數將失敗。
可以指定以下類型模式之一。必須為管道的每個實例指定相同的類型模式。
模式 | 意義 |
PIPE_TYPE_BYTE 0x00000000 |
數據以字節流的形式寫入管道。此模式不能與 PIPE_READMODE_MESSAGE 一起使用。管道不區分在不同寫操作期間寫入的字節。 |
PIPE_TYPE_MESSAGE 0x00000004 |
數據作為消息流寫入管道。管道將每次寫入操作期間寫入的字節視為一個消息單元。當消息未完全讀取時,GetLastError函數返回ERROR_MORE_DATA。此模式可與PIPE_READMODE_MESSAGE或PIPE_READMODE_BYTE 一起使用。 |
對於PIPE_TYPE_BYTE模式寫入管道,讀取端不用每次讀取固定大小的數據,可以讀取任意字節大小數據。
對於PIPE_TYPE_MESSAGE模式寫入管道,讀取端用消息模式讀取時必須全部讀取完,不能只讀取部分消息。
具體參考:
https://www.cnblogs.com/duyy/p/3738610.html
可以指定以下讀取模式之一。同一管道的不同實例可以指定不同的讀取模式。
模式 | 意義 |
---|---|
|
數據作為字節流從管道中讀取。此模式可與PIPE_TYPE_MESSAGE或PIPE_TYPE_BYTE 一起使用。 |
|
數據作為消息流從管道中讀取。僅當還指定了PIPE_TYPE_MESSAGE 時才能使用此模式。 |
可以指定以下等待模式之一。同一管道的不同實例可以指定不同的等待模式。
模式 | 意義 |
---|---|
|
阻塞模式已啟用。當在ReadFile、WriteFile或 ConnectNamedPipe函數中指定管道句柄時 , 直到有數據要讀取、所有數據都已寫入或客戶端已連接時,操作才會完成。使用此模式可能意味着在某些情況下無限期地等待客戶端進程執行操作。 |
|
非阻塞模式已啟用。在這種模式下,ReadFile、WriteFile和 ConnectNamedPipe總是立即返回。 請注意,為了與 Microsoft LAN Manager 2.0 版兼容,支持非阻塞模式,並且不應使用命名管道實現異步 I/O。有關異步管道 I/O 的更多信息,請參閱 同步和重疊輸入和輸出。 |
nMaxInstances
可以為此管道創建的最大實例數。管道的第一個實例可以指定這個值;必須為管道的其他實例指定相同的編號。可接受的值在 1 到PIPE_UNLIMITED_INSTANCES (255)的范圍內。
如果此參數為PIPE_UNLIMITED_INSTANCES,則可以創建的管道實例數量僅受系統資源可用性的限制。如果nMaxInstances大於PIPE_UNLIMITED_INSTANCES,則返回值為INVALID_HANDLE_VALUE並且GetLastError返回ERROR_INVALID_PARAMETER。
默認超時值(以毫秒為單位),如果 WaitNamedPipe函數指定NMPWAIT_USE_DEFAULT_WAIT。命名管道的每個實例都必須指定相同的值。
零值將導致默認超時為 50 毫秒
lpSecurityAttributes
指向SECURITY_ATTRIBUTES結構的指針,該 結構為新命名管道指定安全描述符並確定子進程是否可以繼承返回的句柄
返回值:
如果函數成功,則返回值是命名管道實例的服務器端的句柄。
如果函數失敗,則返回值為INVALID_HANDLE_VALUE。要獲取擴展錯誤信息,請調用 GetLastError。
ConnectNamedPipe
BOOL ConnectNamedPipe( [in] HANDLE hNamedPipe, [in, LPOVERLAPPED lpOverlapped );
功能:允許命名管道服務器進程等待客戶端進程連接到命名管道的實例。客戶端進程通過調用 CreateFile或 CallNamedPipe函數進行連接。
hNamedPipe
命名管道實例的服務器端的句柄。此句柄由CreateNamedPipe函數返回 。
lpOverlapped
指向OVERLAPPED結構的指針 。
如果hNamedPipe是用 FILE_FLAG_OVERLAPPED 打開的,則lpOverlapped參數不能為NULL。它必須指向一個有效的OVERLAPPED結構。如果hNamedPipe使用 FILE_FLAG_OVERLAPPED 打開並且lpOverlapped為NULL,則該函數可能會錯誤地報告連接操作已完成。
如果hNamedPipe是用 FILE_FLAG_OVERLAPPED 創建的並且lpOverlapped不是NULL,則OVERLAPPED結構應該包含一個手動重置事件對象的句柄(服務器可以使用CreateEvent函數創建它 )。【非阻塞重疊IO,異步等待客戶端連接】
如果未使用 FILE_FLAG_OVERLAPPED 打開hNamedPipe,則在連接客戶端或發生錯誤之前,該函數不會返回。如果客戶端在調用函數后連接,則成功的同步操作會導致函數返回非零值。【阻塞同步等待客戶端連接】
返回值:
如果操作是同步的,則ConnectNamedPipe在操作完成之前不會返回。如果函數成功,則返回值非零。如果函數失敗,則返回值為零。要獲取擴展錯誤信息,請調用 GetLastError。
如果操作是異步的,ConnectNamedPipe 會立即返回。如果操作仍處於掛起狀態,則返回值為零且GetLastError返回 ERROR_IO_PENDING。(您可以使用HasOverlappedIoCompleted宏來確定操作何時完成。)如果函數失敗,則返回值為零,並且 GetLastError返回 ERROR_IO_PENDING 或 ERROR_PIPE_CONNECTED 以外的值。
如果客戶端在調用函數之前連接,則函數返回零並且GetLastError返回 ERROR_PIPE_CONNECTED。如果客戶端在調用CreateNamedPipe和調用 ConnectNamedPipe之間的時間間隔內進行連接,則會發生這種情況【舉例:比如服務器端執行CreateNamePipe后,不立即執行ConnectionNamePipe,客戶端隨即執行CreateFile,客戶端此時可以往里面寫數據、然后再執行ConnectionNamePipe時函數返回零並且GetLastError返回 ERROR_PIPE_CONNECTED)】 。
WaitNamedPipe
BOOL WaitNamedPipeA( [in] LPCSTR lpNamedPipeName, [in] DWORD nTimeOut );
nTimeOut:
函數等待命名管道實例可用的毫秒數。您可以使用以下值之一而不是指定毫秒數。
價值 | 意義 |
---|---|
|
超時間隔是服務器進程在CreateNamedPipe函數中指定的默認值 。 |
|
在命名管道的實例可用之前,該函數不會返回。 |
返回值:
如果管道的實例在超時間隔過去之前可用,則返回值非零。
如果管道的實例在超時間隔過去之前不可用,則返回值為零。要獲取擴展錯誤信息,請調用 GetLastError。
客戶端同步寫入,服務器端同步讀取
服務器端讀取:

#pragma warning(disable:4786) #include<Windows.h> #include <string> DWORD WINAPI ThreadProc(LPVOID); bool readMessage(HANDLE hTmpNamedPipe, std::string& message) { char recvBuffer[10001] = { 0 }; DWORD nBytesRead = 0; int ret = ReadFile( hTmpNamedPipe, recvBuffer, sizeof(recvBuffer), &nBytesRead, NULL ); if (0 >= ret) { printf("Read from client error: %d.\n", errno); return false; } else { message = static_cast<std::string>(recvBuffer); return true; } } DWORD WINAPI ThreadProc(LPVOID) { HANDLE hNamedPipe = CreateNamedPipe ("\\\\.\\pipe\\pipeTest", PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 3, 0, 0, 1000, // timeout in millseconds NULL); printf("\\\\.\\pipe\\pipeTest... \n"); if (NULL == hNamedPipe || INVALID_HANDLE_VALUE == hNamedPipe) { return NULL; } if (NULL == hNamedPipe) { return 0; } std::string message; while (true) { printf("strat to read from client...\n"); BOOL fConnected = ConnectNamedPipe(hNamedPipe, NULL); if (fConnected) { printf("connect success...\n"); while (readMessage(hNamedPipe, message)) { while (std::string::npos != message.find("|")) { std::string tmpSourceStr = message.substr(0, message.find("|")); if (tmpSourceStr.length() > 0) { printf("push message=%s in queue...\n", message.c_str()); } message = message.substr(message.find("|") + 1); } } } DisconnectNamedPipe(hNamedPipe); } return NULL; } int main() { HANDLE hThread1; DWORD threadId1; printf("another server is running\r\n"); hThread1 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId1);//創建線程 while (true) { Sleep(30000); } return 0; }
當客戶端沒有實例接入時,服務器端在ConnectNamedPipe函數處阻塞等待客戶端接入。
客戶端寫入

#include <iostream> #include <WinSock2.h> using namespace std; DWORD WINAPI ThreadProc(LPVOID); void Execute(); void ConnectANamePipe(); void ReconnectNamePipe(); HANDLE hThread; int main() { DWORD threadId; hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId);//創建線程 std::cout << "Hello World!\n"; while (1) { Sleep(5000); } } void ConnectANamePipe() { if (!WaitNamedPipe(TEXT("\\\\.\\pipe\\pipeTest"), NMPWAIT_WAIT_FOREVER)) { cout << "conenct namepipe failed" << endl; } DWORD len; HANDLE hClientNamedPipe = CreateFile("\\\\.\\pipe\\pipeTest", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hClientNamedPipe == NULL || hClientNamedPipe == INVALID_HANDLE_VALUE) { printf("Connect to WDS ERROR %d...\n", GetLastError()); } else { while (true) { DWORD nBytesWritten = 0; printf("子線程,pid=%d...\n", GetCurrentThreadId()); char buff[256] = "i am cliect,hello server|"; /* if (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite) == false) { printf("write error,getlasterror=%d...\n", GetLastError()); ReconnectNamePipe(); } else { printf("write success\n"); Sleep(500); }*/ int ret = (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, NULL)); if (0 == ret) { long int iError = GetLastError(); printf("write to server error=%d", iError); } printf("Write to server ok.\n"); Sleep(1000); } } CloseHandle(hClientNamedPipe); } DWORD WINAPI ThreadProc(LPVOID) { ConnectANamePipe(); return 0; } void ReconnectNamePipe() { CloseHandle(hThread); ConnectANamePipe(); }
客戶端接入后,結果如下:
客戶端異步寫入,服務器端同步讀取

// NamePipeClient.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。 // #include <iostream> #include <WinSock2.h> DWORD WINAPI ThreadProc(LPVOID); void Execute(); void ConnectANamePipe(); void ReconnectNamePipe(); HANDLE hThread; int main() { DWORD threadId; hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId);//創建線程 std::cout << "Hello World!\n"; while (1) { Sleep(5000); } } void ConnectANamePipe() { DWORD len; HANDLE hClientNamedPipe = CreateFile("\\\\.\\pipe\\pipeTest", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (hClientNamedPipe == NULL || hClientNamedPipe == INVALID_HANDLE_VALUE) { printf("Connect to WDS ERROR %d...\n", GetLastError()); } /*HANDLE hClientNamedPipe=CreateFile("\\\\.\\Pipe\\pipeTest",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(hClientNamedPipe==INVALID_HANDLE_VALUE) { printf("error,getlasterror=%d\n",GetLastError()); }*/ //CreateFile連接管道成功后,服務器端ovlap.hEvent即可變為有信號 else { while (true) { OVERLAPPED olWrite; memset(&olWrite, 0, sizeof(olWrite)); olWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//創建了一個初始狀態為FALSE的手動(人工)重置的事件對象,當手動重置的事件對象得到通知時,等待該事件對象的所有線程均變為可調度線程。 DWORD nBytesWritten = 0; printf("子線程,pid=%d...\n", GetCurrentThreadId()); char buff[256] = "i am cliect,hello server|"; /* if (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite) == false) { printf("write error,getlasterror=%d...\n", GetLastError()); ReconnectNamePipe(); } else { printf("write success\n"); Sleep(500); }*/ int ret = (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite)); printf(" Write to server return : %d.\n", ret); if (0 == ret) { long int iError = GetLastError(); if (iError != ERROR_IO_PENDING /*997*/)//997表示這個請求是懸而未決的 { printf("a Write to server Error : %d.\n", GetLastError()); ReconnectNamePipe(); return; } } if (0 == GetOverlappedResult(hClientNamedPipe, &olWrite, &nBytesWritten, true)) { printf("Write to server Error : %d.\n", GetLastError()); ReconnectNamePipe(); return; } else { printf("Write to server ok.\n"); Sleep(1000); } } } CloseHandle(hClientNamedPipe); } DWORD WINAPI ThreadProc(LPVOID) { ConnectANamePipe(); return 0; } void ReconnectNamePipe() { CloseHandle(hThread); ConnectANamePipe(); }
服務器端異步

#pragma warning(disable:4786) #include<Windows.h> #include <string> DWORD WINAPI ThreadProc(LPVOID); bool readMessage(HANDLE hTmpNamedPipe, std::string& message) { char recvBuffer[10001] = { 0 }; DWORD nBytesRead = 0; int ret = ReadFile( hTmpNamedPipe, recvBuffer, sizeof(recvBuffer), &nBytesRead, NULL ); if (0 >= ret) { printf("Read from client error: %d.\n", errno); return false; } else { message = static_cast<std::string>(recvBuffer); return true; } } DWORD WINAPI ThreadProc(LPVOID) { HANDLE hNamedPipe = CreateNamedPipe ("\\\\.\\pipe\\pipeTest", PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, 3,//pipeTest管道最大實例數 0, 0, 1000, // timeout in millseconds NULL); printf("\\\\.\\pipe\\pipeTest... \n"); if (NULL == hNamedPipe || INVALID_HANDLE_VALUE == hNamedPipe) { return NULL; } printf("waiting for signal\n"); while (true) { HANDLE hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);//初始狀態為TRUE的人工重置對象 if (INVALID_HANDLE_VALUE == hEvent) { return NULL; } OVERLAPPED ovlap; ZeroMemory(&ovlap, sizeof(OVERLAPPED)); ovlap.hEvent = hEvent;//將一個事件與重疊I/O綁定 std::string message; BOOL fPendingIO = FALSE; DWORD dwWait; printf("strat to read from client...\n"); BOOL fConnected = ConnectNamedPipe(hNamedPipe, &ovlap); if (fConnected) { printf("ConnectNamedPipe failed with %d.\n", GetLastError()); return 0; } switch (GetLastError()) { // The overlapped connection in progress. case ERROR_IO_PENDING: printf("ConnectNamedPipe status fPendingIO.\n"); fPendingIO = true; break; // Client is already connected, so signal an event. case ERROR_PIPE_CONNECTED: if (SetEvent(hEvent)) break; // If an error occurs during the connect operation... default: { printf("ConnectNamedPipe failed with %d.\n", GetLastError()); return 0; } } /* 阻塞狀態下,等待管道另一端的連入,當一個連接到來的時候,ovlap.hEvent會立即變為有信號狀態 */ dwWait = WaitForSingleObject(ovlap.hEvent, INFINITE); DWORD dwTransBytes = -1; switch (dwWait) { case 0: if (fPendingIO) { //獲取Overlapped結果 if (GetOverlappedResult(hNamedPipe, &ovlap, &dwTransBytes, TRUE) == FALSE) { printf("ConnectNamedPipe failed %d\n", GetLastError()); return -1; } } // 讀寫完成 case WAIT_IO_COMPLETION: { while (readMessage(hNamedPipe, message)) { while (std::string::npos != message.find("|")) { std::string tmpSourceStr = message.substr(0, message.find("|")); if (tmpSourceStr.length() > 0) { printf("push message=%s in queue...\n", message.c_str()); } message = message.substr(message.find("|") + 1); } } break; } } DisconnectNamedPipe(hNamedPipe); } } int main() { HANDLE hThread1; DWORD threadId1; /*HANDLE hThread2; DWORD threadId2;*/ printf("another server is running\r\n"); hThread1 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId1);//創建線程 //hThread2 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId2);//創建線程 while (true) { Sleep(30000); } return 0; }
說明:服務器端事件同步使用了HANDLE hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);創建了初始狀態為TRUE的人工重置對象。
在使用CreateNamedPipe(FILE_FLAG_OVERLAPPED)時,使用ConnectNamedPipe(h, &op)后,會立即返回(非阻塞、異步的),這個時候一般是返回FALSE,使用GetLastError()會得到ERROR_IO_PENDING,表示這個請求是懸而未決的。我使用一個BOOL fPendingIO標識來記錄所有懸而未決的的請求,fPendingIO=TRUE。然后使用WaitForMultipleObject方法等待這個事件。線程就會在此處阻塞。我們通過實際現象也可以看出,當客戶端未建立連接時,服務器端在WaitForMultipleObject處等待事件變為有信號狀態,繼續往下執行。
現在來解釋一下為什么開始創建事件時初始態為signaled。按照常理,WaitForMultipleObjects不會被阻塞,因為其中一個事件的狀態為signaled。其實不然,它的狀態在connectNamedPipe(h, &op)后已經改變了。對以OVERLAPPED關聯的事件,當使用OVERLAPPED相關的方法操作后,其狀態會可能會改變的,主要基於下面3個原則:1)當實際操作在函數返回前已經完成,事件的狀態不會改變。2)當函數返回是,實際的操作沒有完成,也即是說這個操作是Pending的,這個時候事件會被設置為nonsignaled.3) 當操作的Pending完成后,事件會被設置為signaled。有了上面的3條原則,OVERLAPPED關聯的事件的狀態變化就好理解了。當使用connectNamedPipe(h, &op)方法時,函數會立即返回,而實際這個操作並沒有進行,而是Pending了,所以,event會由signaled變為nonsignaled,當真正有Client連接時,這個操作才會完成,這個時候,event會由nonsignaled變為signaled。這個時候,WaitForMultipleObject會繼續執行下去。對於Pending后的操作,一定要使用GetOverlappedResult方法,判斷結果。上面的原則適用ReadFile, WriteFile, ConnectNamedPipe, 和 TransactNamedPipe等函數。
參考:https://www.cnblogs.com/xtfnpgy/p/9285448.html#top