win32管道技術和進程通信實例(二)


《win32內核對象共享和進程通信實例(一)》,先繼續了解一下windows匿名管道。

windows管道實質是一塊共享內存,可用於進程間通信。windows管道分為匿名管道和命名管道。匿名管道用戶本地進程間通信;命名管道可以用戶網絡通信。

匿名管道

①dos下的管道操作符|

最開始接觸管道這個概念是dos命令或者linux命令吧。比如dos下可以使用type test.txt|more實現對於test.txt的逐屏顯示。中間的“|”即為管道操作符,對其解釋是:把左邊的輸出作為右邊的輸入。

好好理解這句話,管道操作符“|”的作用是“把左邊的輸出作為右邊的輸入”,確切地來說,應該是把type test.txt的內容先輸出到管道,然后把管道的內容作為more命令的輸入(?這里可能有問題,但是先讓我這么認為)。

本來type test.txt默認是輸出到屏幕設備(執行type test.txt命令,它在屏幕打印出test.txt的內容),這個是它的標准輸出,使用管道操作符之后,把它的從原來的輸出到屏幕設備變成輸出到管道。這種改變原來標准輸入輸出方式的行為,也被稱為輸入輸出重定向。管道操作符是輸入輸出重定向的一種。一個重定向的簡單的例子是,執行 type test.txt>1.txt,這個就是把type test.txt的輸出在屏幕設備上變成輸出在1.txt文件中。重定向操作符參看下表:

重定向操作符

說明

>

將命令輸出寫入到文件或設備(例如打印機)中,而不是寫在命令提示符窗口中

<

從文件中而不是從鍵盤中讀入命令輸入

>>

將命令輸出添加到文件末尾而不刪除文件中的信息

>&

將前一個句柄的輸出寫成后一個句柄的輸入

<&

從后一個句柄讀取輸入並寫入到前一個句柄輸出中

|

從一個命令中讀取輸出並將其寫入另一個命令的輸入中。也稱作管道

那么問題又來了,這些操作符是怎么改變命令的輸入輸出方式的?

應該是通過改變它們的輸入輸出句柄。可以這么認為,標准輸入輸出指的是設備,比如屏幕設備,鍵盤設備,通過輸入輸出句柄可以引用這些設備。如鍵盤輸入用0表示,窗口用1表示,具體可以參照下面的表格:

句柄

數值

說明

STDIN

0

鍵盤輸入

STDOUT

1

輸出到命令提示符窗口

STDERR

2

錯誤輸出到命令提示符窗口

UNDEFINED

3~9

程序自定義

 
當執行 type test.txt>1.txt的時候,等同於type test.txt 1>1.txt,把輸出句柄指向1.txt文件;可以改變一下這個命令,type test.txt 2>1.txt,這就是把錯誤輸出句柄指向1.txt,這個命令的標准輸出還是屏幕,所以會在屏幕輸出內容,與此同時,會把錯誤信息輸出到1.txt,當然這個命令沒有錯誤。不妨換個error 2>1.txt。
再來測試一個命令:error >c.txt 2>&1。error是一個錯誤的命令,直接運行error會將錯誤輸出到命令提示窗口,這里使用2>&1,就是把error命令的標准錯誤輸出變成標准輸出。所以它的錯誤輸出結果能保存在c.txt中。
另外,也可以嘗試直接把標准輸出變成標准輸入,標准輸入變成標准輸出。但是這應該是不可能的?因為怎么把數據輸出到鍵盤,而又讓屏幕輸入數據呢?所以就使用一塊內存,把一個進程的數據輸出到這塊內存,另一個進程從這塊內容中讀取數據,但是剪切板好像也是這樣,既然管道的實質是一塊內存,那本質上跟剪切板應該是一樣:用一塊內存來保存數據。區別也許是網內容讀取和寫入數據的方式不一樣。
 ②win32匿名管道
