IPC之命名管道
1.管道是通過IO接口存取得字節流, windows中利用得是ReadFile()和WriteFile(),windows利用單一句柄支持雙向IO,命名管道也稱做FIFO(first in first out)
命名管道得機制:一個進程把數據放到管道里,另一個知道管道名字得進程把數據把取走,實際是用于進程間通信得一段共享內存,創建管道得進程稱為管道服務器,鏈接到一個管道得進程為管道客戶機,用以下函數?
?
?管道實際是用于進程間通信的一段共享內存,創建管道的進程稱為管道服務器,連
接到-?一個管道的進程為管道客戶機。可以用以下函數創建管道,即
HANDLB?CreateNamedPipel
LPCTSTR?IpName,
DWORD?dwopenMode,
DWORD?dwPipeMode,
DWORD?nMaxInstances,
DWORD?nOutBufferSi?ze,
DWORD?nInBufferSize,
DWORD?nDefaultrimeOut,
LPSECURITY,ATTRIBUTES?1pSecurityAttributes
);
其中參數的含義如F:
IpName:?管道名稱。用下面的方法命名;
每一一個命名管道都有一一個唯一的名字以區分存在于系統的命名對象列表中的其他命
名管道。管道服務器在調用CreateNamedPipe()創建命名管道的一一個或多個實例時為其指
定了名稱。對于管道客戶機,則是在調用CreateFile()或CallINamedPipe()函數以連接一
個命名管道實例時對管道名進行指定。命名管道的命名規范與以后介紹的郵槽有些類似,
對其標識也是采用的UNC?格式:
\\[轉存失敗重新上傳取消host.name?
]\Pipe\[Path]Name
其中,第-?一部分“\[host_name]”指定了服務器的名字,命名管道服務即在此服務
器創建,其字符串部分可表示為一一個小數點(表示本機)、星號(當前網絡字段)、域名
或是一一個真?正的服務;?第二部分“Pipe”與郵槽的“Mailslot”
樣是一一個不可變化的
硬編碼字符串,以表示該文件是從屬于NPFS;?第三部分“[Path]Name"?則使應用程序
可以唯一-定義及標識-?一個命名管道的名字,而且可以設置多級目錄。
dwOpenMode:?管道建方式。可以是下面值的組合:
>?PIPE?ACCESS?INBOUND:?管道只能用作接收數據。
4
PIPE?ACCESS?_OUTBOUND:?管道只能用作發送數據。
PIPE.ACCESS_?DUPLEX:?管道既可以發送也可以接收數據。
上面這三三個值只能夠取其中-?一個,同時也可包括以下的-?一個或兩個標識:
管道用于同步發送和接收數據,在系統內部
FILE?FLAG?WRITE?THROUGH:
對于命名管道的處理上不經緩沖區并能直接發送,并且只有在數據被發送到目
標地址時發送函數才會返回。如果不設置這個參數,那么需要在數據積累到一
定量時才發送,并且對于發送函數的調用會馬上返回。
?>?FILE?FLAG?OVERLAPPED:?管道可以用于異步輸入和輸出,異步讀寫的有關
方法和文件異步讀寫是相同的。
dwPipeMode:?命名管道模式。可以是下面值的組合:
>?PIPE_TYPE_BYTE:?數據在通過管道發送時作為字節流發送,不能與
PIPE?READMODE?MESSAGE?共用。
PIPE.TYPE.MESSAGE:?數據在通過管道發送時作為消息發送,不能與
PIPE?READMODE?BYTE?共用。
IPE_READMODE_BYTE:?在接收數據時接收字節流。
令
PIPE_READMODE_MESSAGE:?在接收數據日接收消息。
IPE?_WAIT:?使用等待模式,在讀、寫和建立連接時都需要管道的另-?-方完成相
令
應動作后才會返回。
IPE?NOWAIT:?使用#I?等待模式,在讀、寫和建立連接時不需要管道的另-?一方
完成相應動作后就會立即返回。
該管道最大的實例數量。在第-?一次建立服務器方管道時這個參數表
nMaxInstances:
明該管道可以同時存在的數量。PIPE?UNLIMITED?INSTANCES?表明不對數量進行
限制。
nOutBufferSize?和nnBufferSize:?輸出和輸入緩沖區大小。
nDefaultTimeOut:?指定默認的超時時間(以亳秒為單位),如果在創建時設置為
NMPWAIT.USE?DEFAULT.WAIT?表明無限制的等待,而以后服務器方的其他管道實例
也需要設置相同的值。
lpSecurtytltrbutes:?描述安全信息的一一個結構,一般設置為NULL。如果創建或打
開失敗則返回INVALID?HANDLE?VALUE。可以通過GetLastErrort0得到錯誤信息。
其他管道函數簡介如下所述。
CallNamedPipeO:?連接到一個命名管道,讀取或寫入數據之后關閉它。
ConnectNamedPipeQ:?服務進程準備好一個連接到客戶進程的管道,并等待一
個客戶進程連接上為止。
DiscocctNamedPipc:?服務端用來斷開與客戶端的連接。
GetNamedPipeHandleta?獲取一個命名管道的狀態信息。
獲取一個命名管道的信息。
GetNamedPipeInfoO
PeekNamedPipeO:?從一個匿名或命名管道中復制數據到一個緩沖區。
SetNamedPipeHandleStat?設置管道的類型及其他狀態信息,比如說是比特
流還是消息流管道。
TransactNamedPipeO:?從一?個消息管道讀?息或向其?寫入肖息。
WaitNamedPipeO:使服務器進程等待來自客戶的實例連接。
?
?
2.命名管道服務端與客戶端之間通信的實現流程:
?--?-4.2.2
命名管道服務端與客戶端之間通信的實現流程一
(1)?連接建立
使用消息管道、郵槽和套接字通信
服務端通過函數CreateNamedPipe()創建-?一個命名管道的實例并返回用于今后操作
的句柄,或為已存在的管道創建新的實例。如果在已定義超時值變為0?以前,有一一個實
例管道可以使用,則創建成功并返回管道句柄,并用以偵聽來自客戶端的連接請求,該
功能通過ConneclNamedPipe(實現。
另-?一方面,客戶端通過函數WaitINamedPipe()使服務進程等待來自客戶的實例連接。
如果在超時值變為0以前,有一一個管道可以為連接使用,則WaitNamedPipe()返回TRUE,
并通過調用CreateFile()或CallINamedPipe()來呼叫對服務端的連接。此時服務端將接受客
戶端的連接請求,成功建立連接,服務端ConnectNamedPipe(返回TRUE,客戶端
CreatcFile()返回-?一個指向管道文件的句柄。
從時序上講,首先是客戶端通過WaitNamedPipe()使服務端的CreateFile()在限定時間
內創建實例成功,然后雙方通過ConnectNamedPipe()和CreateFile()成功連接,并返回用
于通信的文件句柄,此時雙方即可進行通信。
(2)?通信實現
建立連接之后,客戶和服務端可以通過得到的管道文件句柄利用ReadFile()和
WriteFile()進行彼此間的信息交換。
(3)?連接終止
當客戶端與服務端的通信結束,或由于某種原因一一方需要斷開時,客戶端應調用
CloseFile(),而服務端應接著調用DisconnectNamedPipe。當然服務端亦可通過單方面
調用DisconnectNamedPipe()終止連接。最后應調用CloseHandle()來關閉該管道。
?
?
?
Server端 (1.啟動服務 創建管道和事件2.創建線程負責與客戶端通信(利用IO重疊與創建的事件聯系)。3.接收客戶端數據做出計算再反饋到客戶端)
一 啟動服務StartServer
1.創建管道的名字 CString PipeFullPathData = L"\\\\.\\Pipe\\NamedPipePipe";
2.創建管道 分配事件(因為是NamePipe),通過線程來通信
3.代碼
void CServerDlg::OnBnClickedButtonStartServer()
{
UpdateData(TRUE);
CString PipeFullPathData = L"\\\\.\\Pipe\\NamedPipePipe";
if (m_CEdit_Max_Connect_Count > 0 && m_CEdit_Max_Connect_Count < 100)
{
for (UINT i = 0; i < m_CEdit_Max_Connect_Count; i++)
{
// 創建管道實例
m_UserData[i].PipeHandle = CreateNamedPipe(PipeFullPathData, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, \
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, m_CEdit_Max_Connect_Count, 0, 0, 1000, NULL);
if (m_UserData[i].PipeHandle == INVALID_HANDLE_VALUE)
{
DWORD ErrorCode = GetLastError();
this->MessageBox(L"創建管道錯誤!");
return;
}
// 為每個管道實例創建一個事件對象,用于實現重疊IO
m_UserData[i].EventHandle = CreateEvent(NULL, FALSE, FALSE, NULL);
// 為每個管道實例分配一個線程,用于響應客戶端的請求
m_UserData[i].ThreadHandle = AfxBeginThread(ThreadProcedure, &m_UserData[i], THREAD_PRIORITY_NORMAL);
}
{
this->SetWindowText(L"命名管道—服務器(運行)");
this->MessageBox(L"服務啟動成功");
}
}
}
事件線程函數(利用的是IO重疊)注意的是BufferData的傳入 兩個或多個數據的傳入這樣做
UINT ThreadProcedure(LPVOID ParameterData)
{
DWORD ReturnLength = 0;
char BufferData[0x400] = { 0 };
USER_DATA UserData = *(USER_DATA*)ParameterData;
OVERLAPPED Overlapped = { 0, 0, 0, 0, UserData.EventHandle};
while (1)
{
memset(BufferData, 0, sizeof(BufferData));
// 命名管道的連接函數,等待客戶端的連接(只針對NT)
ConnectNamedPipe(UserData.PipeHandle, &Overlapped);
// 實現重疊I/0,等待OVERLAPPED結構的事件對象
WaitForSingleObject(UserData.EventHandle, INFINITE);
// 檢測I/0是否已經完成,如果未完成,意味著該事件對象是人工設置,即服務需要停止
if (!GetOverlappedResult(UserData.PipeHandle, &Overlapped, &ReturnLength, true))
break;
// 從管道中讀取客戶端的請求信息
if (!ReadFile(UserData.PipeHandle, BufferData, 0x400, &ReturnLength, NULL))
{
MessageBox(0, L"讀取管道錯誤!", 0, 0);
break;
}
int v1, v2;
sscanf(BufferData, "%d %d", &v1, &v2); //字符串轉換為%d
__ServerDlg->m_v1 = v1;
__ServerDlg->m_v2 = v2;
__ServerDlg->m_v3 = v1 + v2;
memset(BufferData, 0, sizeof(BufferData));
sprintf(BufferData, "%d", __ServerDlg->m_v3);//從客戶端到主控端出現
// 把反饋信息寫入管道
WriteFile(UserData.PipeHandle, BufferData, strlen(BufferData), &ReturnLength, NULL);
__ServerDlg->SetDlgItemInt(IDC_EDIT_V1, v1, true);
__ServerDlg->SetDlgItemInt(IDC_EDIT_V2, v2, true);
__ServerDlg->SetDlgItemInt(IDC_EDIT_V3, __ServerDlg->m_v3, true);
// 斷開客戶端的連接,以便等待下一客戶的到來
DisconnectNamedPipe(UserData.PipeHandle);
}
return 0;
}
關閉服務
void CServerDlg::OnBnClickedButtonStopServer()
{
DWORD dwNewMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT;
for (UINT i = 0; i < m_CEdit_Max_Connect_Count; i++)
{
// 設置重疊I/O的事件,使得工作線程安全結束
SetEvent(m_UserData[i].EventHandle);
Sleep(1);
CloseHandle(m_UserData[i].ThreadHandle);
CloseHandle(m_UserData[i].PipeHandle);
}
this->SetWindowText(L"命名管道—服務器(停止)");
this->MessageBox(L"停止啟動成功");
}
Client端(提交數據到主控端,主控端反饋最終結果到客戶端)
void CClientDlg::OnBnClickedButtonSubmitClient()
{
// TODO: 在此添加控件通知處理程序代碼
HANDLE NamedPipeHandle = CreateFile(L"\\\\.\\Pipe\\NamedPipePipe", GENERIC_READ | GENERIC_WRITE, \
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (NamedPipeHandle == INVALID_HANDLE_VALUE)
{
this->MessageBox(L"打開管道失敗,服務器尚未啟動,或者客戶端數量過多");
return;
}
DWORD ReturnLength = 0;
char BufferData[0x400] = { 0 };
// 把兩個整數(a,b)格式化為字符串
m_v1 = GetDlgItemInt(IDC_EDIT_V1);
m_v2 = GetDlgItemInt(IDC_EDIT_V2);
sprintf(BufferData, "%d %d", this->m_v1, this->m_v2);//v1,v2寫入數組
// 把數據寫入管道
WriteFile(NamedPipeHandle, BufferData, strlen(BufferData), &ReturnLength/*寫入地*/, NULL);
memset(BufferData, 0,0x400);//清空
// 服務器反饋后,讀取服務器的反饋信息
ReadFile(NamedPipeHandle, BufferData, 0x400, &ReturnLength, NULL);
sscanf(BufferData, "%d", &(this->m_v3));//Client寫入 Server輸出
/*因為是客戶端做運算,然后主控端響應再反饋*/
SetDlgItemInt(IDC_EDIT_V3, m_v3, true);
CloseHandle(NamedPipeHandle);
總結
- 上一篇: 腾讯QQ上线智能视频字幕功能:AI实时加
- 下一篇: 第八节: EF的性能篇(一) 之 EF自