-------------------------------------------------------------
命名管道技術實驗
管道介紹
管道(Pipe)是一種程序間的通信機制,Windows、Linux和UNIX都使用這種機制。
管道是通過I/O接口存取的位元組流建立管道後,通過使用作業系統的任何讀或寫I/O系統調用來讀或者寫它。建立管道的程序稱為管道伺服器,連接配接到一個管道的程序為管道客戶機。一個程序在向管道寫入資料後,另一程序就可以從管道的另一端将其讀取出來。Linux管道是通過傳回兩個檔案描述符來實作雙向I/O,而Windows管道是使用單一句柄(類似于Linux檔案描述符)支援雙向I/O的,比Linux管道要複雜得多。
管道分類
- 匿名管道
匿名管道隻能用于相關程序(如父子程序、兄弟程序)之間的通信,并且它建立在記憶體區域。程序終止後,匿名管道也就消失了。匿名管道使得關聯的程序可以互相傳送資訊,通常匿名管道用于重定向子程序的标準輸入輸出,以便于和父程序交換資料。要雙向交換資料必須建立兩個匿名管道。父程序使用寫句柄寫資料到一個管道,子程序使用讀句柄從管道中讀取資料,相應的子程序使用寫句柄寫資料到另一個管道,父程序使用讀句柄從管道中讀取資料。匿名管道是同一台計算機的關聯程序的子程序重定向标準輸入輸出的一種有效方法,但不能用于網絡環境,也不能用于非關聯的程序間。
- 命名管道
命名管道是在管道伺服器和一台或多台管道客戶機之間進行單向或雙向通信的一種命名的管道。一個命名管道的所有執行個體共享同一個管道名,但是每一個執行個體均擁有獨立的緩存與句柄,并且為客戶—服務通信提供一個分離的管道。執行個體的使用保證了多個管道客戶能夠在同一時間使用同一個命名管道。命名管道用于在非關聯程序和不同計算機上的程序間傳送資料。通常命名管道伺服器程序建立使用一個衆所周知的名字或客戶機知道名字的命名管道,知道管道名字的命名管道客戶機程序在管道另一端打開管道,并服從伺服器程序指定的通路限制。在伺服器和客戶機都連接配接到管道後,就可以在管道上使用讀寫操作來交換資料。命名管道在程序間提供一個傳送資料的簡單的程式設計接口,不管程序是否在同一台計算機上。
命名規範
采用的UNC格式:
\\Server\Pipe\[Path]Name
,其中,第一部分
\\Server
指定了伺服器的名字,命名管道服務即在此伺服器建立,其字串部分可表示為一個小數點(表示本機)、星号(目前網絡字段)、域名或是一個真正的服務;第二部分
\Pipe
與郵槽的
\Mailslot
一樣是一個不可變化的寫死字串,以指出該檔案是從屬于NTFS;第三部分`[Path]Name`則使應用程式可以唯一定義及辨別一個命名管道的名字,而且可以設定多級目錄。
通信模式
命名管道提供了兩種基本的通信模式:位元組模式和消息模式,可在
CreateNamePipe()
建立命名管道時分别用
PIPE_TYPE_BYTE
和
PIPE_TYPE_MESSAGE
标志進行設定。在位元組模式中,資訊以連續位元組流的形式在客戶與伺服器之間流動。這也就意味着對于客戶機應用和伺服器應用在任何一個特定的時間段内都無法準确知道有多少位元組從管道中讀出或寫入。在這種通信模式中,一方在向管道寫入某個數量的位元組後并不能保證管道的另一方能讀出等量的位元組。對于消息模式,客戶機和伺服器則是通過一系列不連續的資料包進行資料的收發。從管道發出的每一條消息都必須作為一條完整的消息讀入。在此建議使用消息模式。
實作方法
要想實作一個命名管道伺服器必須開發一個應用程式,通過它建立命名管道的一個或多個“執行個體”,再由客戶機進行通路。對伺服器來說,管道執行個體實際就是一個句柄,用于從本地或遠端客戶機的應用程式接受一個連接配接請求。按照下面的步驟,可以寫出一個基本的伺服器應用程式。
- 使用API函數
建立一個命名管道執行個體句柄。CreateNamedPipe
-
在命名管道執行個體上監聽客戶機的連接配接請求。ConnectNamedPipe
- 分别使用API函數
和ReadFile
從客戶機接收資料或将資料發送給客戶機。WriteFile
-
關閉命名管道的連接配接。DisconnectNamedPipe
-
關閉命名管道執行個體句柄CloseHandle
實作一個命名管道客戶機時要開發一個應用程式,令其建立與某個命名管道伺服器的連接配接。注意客戶機不可建立命名管道執行個體,它可打開來自伺服器的現成的執行個體。按照下面步驟,可以編寫一個最基本的客戶機應用程式。
-
等待一個命名管道執行個體供自已使用。WaitNamePipe
-
建立與命名管道的連接配接。CreateFile
-
WriteFile
分别向伺服器發送資料或從中接收資料。ReadFile
-
關閉打開的命名管道會話。CloseHandle
C++語言版
伺服器
m_hPipe=CreateNamedPipe(“\\\\.\\Pipe\\Test”,PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE/|PIPE_READMODE_BYTE, 1,0,0,1000,NULL);//建立命名管道
if(m_hPipe==INVALID_HANDLE_VALUE)
m_sMessage=“Errorcreatepipe”;
else
{
m_sMessage=“success creat pipe”;
AfxBeginThread(ReadProc,this);//開啟線程
}
由于
ConnectNamedPipe()
函數在沒有客戶機連接配接到伺服器時會無限的等待下去,是以為避免由此引起的主線程的阻塞,而開辟了一個子線程
ReadProc
:
UINT ReadProc(LPVOIDlpVoid)
{
char buffer[1024];//資料緩存
DWORD ReadNum;
CServerView* pView=(CServerView*)lpVoid;//擷取
視句柄
if(ConnectNamedPipe(pView->m_hPipe,NULL)==FALSE)//等待客戶機的連接配接
{
CloseHandle(pView->m_hPipe);//關閉管道句柄
pView->m_sMessage=“error connect”;
pView->Invalidate();
return 0;
}
else
{
pView->m_sMessage=“success connect”;
pView->Invalidate();
//從管道讀取資料
if(ReadFile(pView->m_hPipe,buffer,sizeof(buffer),
&ReadNum,NULL)==FALSE)
{
CloseHandle(pView->m_hPipe);//關閉管道句柄
pView->m_sMessage=“read error”;
pView->Invalidate();
}
else
{
buffer[ReadNum]='\0';//顯示接收到的資訊
pView->m_sMessage=CString(buffer);
pView->Invalidate();
}
return1;
}
}
在客戶同伺服器建立連接配接後,
ConnectNamedPipe()
才會傳回,其下語句才得以執行。随後的
ReadFile()
将負責把客戶寫入管道的資料讀取出來。在全部操作完成後,伺服器可以通過調用函數
DisconnectNamedPipe()
而終止連接配接:
if(DisconnectNamedPipe(m_hPipe)==FALSE)//終止連接配接
m_sMessage=“error terminate connect”;
else
{
CloseHandle(m_hPipe);//關閉管道句柄
m_sMessage=“success terminate connect”;
}
用戶端
用戶端連接配接伺服器并發送資料
CString Message=“[testdata,fromclient]”;//要發送的資料
DWORD WriteNum;//發送的是資料長度
//等待與伺服器的連接配接
if (WaitNamedPipe (“\\\\.\\Pipe\\Test“, NMPWAIT_WAIT_FOREVER)==FALSE)
{
m_sMessage=“error waiting connect”;
Invalidate();
return;
}
//打開已建立的管道句柄
HANDLE hPipe=CreateFile(“\\\\.\\Pipe\\Test”,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hPipe==INVALID_HANDLE_VALUE)
{
m_sMessage=“erroropenpipe”;
Invalidate();
return;
}
else
{
m_sMessage=“successopenpipe”;
Invalidate();
}
//向管道寫入資料
if(WriteFile(hPipe,Message,Message.GetLength(), &WriteNum,NULL)==FALSE)
{
m_sMessage="error data write";
Invalidate();
}
else
{
m_sMessage=“success write”;
Invalidate();
}
CloseHandle(hPipe);
用戶端接收資料
static UINT ReceiveFromPipe(LPVOID pArgs)
{
try
{
CNamedPipeClientCDlg* pDlg = (CNamedPipeClientCDlg*)pArgs;
char szBuf[1024]={0};
memset(szBuf,0,sizeof(szBuf));
DWORD dwRead,dwWrite;
while(1)
{
if(!ReadFile(pDlg->hlPC,szBuf,1024,&dwRead,0))
break;
SetWindowText((HWND)pDlg->m_ReceivedEdit,szBuf);
memset(szBuf,0,1024);
}
}
catch (CMemoryException* e)
{
}
catch (CFileException* e)
{
}
catch (CException* e)
{
}
return 0;
}
由于用戶端需要不斷接收伺服器端的消息,是以需要在新線程中執行,防止阻塞主線程。
hlThread = AfxBeginThread(ReceiveFromPipe,this)
其中,
hlThread
的定義為
HANDLE hlThread;