回顧一下win32創建匿名管道的過程。管道是核心對象,核心對象是一塊內存,這樣說好像沒有問題。主進程先創建一個匿名管道,得到管道的讀寫句柄,那可以認為管道有兩個接口,讀和寫接口;怎么讀,當然就是使用ReadFile和WriteFile讀寫。
主線程把這兩個句柄設置為可繼承的,然后創建一個子進程,讓子進程繼承自己可以操作的內核對象(當然也包括了管道的讀寫權限)。這樣一來,有了三個對象,主進程、子進程和可以被兩個進程讀寫的管道。
這樣看起來就結束了。父子進程都可以通過管道讀寫句柄往管道里讀寫數據了。但是問題是,雖然子進程從父進程繼承了對於管道的讀寫權限,但是它怎么獲得這兩個句柄?
對於子進程來說,它並不知道自己繼承了哪些核心對象。所以要做的就是在主進程創建子進程,子進程生成窗口的時候,把管道的讀寫句柄賦值給子進程的標准輸入輸出句柄。這里有兩層含義:一、父進程顯示地告訴子進程,你能繼承這兩個句柄;二、子進程用來接收句柄的恰好是它的標准輸入輸出。
另外我也注意到創建管道的時候,並不能為管道取一個名字,所以使用為對象命名的方法實現內核對象在進程中的共享就不能了。那子進程能不能通過讀自己的句柄表來獲得管道的讀寫句柄呢?我覺得應該不可以吧,因為句柄表的結構沒有官方的說明文檔,是不是意味着也沒有官方的API來讀取它里面的數據?
另外把子進程的標准輸入和標准輸出設置成管道的讀寫句柄,這意味着什么?我開始想的是那子進程按照標准輸入輸出的數據,就會變成往管道取數據和往管道存數據(這樣理解應該是對的);但是我之前測試的程序,是子進程通過GetStdHandle獲得它的標准輸入輸出句柄,而在父進程中已經把他們設置成了管道的讀寫句柄,所以子進程獲取到的就是管道的讀寫句柄。從這個角度來說,把這一過程理解為單純傳值好像要好一些。
那么問題來了,管道和剪貼板有啥區別?
剪貼板是系統設置一段內存空間,應用程序只能打開,不能創建一個剪貼板,而且要自己開辟一塊全局的內存,然后使用SetClipboardData只是把內存的句柄給剪貼板。管道是內核對象,自己本身是應用創建的一塊內存,這個內存並不是全局的,而是進程間共享的。
③說明
關於匿名管道就先這么理解吧,不知道有沒有理解錯。
命名管道
 匿名管道只能用於相關進程(父子進程和兄弟進程)之間的通信,不能用於網絡環境,即跨網絡傳輸數據。
命名管道則用於非關聯進程和不同計算機上的進程通信。一個管道的所有實例共享同一個命名管道,但是每一個實例均擁有獨立的緩存與句柄,並且為“客戶-服務”通信提供一個分離的管道。命名管道是圍繞Windows文件系統而設計的一種機制,采用的是命名管道文件系統(Named Pipe File System,NPFS)接口,對數據的收發也采用文件讀寫函數來完成。在設計上,由於命名管道也利用了微軟網絡提供者(MSNP)重定向器,因此無需設計底層的通信協議細節。在Windows2000下命名管道與I/0子系統緊密聯系在一起,實際上它實現的是一個文件系統,用戶看到的管道只不過是另一個文件系統。《Window命名管道技術的分析與實現》
命令管道可以說是一種網絡編程方案,它實際上建立了一個客戶機/服務器通信體系,並在其中可靠地傳輸數據。實現命名管道通信,需要有一個服務端和至少一個客戶端。
編寫命名管道服務端程序

 ①使用CreateNamedPipe函數創建一個命名管道實例,並且為隨后的管道操作返回一個句柄。一個命名管道服務進程調用該函數可以創建一個特定的命名管道的第一個實例,並設置它的基本屬性,也可以創建一個已經存在的命名管道的新的實例。

