命名管道實踐

命名管道技術實驗

管道介紹

管道(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`則使應用程序能夠惟必定義及標識一個命名管道的名字,並且能夠設置多級目錄。this

通訊模式

命名管道提供了兩種基本的通訊模式:字節模式和消息模式,可在CreateNamePipe()建立命名管道時分別用PIPE_TYPE_BYTEPIPE_TYPE_MESSAGE標誌進行設定。在字節模式中,信息以連續字節流的形式在客戶與服務器之間流動。這也就意味着對於客戶機應用和服務器應用在任何一個特定的時間段內都沒法準確知道有多少字節從管道中讀出或寫入。在這種通訊模式中,一方在向管道寫入某個數量的字節後並不能保證管道的另外一方能讀出等量的字節。對於消息模式,客戶機和服務器則是經過一系列不連續的數據包進行數據的收發。從管道發出的每一條消息都必須做爲一條完整的消息讀入。在此建議使用消息模式。編碼

實現方法

要想實現一個命名管道服務器必須開發一個應用程序,經過它建立命名管道的一個或多個「實例」,再由客戶機進行訪問。對服務器來講,管道實例實際就是一個句柄,用於從本地或遠程客戶機的應用程序接受一個鏈接請求。按照下面的步驟,能夠寫出一個基本的服務器應用程序。操作系統

  1. 使用API函數CreateNamedPipe建立一個命名管道實例句柄。線程

  2. 使用API函數ConnectNamedPipe在命名管道實例上監聽客戶機的鏈接請求。

  3. 分別使用API函數ReadFileWriteFile從客戶機接收數據或將數據發送給客戶機。

  4. 使用API函數DisconnectNamedPipe關閉命名管道的鏈接。

  5. 使用API函數CloseHandle關閉命名管道實例句柄

實現一個命名管道客戶機時要開發一個應用程序,令其創建與某個命名管道服務器的鏈接。注意客戶機不可建立命名管道實例,它可打開來自服務器的現成的實例。按照下面步驟,能夠編寫一個最基本的客戶機應用程序。

  1. 使用API函數WaitNamePipe等待一個命名管道實例供自已使用。

  2. 使用API函數CreateFile創建與命名管道的鏈接。

  3. 使用API函數WriteFileReadFile分別向服務器發送數據或從中接收數據。

  4. 使用API函數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;

C#語言版

客戶端

在工程中引用AsyncPipe,定義管道對象

private NamedPipeStreamClient pipeClient = new NamedPipeStreamClient("TestC");

發送消息

pipeClient.SendMessage(Encoding.Default.GetBytes(tbSend.Text));

接收消息

1.定義委託

pipeClient.MessageReceived += new MessageEventHandler(pipeClient_MessageReceived);

2.定義回調函數

void pipeClient_MessageReceived(object sender, MessageEventArgs args)
{
    string content = Encoding.Default.GetString(args.Message);
    this.tbReceive.Text += content;
}

Delphi語言版

客戶端

定義管道對象

pipeNameStr:='\\.\Pipe\TestC';
if(WaitNamedPipe(pchar(pipeNameStr),NMPWAIT_WAIT_FOREVER)=FALSE) then
begin
   ShowMessage(format('WaitNamedPipe failed with error %d',[GetLastError()]));
   exit;
end;
pipeHandle := CreateFile(pchar(pipeNameStr),GENERIC_READ or GENERIC_WRITE,FILE_SHARE_WRITE,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED,0);
if pipeHandle = INVALID_HANDLE_VALUE then
begin
   ShowMessage(format('CreateFile failed with error %d',[GetLastError()]));
   exit;
end;

其中,pipeHandlepipeNameStr的定義爲

pipeHandle:HWND;
pipeNameStr:string;

發送消息

if WriteFile(pipeHandle,pchar(EditMessageToSend.Text)^,length(EditMessageToSend.Text),bytesWrite,nil)= False then
begin
   ShowMessage(format('WriteFile failed with error %d',[GetLastError()]));
   exit;
end;

ShowMessage(format('write %d Bytes',[bytesWrite]));

接收消息

function ReadFromPipe(p:Pointer):DWORD;stdcall;
begin
  if pipeHandle <> INVALID_HANDLE_VALUE then
  begin
    while TRUE DO
    begin
      if ReadFile(pipeHandle,buffer,sizeof(buffer),bytesRead,nil)=FALSE then
      begin
        ShowMessage(format('ReadFile failed with error %d',[GetLastError()]));
        Application.Destroy;
      end;
      SendMessage(integer(p),WM_MYMSG,1,1);
    end;
  end ;
  result := 0;
end ;

其中,WM_MYMSG的定義爲

const
  WM_MYMSG = WM_USER+1024;

對應的消息處理函函數聲明爲

procedure display(var msg:TMessage);message WM_MYMSG;

實現爲

procedure TForm1.display(var msg:TMessage);
begin
  EditMessageReceived.Text := EditMessageReceived.Text + buffer;
end;

因爲客戶端須要不斷接收服務器端的消息,因此須要在新線程中執行,防止阻塞主線程。

threadHandle := CreateThread(nil,0,@ReadFromPipe,Ptr(form1.Handle),0,threadId);

其中,threadHandlethreadId的定義爲

threadHandle:HWND;
threadId:Cardinal;
相關文章
相關標籤/搜索