1.郵槽實(shí)施細(xì)節(jié)
郵槽是圍繞Windows文件系統(tǒng)接口設(shè)計(jì)出來的.客戶機(jī)和服務(wù)器應(yīng)用需要使用標(biāo)準(zhǔn)的Win32文件系統(tǒng)I/O(輸入/輸出)函數(shù),比如ReadFile和WriteFile等等,以便在郵槽上收發(fā)數(shù)據(jù),同時(shí)利用Win32文件系統(tǒng)的命名規(guī)則.郵槽必須依賴Windows重定向器,通過一個(gè)"郵槽文件系統(tǒng)"(Mailslot File System , MSFS), 來創(chuàng)建及標(biāo)識(shí)郵槽.
1.1 郵槽的名字
對(duì)郵槽進(jìn)行標(biāo)識(shí)時(shí),需遵守下述命名規(guī)則:
請(qǐng)將上述字串分為三段來看: \\server, \Mailslot和\[path]name.第一部分\\server對(duì)應(yīng)于服務(wù)器的名字,我們要在上面創(chuàng)建郵槽,并在上面運(yùn)行服務(wù)器程序.第二部分\Mailslot是一個(gè)"硬編碼"的固定字串,用于告訴系統(tǒng)這個(gè)文件名從屬于MSFS.而第三部分\[path]name則允許應(yīng)用程序獨(dú)一無二地定義及標(biāo)識(shí)一個(gè)郵槽名.其中,"path"代表路徑,可指定多級(jí)目錄.舉個(gè)例子來說,對(duì)一個(gè)郵槽進(jìn)行標(biāo)識(shí)時(shí),下面這些形式的名字都是合法的(注意Mailslot不得變化,必須原文照輸).
1.2 消息的長度
郵槽通常用"數(shù)據(jù)報(bào)"(Datagram)在網(wǎng)絡(luò)上傳遞消息.數(shù)據(jù)報(bào)實(shí)際是一些小數(shù)據(jù)包,只不過要以"無連接"的形式,通過網(wǎng)絡(luò)進(jìn)行傳輸."無連接"意味著每個(gè)數(shù)據(jù)包在發(fā)給接收者之后,不要求對(duì)方提供包的收到確認(rèn)信息.顯然,這是一種"不可靠"的數(shù)據(jù)傳輸,因?yàn)闊o法保證消息肯定能正確送達(dá).然而,通過無連接傳輸,我們確實(shí)可將消息從一個(gè)客戶機(jī)廣播給多個(gè)服務(wù)器.當(dāng)然,上述情況也有例外.在Windows NT 和Windows2000中,假如消息的長度超過424個(gè)字節(jié).
若消息長度超過426字節(jié),便必須在一個(gè)SMB會(huì)話之上,通過一種"面向連接"的協(xié)議進(jìn)行傳輸,而不再采用無連接的"數(shù)據(jù)報(bào)"形式.這樣一來,在消息較大的情況下,便可保證它們的穩(wěn)定,高效傳輸.然而,此時(shí)再也不能將一條消息從客戶機(jī)廣播給多個(gè)服務(wù)器.對(duì)于"面向連接"的傳輸來說,它必然是一種"一對(duì)一"通信:一個(gè)客戶對(duì)一個(gè)服務(wù)器,在不同的進(jìn)程之間,使用"面向連接"的傳輸方式,往往可保證數(shù)據(jù)傳輸?shù)目煽啃?
1.3 應(yīng)用程序的編譯
用Microsoft VisualC++編制一個(gè)郵槽客戶機(jī)或服務(wù)器應(yīng)用程序時(shí),必須在程序文件中將Winbase.h這個(gè)包容文件包括在內(nèi).假如已經(jīng)包含了Windows.h(大多數(shù)應(yīng)用程序都會(huì)這樣), 那么也可將Winbase.h省去.此外,應(yīng)用程序也要負(fù)責(zé)建立與Kernel32.lib的鏈接,這通常需要用VisualC++鏈接器標(biāo)志進(jìn)行配置.
1.4錯(cuò)誤代碼
開發(fā)郵槽客戶機(jī)和服務(wù)器應(yīng)用時(shí),所有Win32API函數(shù)(CreateFile和CreateMailslot除外)在調(diào)用失敗的情況下,都會(huì)返回0值.CreateFile和CreateMailslot這兩個(gè)API卻會(huì)返回INVALID_HANDLE_VALUE(無效句柄值)。若這些API函數(shù)調(diào)用失敗,應(yīng)用程序即應(yīng)調(diào)用GetLastError函數(shù),來接收與此次失敗有關(guān)的特殊信息。至于所有錯(cuò)誤代碼的一個(gè)完整列,可參考Winerror.h
2. 基本客戶機(jī)/服務(wù)器
郵槽建立了一個(gè)簡單的客戶機(jī)/服務(wù)器設(shè)計(jì)體系。在這個(gè)體系中,數(shù)據(jù)只能從客戶機(jī)傳到服務(wù)器,數(shù)據(jù)通信是單向進(jìn)行的。服務(wù)器進(jìn)程的職責(zé)是創(chuàng)建一個(gè)郵槽,而且是能從郵槽讀取數(shù)據(jù)的唯一一個(gè)進(jìn)程。郵槽客戶機(jī)進(jìn)程則負(fù)責(zé)打開郵槽“實(shí)例”,該進(jìn)程是能夠向其中寫入數(shù)據(jù)的唯一一種進(jìn)程。
2.1 郵槽服務(wù)器的詳情
若想實(shí)現(xiàn)一個(gè)郵槽, 要求開發(fā)一個(gè)服務(wù)器應(yīng)用,來負(fù)責(zé)郵槽的創(chuàng)建。下述步驟解釋了如何編寫一個(gè)基本的服務(wù)器應(yīng)用:
1)用CreateMailslot API 函數(shù)創(chuàng)建一個(gè)郵槽句柄.
2)調(diào)用ReadFile API函數(shù),并使用現(xiàn)成的郵槽句柄,從任何客戶機(jī)接收數(shù)據(jù)。
3)用CloseHandle這個(gè)API函數(shù),關(guān)閉郵槽句柄。
可以看出,要開發(fā)一個(gè)郵槽服務(wù)器程序,只需使用極少的API調(diào)用。服務(wù)器進(jìn)程是用CreateMailslot這個(gè)API調(diào)用來創(chuàng)建郵槽的。定義如下:
HANDLE CreateMailslot(
LPCTSTR lpName,
DWORD nMaxMessageSize,
DWORD lReadTimeout,
LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
其中, 第一個(gè)參數(shù)lpName指定郵槽的名字,名字的格式如下:
\\.\Mailslot\[path]name
要注意的是,服務(wù)器的名字用一個(gè)小數(shù)點(diǎn)來表示,亦即服務(wù)器就是本地機(jī)器。這樣做是很有必要的,因?yàn)槲覀儾荒茉谶h(yuǎn)程計(jì)算機(jī)上創(chuàng)建郵槽。在lpName參數(shù)中,名字必須以一種獨(dú)一無二的形式表達(dá)??蓪⑺O(shè)為一個(gè)獨(dú)立的名字,也可以在它前面加上一個(gè)完整的目錄路徑。
nMaxMessageSize參數(shù)定義的是可寫入郵槽的一條消息的最大長度(以字節(jié)為單位)。假如客戶機(jī)寫入的字節(jié)數(shù)多于nMaxMessageSize的設(shè)置,服務(wù)器便不會(huì)接收這條消息。若將它的值設(shè)為0,服務(wù)器便會(huì)接收任意長度的消息。
在一個(gè)郵槽上,讀操作可以等待或不等待這兩種模式進(jìn)行,具體由lReadTimeout參數(shù)決定。它以毫秒為單位,指定了讀操作需要等候進(jìn)入消息的時(shí)間。若將它的值設(shè)為MAILSLOT_WAIT_FOREVER,那么在進(jìn)入的數(shù)據(jù)可以讀取之前,讀操作便會(huì)無限期地等待下去。若設(shè)為0,讀操作就會(huì)立即返回。
lpSecurityAttributes參數(shù)決定了為郵槽施加的訪問控制權(quán)限。在Windows NT和Windows2000中,這個(gè)參數(shù)只實(shí)現(xiàn)了一部分,所以同時(shí)還應(yīng)指定一個(gè)null(空)參數(shù)。在郵槽上,唯一能夠施加的安全措施是針對(duì)本地I/O進(jìn)行的----客戶機(jī)試圖將服務(wù)器的名字設(shè)為小數(shù)點(diǎn)(.),以打開一個(gè)郵槽。要想繞過這種安全機(jī)制,客戶機(jī)可指定服務(wù)器的實(shí)際名字,而不是一個(gè)小數(shù)點(diǎn),亦即相當(dāng)于發(fā)出一個(gè)遠(yuǎn)程I/O調(diào)用。在Windows NT和Windows2000中,并未針對(duì)遠(yuǎn)程I/O而實(shí)現(xiàn)lpSecurityAttributes參數(shù),因?yàn)榧偃缑看伟l(fā)出一條消息時(shí),都在客戶機(jī)與服務(wù)器之間建立一個(gè)授權(quán)會(huì)話,那么效率會(huì)顯得十分低下。因此,郵槽僅一部分符合標(biāo)準(zhǔn)文件系統(tǒng)采用的Windows NT和Windows2000安全模型。結(jié)果便是,網(wǎng)絡(luò)中的任何郵槽客戶機(jī)都可將數(shù)據(jù)發(fā)給服務(wù)器。
用一個(gè)有效的句柄創(chuàng)建了郵槽之后,便可開始數(shù)據(jù)的實(shí)際讀取。服務(wù)器是唯一能從郵槽讀入數(shù)據(jù)的進(jìn)程。服務(wù)器應(yīng)使用ReadFile這個(gè)Win32函數(shù),來進(jìn)行數(shù)據(jù)讀取。對(duì)ReadFile的定義如下:
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
CreateMailslot會(huì)返回一個(gè)句柄hFile.lpBuffer和nNumberOfBytesToRead參數(shù)決定了可從郵槽讀入多少數(shù)據(jù)。特別值得注意的是,這個(gè)緩沖區(qū)的大小應(yīng)該比來自CreateMailslot API調(diào)用的nMaxMessageSize參數(shù)的設(shè)置大。此外,緩沖區(qū)應(yīng)該大于郵槽上的進(jìn)入消息;如果不夠大,ReadFile調(diào)用便會(huì)失敗,并返回一個(gè)ERROR_INSUFFICIENT_BUFFER錯(cuò)誤。lpNumberOfBytesRead參數(shù)用于在ReadFile操作完成后,報(bào)告讀入的實(shí)際字節(jié)數(shù)量。
利用lpOverlapped參數(shù),我們可以通過異步方式,進(jìn)行數(shù)據(jù)的讀取。該參數(shù)采用的是Win32重疊I/O機(jī)制,在默認(rèn)情況下,ReadFile操作會(huì)處于暫停狀態(tài),直到有數(shù)據(jù)可以讀入為止。重疊I/O只能在Windows NT和windows20000上完成;如果使用的操作系統(tǒng)是Windows 95或Windows 98,那么應(yīng)將該參數(shù)設(shè)為NULL。在程序清單中我們進(jìn)一步闡釋。
// Serverl.cpp
#include <windows.h>
#include <stdio.h>
void main(void)
{
HANDLE Mailslot;
char buffer[256];
DWORD NumberOfBytesRead;
// Create the mailslot
if ((Mailslot = CreateMailslot(\\\\.\\Mailslot\\Myslot, 0,
MAILSLOT_WAIT_FOREVER, NULL)) == INVALID_HANDLE_VALUE)
{
printf("Failed to create a mailslot %d\n", GetLastError());
return;
}
// Read data from the mailslot forever!
while (ReadFile(Mailslot, buffer, 256, &NumberOfBytesRead, NULL) != 0)
{
printf("%.*s\n", NumberOfBytesRead, buffer);
}
}
2.2 郵槽客戶機(jī)的詳情
要想實(shí)現(xiàn)一個(gè)客戶機(jī),需要開發(fā)一個(gè)應(yīng)用程序,對(duì)一個(gè)現(xiàn)有的郵槽進(jìn)行引用和寫入.下述步驟解釋了如何編寫一個(gè)基本的客戶機(jī)應(yīng)用:
1)使用CreateFile這個(gè)API函數(shù),針對(duì)想向其傳送數(shù)據(jù)的郵槽,打開指向它的一個(gè)引用句柄.
2)調(diào)用WriteFile這個(gè)API函數(shù), 向郵槽寫入數(shù)據(jù).
3) 完成了數(shù)據(jù)的寫入后,用CloseHandle這個(gè)API函數(shù),關(guān)閉打開的郵槽句柄.
如前所述, 采用一種"無連接"的形式,郵槽客戶機(jī)同郵槽服務(wù)器通信.客戶機(jī)打開指向郵槽的一個(gè)引用句柄時(shí),實(shí)際并不建立同郵槽服務(wù)器的一個(gè)連接.要想對(duì)一個(gè)郵槽進(jìn)行引用需要使用CreateFile這個(gè)API調(diào)用.對(duì)它的定義如下:
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreatingDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
lpFileName參數(shù)用于描述一個(gè)或多個(gè)郵槽,我們可用本章早些時(shí)候介紹的郵槽命名格式,向其寫入數(shù)據(jù).dwDesiredAccess參數(shù)必須設(shè)為GENERIC_WRITE, 因?yàn)榭蛻魴C(jī)只能向服務(wù)器寫入數(shù)據(jù).dwShareMode參數(shù)必須設(shè)為FILE_SHARE_READ,允許服務(wù)器在郵槽上打開和進(jìn)行讀操作.lpSecurityAttributes參數(shù)對(duì)于郵槽不會(huì)有什么效果,應(yīng)將其設(shè)為NULL.dwCreatiionDisposition標(biāo)志應(yīng)設(shè)為OPEN_EXISTING。若一臺(tái)機(jī)器既是客戶機(jī),也是服務(wù)器,這一設(shè)置便顯得尤其重要----如果服務(wù)器沒有創(chuàng)建郵槽,對(duì)API函數(shù)CreateFile的調(diào)用便會(huì)失敗.如果服務(wù)器在遠(yuǎn)程工作,那么.dwCreationDisposition參數(shù)便沒什么意義dwFlagsAndAttributes參數(shù)應(yīng)定義成FILE_ATTRIBUTE_NORMAL. hTemplateFile參數(shù)應(yīng)設(shè)為NULL.
成功創(chuàng)建一個(gè)句柄后,便可開始向郵槽寫入數(shù)據(jù).請(qǐng)記住,作為客戶機(jī),只能將數(shù)據(jù)寫入郵槽.這可以用Win32函數(shù)WriteFile來做到,定義如下:
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
其中,hFile參數(shù)是由CreateFile返回的一個(gè)引用句柄。lpBuffer和nNumberOfBytesToWrite參數(shù)決定了有多少字節(jié)從客戶機(jī)發(fā)向服務(wù)器。一條消息的最大長度為64KB。如果當(dāng)初是用一個(gè)域或星號(hào)(*)格式來創(chuàng)建郵槽句柄,那么在Windows NT和Windows2000中,消息的長度限制在424字節(jié)之內(nèi); 而在Windows 95和Windows98中,限制在64KB之內(nèi)。如客戶機(jī)試圖發(fā)送的消息超出了這一長度限制,WriteFile函數(shù)便會(huì)失敗,而且GetLastError函數(shù)會(huì)返回ERROR_BAD_NETPATH錯(cuò)誤。之所以會(huì)出現(xiàn)這一情況,是由于需要以廣播數(shù)據(jù)報(bào)的形式,把消息發(fā)送給網(wǎng)絡(luò)中的所有服務(wù)器。lpNumberOfBytesWritten參數(shù)返回的是當(dāng)WriteFile操作完成后,傳給服務(wù)器的實(shí)際字節(jié)數(shù)量。
通過lpOverlapped參數(shù),我們可采用異步形式,將數(shù)據(jù)寫入一個(gè)郵槽。由于郵槽最大的特點(diǎn)便是“無連接”的數(shù)據(jù)傳輸,所以WriteFile函數(shù)不會(huì)在I/O調(diào)用的時(shí)候暫停等候。在客戶機(jī)上,這個(gè)參數(shù)應(yīng)設(shè)為NULL。在程序中我們進(jìn)一步闡述。
// Client.cpp
#include <windows.h>
#include <stdio.h>
void main(int argc, char *argv[])
{
HANDLE Mailslot;
DWORD BytesWritten;
CHAR ServerName[256];
// Accept a command line argument for the server to send
// a message to
if (argc < 2)
{
printf("Usage: client <server name>\n");
return;
}
sprintf(ServerName, \\\\%s\\Mailslot\\Myslot, argv[1]);
if ((Mailslot = CreateFile(ServerName, GENERIC_WRITE,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
NULL)) == INVALID_HANDLE_VALUE)
{
printf("CreateFile failed with error %d\n", GetLastError());
return;
}
if (WriteFile(Mailslot, "This is a test", 14, &BytesWritten, NULL) == 0)
{
printf("WriteFile failed with error %d\n", GetLastError());
return;
}
printf("Wrote %d bytes \n", BytesWritten);
CloseHandle(Mailslot);
}
3. 其他郵槽API
對(duì)郵槽服務(wù)器應(yīng)用來說,它可使用另外兩個(gè)API函數(shù)同郵槽打交道:GetMailslotInfo和SetMailslotInfo.其中,一旦郵槽上有消息可以傳遞,GetMailslotInfo函數(shù)便可負(fù)責(zé)獲取消息的長度信息.利用這個(gè)函數(shù),程序可針對(duì)長度不定的進(jìn)入消息,動(dòng)態(tài)地調(diào)節(jié)其緩沖區(qū).GetMailslotInfo函數(shù)亦可用來對(duì)進(jìn)入數(shù)據(jù)進(jìn)行"輪詢".對(duì)GetMailslotInfo函數(shù)的定義如下:
BOOL GetMailslotInfo(
HANDLE hMailslot,
LPDWORD lpMaxMessageSize,
LPDWORD lpNextSize,
LPDWORD lpMessageCount,
LPDWORD lpReadTimeout
);
其中, hMailslot參數(shù)指定自CreateMailslot API調(diào)用返回的一個(gè)郵槽. lpMaxMessageSize參數(shù)設(shè)置可將多大的一條消息寫入郵槽(以字節(jié)為單位). lpNextSize參數(shù)則以字節(jié)為單位,指出下一條消息的長度.GetMailslotInfo可能會(huì)返回一個(gè)MAILSLOT_NO_MESSAGE值,指出在郵槽之上,目前沒有等待接收的消息。利用這個(gè)參數(shù),服務(wù)器便可在郵槽上輪詢(不斷地查詢)是否有進(jìn)入的消息,防止應(yīng)用程序在ReadFile函數(shù)調(diào)用過程中“凍結(jié)”,傻乎乎地一直等候下去.然而,用這種方式對(duì)數(shù)據(jù)進(jìn)行輪詢并不是一種很好的習(xí)慣.因?yàn)閼?yīng)用程序會(huì)連續(xù)不停地消耗寶貴的CPU資源,以檢查進(jìn)入的數(shù)據(jù)----即使根本沒有需要處理的消息.這樣一來,便會(huì)降低系統(tǒng)的總體性能.如果想防止ReadFile"凍結(jié)"或暫停執(zhí)行,建立你使用Win32的重疊I/O.lpMessageCount參數(shù)指定一個(gè)緩沖區(qū),用于接收等候讀入的消息的總量.lpReadTimeout參數(shù)則指定了另一個(gè)緩沖區(qū),它以毫秒為單位,返回了一次讀操作最多能等候多久的時(shí)間,讓一條消息寫入郵槽.
SetMailslotInfo函數(shù)用于設(shè)置一個(gè)郵槽的超時(shí)值.超過這個(gè)時(shí)間,讀操作便不再等候進(jìn)入消息.因此,應(yīng)用程序完全有能力將讀操作從"凍結(jié)"狀態(tài)轉(zhuǎn)變成"非凍結(jié)"狀態(tài);或者相反.對(duì)SetMailslotInfo的定義如下:
BOOL SetMailslotInfo(
HANDLE hMailslot,
DWORD lReadTimeout
);
bMailslot參數(shù)指定一個(gè)自CreateMailslot API調(diào)用返回的郵槽. lReadTimeout參數(shù)以毫秒為單位,指定一次讀操作等候一條消息寫入郵槽的最長時(shí)間.若將其設(shè)為0,在不存在消息的前提下,讀操作便會(huì)立即返回; 若設(shè)為MAILSLOT_WAIT_FOREVER, 讀操作便會(huì)永遠(yuǎn)等待下去。
聯(lián)系客服