HANDLE CreateNamedPipe(
  LPCTSTR lpName,         // pointer to pipe name
  DWORD dwOpenMode,       // pipe open mode
  DWORD dwPipeMode,       // pipe-specific modes
  DWORD nMaxInstances,    // maximum number of instances
  DWORD nOutBufferSize,   // output buffer size, in bytes
  DWORD nInBufferSize,    // input buffer size, in bytes
  DWORD nDefaultTimeOut,  // time-out time, in milliseconds
  LPSECURITY_ATTRIBUTES lpSecurityAttributes  // pointer to security attributes
);

第一個參數,指向管道名字字符串指針,字符串必須是如下格式:
\\.\pipe\pipename
第二個參數,管道打開模式,每一個管道的實例必須設置成相同的模式。當它設置為PIPE_ACCESS_DUPLEX,表示管道是雙向的,服務端和客戶端都可以從管道讀寫數據。

與此同時還可以設置FILE_FLAG_OVERLAPPED標識。表示開啟重疊模式,重疊模式開啟之后,那些可能需要花很多時間去操作的讀寫和連接操作能夠立即返回。該模式可以讓前台的線程把耗時的操作放在后台執行而去執行其他的操作。例如在重疊模式下,一個線程可以處理多個管道實例的同步輸入輸出(I/O)操作,或者在相同的管道上同步讀寫操作。如果沒有設置重疊模式,管道上的讀寫和連接操作要等操作完成之后才返回。

第三個參數,管道特性模式。指定管道句柄的類型,讀和寫。這對應命名管道的兩種通信模式,即字節模式和消息模式,第三個參數設置為PIPE_TYPE_BYTE表示使用字節模式通信;設置為PIPE_TYPE_MESSAGE表示使用消息模式通信。
第四個參數,指定管道最多可以創建多少個實例。所有實例必須指定相同的數目。可接受的值為1到PIPE_UNLIMITED_INSTANCES,如果設置為PIPE_UNLIMITED_INSTANCES,表示可根據系統資源的能力上限創建實例個數。
第五個參數,為輸出緩存預留字節數。
第六個參數,為輸入緩存預留的字節數。
第七個參數,指定一個超時值,以毫秒為單位,如果后面使用WaitNamedPipe指定了NMPWAIT_USE_DEFAULT_WAIT,每個實例必須設置相同的值。
第八個參數,指向安全屬性的結構。主要用來確認創建的管道對象能否被子進程繼承。

②使用ConnectNamedPipe去等待一個客戶端進程連接一個命名管道的實例。

BOOL ConnectNamedPipe(
  HANDLE hNamedPipe,          // handle to named pipe to connect
  LPOVERLAPPED lpOverlapped   // pointer to overlapped structure
);

第一個參數,命名管道實例的句柄,即CreateNamedPipi的返回值。
第二個參數,指向OVERLAPPED結構體。

如果在CreateNamedPipe的時候設置了FILE_FLAG_OVERLAPPED標識,即開啟了重疊模式,這里就要傳入一個OVERLAPPED的結構體指針,同時OVERLAPPED結構體必須包含一個人工重置對象的事件句柄(MSDN上是這么說的)。

OVERLAPPED結構體包含了用於異步輸入/輸出(I/0)的信息。這里只用到最后一個成員即hEvent。

typedef struct _OVERLAPPED { // o 
    DWORD  Internal; 
    DWORD  InternalHigh; 
    DWORD  Offset; 
    DWORD  OffsetHigh; 
    HANDLE hEvent; 
} OVERLAPPED; 

這個過程是怎么樣的呢?我們首先用CreateEvent創建一個人工重置事件,並設置成無信號,並將返回的event對象句柄賦值給OVERLAPPED結構體的hEvent,這樣一來,有客戶端連接管道實例的時候,event就會變成有信號狀態。我們使用WaitForSingleObject等待event變為有信號狀態,然后讓程序返回。

