基於Windows API的命名管道的封裝與使用詳解

峻峰飛陽發表於2019-01-16

       命名管道是一種程式間通訊(RPC)的方式,類似於socket,命名管道的一端為server,另一端為client,client與server之間支援單向或雙向通訊。與socket相比,命名管道更適合本地程式間的通訊,使用更方便和高效。

       與socket相同,命名管道在通訊之前,客戶端和伺服器端必須建立連線。好比兩個人約會,需要事先商量好一個時間在某一個地點(如:某某咖啡廳)見面,見了面後就可以愉快的暢談了。

        命名管道的名字類似於這個約好的咖啡廳,通過這個名字,客戶端與服務端建立連線。

        誰是客戶端誰是服務端,建立連線的過程是怎樣的? 還是以約會為例,兩個人約好了時間在某個咖啡廳見面,肯定有一個人先到有一個人後到,如果先到的人到了咖啡廳看對方沒來,認為對方爽約,就立即離開了,則這樣的約會一定會失敗。所以為了能成功約會,先到的人到了咖啡廳應該等待一段時間,半個小時,1個小時,1天,甚至可以等上10年(忠誠的八公)。這樣一般會等到另一個人到來,約會成功,下面就可以愉快的交談了。先來的人就是服務端,他需要的特殊操作是等待,後來的人是客戶端,他來到咖啡廳後,要找到等待他的人,客戶端的特殊操作就是連線。

         我封裝一個類CPipe,利用Windows提供的API實現了Pipe連線和通訊的功能。

         CreatePipe是服務端呼叫,建立命名管道,為防止重複建立,需要先測試該命名管道是否已經建立(可以使用WaitNamedPipe測試)

BOOL CPipe::CreatePipe(const char* pipename)
{
    if(m_hPipe != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hPipe);
        m_hPipe = INVALID_HANDLE_VALUE;
    }
   
    //建立命名管道
    m_hPipe = CreateNamedPipe(pipename, PIPE_ACCESS_DUPLEX |
        (OVERLAPPED_IO ? FILE_FLAG_OVERLAPPED : 0), PIPE_TYPE_BYTE |
        PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0,
        60000, NULL);
    
    if(m_hPipe == INVALID_HANDLE_VALUE)
    {
        WT_Error("CPipe::CreatePipe: lasterr=%d\n", GetLastError());
        return FALSE;
    }
    
    CreateThread(0, 0, PipeServerListenProc, this, 0, 0);
    WaitForSingleObject(m_hEvent, 2000);    //wait child thread running

    return TRUE;
}

客戶端使用OpenPipe連線命名管道:

BOOL CPipe::OpenPipe(const char* pipename)
{
    if(m_hPipe != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hPipe);
        m_hPipe = INVALID_HANDLE_VALUE;
    }
    
    m_hPipe = ::CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
        OPEN_EXISTING, OVERLAPPED_IO ? FILE_FLAG_OVERLAPPED : 0, NULL);

    if(m_hPipe == INVALID_HANDLE_VALUE)
    {
        WT_Error("CPipe::CreatePipe: lasterr=%d\n", GetLastError());
        return FALSE;
    }
    
    m_bConnected = TRUE;
    CreateThread(0, 0, PipeClientListenProc, this, 0, 0);
    WaitForSingleObject(m_hEvent, 2000);        //wait child thread running

    return TRUE;
}

如果不想區分服務端和客戶端,即:不管是客戶端還是服務端用同一個函式完成管道的連線,可使用BindPipe:

BOOL CPipe::BindPipe(DWORD dwPortId)
{
    char pipename[256];
    
    sprintf(pipename, PIPENAME, dwPortId);
    WT_Trace("BindPipe: name=%s\n", pipename);

    if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT))
    {
        //若命名管道不存在則建立(將成為服務端)
        return CreatePipe(dwPortId);
    }
    else
    {
        //若命名管道存在則連線(將成為客戶端)
        return OpenPipe(dwPortId);
    }
}

 

 

相關文章