之前有說過,管道的等待連接操作是一個耗時操作,如果不設置為重疊模式,就會一直等待,服務端也會阻塞。當然也可以創建一個新線程,讓程序做別的事。

③使用ReadFile和WriteFile讀寫數據。

編寫命名管道客戶端程序

①使用WaitNamedPipe等待一個指定的可連接的命名管道(也就是,管道的服務端進程有一個在等待的ConnectNamedPipe操作),直到超時

BOOL WaitNamedPipe(
  LPCTSTR lpNamedPipeName,  // pointer to name of pipe for which to wait 
  DWORD nTimeOut            // time-out interval, in milliseconds
);

 第一個參數,指向在等待連接的管道的名稱,要使用這樣的格式:

\\servername\pipe\pipename
如果服務端在本地,那么servername設置為.
第二個參數,設置以毫秒為單位的超時。除了使用毫秒數字之外,還可以使用下面兩個值:
NMPWAIT_USE_DEFAULT_WAIT,表示使用服務端CreateNamePipe指定的超時值。
NMPWAIT_WAIT_FOREVER,如果沒有管道可用那就一直等待。

 ②使用CreateFile打開管道

HANDLE CreateFile(
  LPCTSTR lpFileName,          // pointer to name of the file
  DWORD dwDesiredAccess,       // access (read-write) mode
  DWORD dwShareMode,           // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
                               // pointer to security attributes
  DWORD dwCreationDisposition,  // how to create
  DWORD dwFlagsAndAttributes,  // file attributes
  HANDLE hTemplateFile         // handle to file with attributes to 
                               // copy
);

 最后完成一個實例吧:

服務端代碼

#include<windows.h>
#define IDB_CREATE 1020
#define IDB_WRITE 1021
#define IDB_READ 1022
HINSTANCE hInstance;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){

    hInstance=hInstance;

    WNDCLASS wc;

    wc.cbClsExtra=0;
    wc.cbWndExtra=0;
    wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor=LoadCursor(NULL,IDC_ARROW);
    wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wc.hInstance=hInstance;
    wc.lpfnWndProc=WndProc;
    wc.lpszClassName="NpipServ";
    wc.lpszMenuName=NULL;
    wc.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wc)){
        MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR);
        return 0;
    }

    HWND hwnd=CreateWindow("NpipServ","NamedPipe Server",WS_OVERLAPPEDWINDOW,300,300,350,150,NULL,NULL,hInstance,NULL);

    ShowWindow(hwnd,nShowCmd);
    UpdateWindow(hwnd);
    MSG msg;

    while(GetMessage(&msg,NULL,0,0)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){
    HDC hdc;
    PAINTSTRUCT ps;
    static HANDLE hPipe;
    switch(message){
    case WM_CREATE:

        CreateWindow("Button","創建管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CREATE,hInstance,NULL);
        CreateWindow("Button","寫入數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL);
        CreateWindow("Button","讀取數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL);
        return 0;
    case WM_PAINT:
        hdc=BeginPaint(hwnd,&ps);
        EndPaint(hwnd,&ps);
        return 0;
    case WM_COMMAND:
        switch(LOWORD(wParam)){
        case IDB_CREATE:
        {
            //創建命名管道
            hPipe=CreateNamedPipe("\\\\.\\pipe\\NamedPipeForTest",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,1,1024,1024,0,NULL);
            if(INVALID_HANDLE_VALUE==hPipe){
                MessageBox(hwnd,"Fail to Create Namedpipe","",MB_ICONERROR);
                return 0;
            }

            //
            HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
            if(!hEvent){
                CloseHandle(hPipe);
                MessageBox(hwnd,"Fail to Create Event","",MB_ICONERROR);
                return 0;
            }
        
            OVERLAPPED ov;
            ov.hEvent=hEvent;
            //等待連接
            if(!ConnectNamedPipe(hPipe,&ov)){
                if(ERROR_IO_PENDING!=GetLastError()){
                    CloseHandle(hPipe);
                    CloseHandle(hEvent);
                    MessageBox(hwnd,"Fail to wait a client connect","",MB_ICONERROR);
                    return 0;
                }
            }
            
            WaitForSingleObject(hEvent,INFINITE);
            return 0;
        }
        case IDB_WRITE:
        {
        //寫入
            TCHAR writeBuff[]="The Sky Becomes lively Rise...(from server)";
            DWORD dwWrite;
            if(!WriteFile(hPipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){
                MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR);
                return 0;
            }
            
            return 0;
        }
        case IDB_READ:
        {
            //讀取數據
            TCHAR readBuff[100];
            ZeroMemory(readBuff,sizeof(readBuff));
            DWORD dwRead;
            if(!ReadFile(hPipe,readBuff,100,&dwRead,NULL)){
                MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR);
                return 0;
            }else{
                MessageBox(hwnd,readBuff,"msg",MB_OK);
            }
            return 0;
        }
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,message,wParam,lParam);
}

客戶端代碼

#include<windows.h>
#define IDB_CONNECT 1030
#define IDB_WRITE 1031
#define IDB_READ 1032
HINSTANCE hInstance;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){

    hInstance=hInstance;

    WNDCLASS wc;

    wc.cbClsExtra=0;
    wc.cbWndExtra=0;
    wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.hCursor=LoadCursor(NULL,IDC_ARROW);
    wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    wc.hInstance=hInstance;
    wc.lpfnWndProc=WndProc;
    wc.lpszClassName="NpipeClient";
    wc.lpszMenuName=NULL;
    wc.style=CS_HREDRAW|CS_VREDRAW;

    if(!RegisterClass(&wc)){
        MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR);
        return 0;
    }

    HWND hwnd=CreateWindow("NpipeClient","NamedPipe Client",WS_OVERLAPPEDWINDOW,700,300,350,150,NULL,NULL,hInstance,NULL);

    ShowWindow(hwnd,nShowCmd);
    UpdateWindow(hwnd);
    MSG msg;

    while(GetMessage(&msg,NULL,0,0)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){
    HDC hdc;
    PAINTSTRUCT ps;
    static HANDLE hPipe;
    switch(message){
    case WM_CREATE:
        CreateWindow("Button","連接管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CONNECT,hInstance,NULL);
        CreateWindow("Button","寫入數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL);
        CreateWindow("Button","讀取數據",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL);
        return 0;
    case WM_PAINT:
        hdc=BeginPaint(hwnd,&ps);
        EndPaint(hwnd,&ps);
        return 0;
    case WM_COMMAND:
        switch(LOWORD(wParam)){
        case IDB_CONNECT:
        {
            //連接管道
            if(!WaitNamedPipe("\\\\.\\pipe\\NamedPipeForTest",NMPWAIT_WAIT_FOREVER)){
                MessageBox(hwnd,"Fail to connect namedpipe","",MB_ICONERROR);
                return 0;
            }

            //打開管道
            hPipe=CreateFile("\\\\.\\pipe\\NamedPipeForTest",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
            if(INVALID_HANDLE_VALUE==hPipe){
                MessageBox(hwnd,"Fail to open namedpipe","",MB_ICONERROR);
                return 0;
            }
            return 0;
         }
        case IDB_WRITE:
        {
        //寫入
            TCHAR writeBuff[]="A Bird Fly To Sky..(from client)";
            DWORD dwWrite;
            if(!WriteFile(hPipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){
                MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR);
                return 0;
            }
            
            return 0;
        }
        case IDB_READ:
        {
            //讀取數據
            TCHAR readBuff[100];
            ZeroMemory(readBuff,sizeof(readBuff));
            DWORD dwRead;
            if(!ReadFile(hPipe,readBuff,100,&dwRead,NULL)){
                MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR);
                return 0;
            }else{
                MessageBox(hwnd,readBuff,"msg",MB_OK);
            }
            return 0;
        }
        }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,message,wParam,lParam);
}

運行

 

推薦

window命名管道技術的分析與實現


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM