在Internet Explorer
中,微軟帶有兩個很好的局域網通信工具:Chat 和NetMeeting,它們能使局域網中的用戶通過互發消息文本、電子白板,甚至語音和視頻圖像進行交流,但是它們都需要指定一個服務器才能正常工作。在通常由若干臺Windows
95/98
組成的對等網中,真正適用的消息傳送工具仍然是微軟通過網絡組件安裝的WinPopup.EXE,但微軟好像忘記了這個小程序,使它從最初發行到現在依然是老樣子,程序界面跟不上時代不說,每次只能發送38
個字節的消息文本,消息不能保存等不足使人感到十分遺憾。既然認為它不好,那我們就自己寫一個。就像VC
++中某個類的增強版都帶有Ex 后綴一樣,我們也決定將增強后的WinPopup.EXE
命名為WinPopup ?Ex.EXE,圖1 是完成后的WinPopupEx 的外觀。
要在局域網中實現計算機之間的通信,可以采用的辦法很多,最容易想到的是針對某一個網絡協議進行編程,如TCP/IP、IPX/SPX
和NetBEUI,但是控制稍顯復雜,不易實現網絡廣播及只能針對某一個協議,顯得不夠靈活。微軟為我們提供了內部進程的通信(IPC)接口,如果按照ISO
的OSI
模型劃分,它工作在會話層,與它的下一層(傳輸層)采用何種協議無關。在IPC
接口中,MailLosts(郵槽)和NamedPipes(命名管道)都可以在服務器進程和客戶機進程之間進行通信,而且不論服務器進程和客戶機進程是駐留在同一臺機器,還是通過網絡聯系在一起,IPC
接口都能正確地將信息從一個進程傳送到另一個進程。而我們要做的就是在網絡中的每臺計算機上以它的“計算機名”建立一個郵槽或命名管道,其他計算機如果要發送信息給某臺計算機,它只需要像打開一個文件一樣(后面您將看到,的確是采用文件操作函數)打開以那臺計算機命名的郵槽或命名管道,然后像寫文件一樣將數據寫入,最后關閉它就完成了一次通信操作。
郵槽和命名管道各有優缺點,命名管道是可靠的,在發送方不能確認接收方已接收到數據時,它會返回一個錯誤,但是它對網絡廣播操作就顯得力不從心;而郵槽則剛好相反,它可以將消息一次傳送給一組計算機,比如一個“工作組”或整個局域網,但它不能保證發送出去的數據一定就被接收方所接收??紤]到WinPopup
使用的是郵槽,為保證連續性,我們也決定采用MailLosts(郵槽)機制,至于通信的不可靠性,您在后面將看到,我們用一點手工代碼就可以彌補它。
在這個增強版本中,我們要實現以下一些WinPopup 沒有的功能:
* 消息可以自動保存, 根據您的選擇最多可以保存30 天;
* 消息大小不再限制在38 字節, 每條消息最多可以達到400 字節;
* 對單個計算機發出的消息, 可以要求接收方確認“已收到";
* 可以廣播消息到局域網中的多個工作組;
* 可將它縮小為系統狀態條圖標, 當有消息到達時,
它可以發出聲音或閃動圖標加以提醒;
* 可定制的消息文本顯示字體和顏色;
* 可選擇讓它開機自動運行;
* 自動收集網絡信息, 您可以在“網絡鄰居”列表中選擇接收人,
而不是手工輸入它。
本文不打算在這里將開發過程中的每一步細節都寫出來,而是只就一些重點問題進行說明,開發環境是Celeron
333、64M、Windows 98 和Visual C ++6.0。
一、接收和發送消息
WinPopupEx
的核心是消息的接收和發送,也就是對郵槽的處理。在程序開始運行時,它會調用函數:
HANDLE CreateMailslot(
LPCTSTR lpName, // 格式:
"\\.\\MailSlot\\ 郵槽名”-本地郵槽
DWORD nMaxMessageSize,
// 最大的消息文本長度,幫助文檔上說
將該值設為0
則消息長度無限,實際上每次收發的消息長度不能超過424 字節
DWORD lReadTimeout, // 讀超時時間(毫秒)
LPSECURITY_ATTRIBUTES
lpSecurityAttributes // Windows 95/98
的安全屬性應設置為NULL
);
建立兩個本地郵槽WinPopup 和WPAnswer,郵槽\\.\\MailSlot\\WinPopup
用于接收消息正文,而郵槽\\.\\MailSlot\\WPAnswer
則是為了彌補郵槽機制傳送消息的不可靠。當郵槽建立成功后,程序就在主線程之外新啟動一個工作線程,這個線程不停地檢查郵槽\\.\\MailSlot\\WinPopup,當郵槽不為空(有消息到達)時,它首先查看消息數據包中的發送方名字,如發送方名為B,則它向郵槽\\B\\MailSlot\\WPAnswer
發送一個極短的標志文本,以通知發送方自己已經收到它發來的消息,然后向主線程發送一條自定義消息,通知主線程有消息到達,主線程在該自定義消息處理函數中從郵槽\\.\\MailSlot\\WinPopup
里讀出消息正文并將它顯示給用戶。如果計算機A 要向計算機B
發送消息,它只需將消息正文按一定格式的數據包寫入郵槽\\B\\MailSlot\\WinPopup
中,然后在預定義的延遲時間后,檢查本地郵槽\\.\\MailSlot\\WPAnswer
是否有計算機B
返回的應答標志文本,就可知道接收方是否已收到消息。
檢查郵槽中是否有消息到達使用函數:
BOOL GetMailslotInfo(
HANDLE hMailslot, // 郵槽句柄
LPDWORD lpMaxMessageSize,
// 指向存放最大消息長度的變量的指針
LPDWORD lpNextSize,
// 指向存放下一條消息長度的變量的指針
LPDWORD lpMessageCount,
// 指向存放消息條數的變量的指針
LPDWORD lpReadTimeout
// 讀超時時間(毫秒)
);
如果( *lpNextSize) != MAILSLOT_NO_MESSAGE,則說明有消息到達。
從郵槽中讀取消息同從文件中讀取數據沒有區別:
BOOL ReadFile(
HANDLE hFile, // 句柄(這里是郵槽)
LPVOID lpBuffer, // 接收數據的緩沖區指針
DWORD nNumberOfBytesToRead, // 要讀取的字節數
LPDWORD lpNumberOfBytesRead,
// 指向存放已讀取字節數的變量的指針
LPOVERLAPPED lpOverlapped
// 指向OVERLAPPD(重疊I/O) 結構的指針
);
寫入消息到郵槽遵循一般文件的建立、寫入和關閉三個步驟:
建立:HANDLE CreateFile(
LPCTSTR lpFileName,
// 文件名,通常是對方計算機的郵槽名,
如:// "\\B\\MailSlot\\WinPopup"
DWORD dwDesiredAclearcase/" target="_blank" >ccess,
// 存取模式,一般是:GENERIC_WRITE
DWORD dwShareMode,
// 共享模式,一般是:FILE_SHARE_READ
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
// Windows 95/98 的安全屬性應設置為NULL
DWORD dwCreationDisposition,
// 如何建立,一般是:OPEN_EXISTING
DWORD dwFlagsAndAttributes,
// 文件屬性,一般是:FILE_ATTRIBUTE_NORMAL
HANDLE hTemplateFile // 設置為NULL 即可
);
寫入:BOOL WriteFile(
HANDLE hFile, // 文件句柄
LPCVOID lpBuffer, // 要寫的數據緩沖區指針
DWORD nNumberOfBytesToWrite, // 要寫入的字節數
LPDWORD lpNumberOfBytesWritten,
// 指向存放已寫入字節數的變量的指針
LPOVERLAPPED lpOverlapped
// 指向OVERLAPPD(重疊I/O) 結構的指針
);
關閉:BOOL CloseHandle(
HANDLE hObject // 文件句柄
);
二、消息數據包格式
消息正文的數據包格式為:
{
UINT m_uMID; // 唯一表示本消息的ID
char m_cNeedAnswer; // 是否需要應答
char m_cEntirNet; // 是否廣播到“整個網絡"
LPCTSTR m_lpcsTo;
// 接收人顯示姓名( 轉換“整個網絡" 為“*")
LPCTSTR m_lpcsMessage; // 消息正文
}
應答消息包的格式為:
{
UINT m_uMID; // 表示要應答的消息的ID
(UINT)
LPCTSTR m_lpcsTo; // 應答接收人(LPCTSTR)
}
請注意上面的兩個數據包格式中都包含一個ID
值,原因比較有趣:就像我們前面說過的那樣,郵槽是工作在會話層,與下一層(傳輸層)采用何種協議無關。但是,下層的每種協議都是單獨與郵槽機制綁定在一起的,其結果就是當您通過郵槽發送數據時,對方計算機不只收到一條消息,而是若干條一樣的消息,數量是兩臺計算機安裝的通信協議數量的最小值,比如說計算機A
安裝有TCP/IP、IPX/SPX 和NetBEUI 三種協議,計算機B 安裝有TCP/IP 和NetBEUI
兩種協議,那么計算機A 向計算機B 通過郵槽發送消息,則計算機B
將會收到兩條一樣的消息。為了過濾掉多余的消息,我們給每條消息生成一個唯一的隨機數ID,接收消息時只保留其中一條,其余的簡單拋棄即可。
三、界面
我們一直認為系統托盤區是桌面上比較敏感的區域,只有那些對某個事件進行監視的應用才應該在系統托盤區放置圖標,否則只能使人反感。而WinPopupEx
正好符合這個條件,它將一直在后臺運行,當有消息到達時,我們不停地閃動圖標并通過系統音頻發出電話振鈴的聲音,以這種方式提醒用戶,直到程序被用戶手工切換到前臺,見圖2。既然在系統托盤區放置了圖標,那么系統任務條按鈕就不需要了,它被函數ShowWindow(
SW_HIDE ) 隱藏了起來。
程序的主窗口被分為上下兩部分,上面是一個ListCtrl,它的內容包括消息發送人、接收人、接收時間和消息正文的摘要;下面是一個RichEditCtrl,通過選擇上面列表中的項目,這里將會顯示該消息正文的詳細內容。這兩個子窗口的字體和顏色都是可以定制的。
四、消息的保存
原來的WinPopup
最不足的地方就是歷史消息不能保存下來,每次重新打開它都是一片空白。而我們通過網絡的交流一般都希望保存下來以后再看看。這個功能實現起來并不復雜,每次程序被關閉時,它都將所有的消息寫入處于同一目錄下的WinPopupEx.History
文件中,每次運行時也從這個文件中讀入,并將它填入程序對應的消息結構中即可。
五、開機自動運行
要讓一個程序開機自動運行并不是一個新技術,您只需往系統注冊表中新建一個鍵值就可以實現,即在“Software\\Microsoft\\windows\\CurrentVersion\\Run"
下新建一個鍵,鍵名為“WinPopupEx",值為您的WinPopupEx.EXE
所在的磁盤路徑。讓我們考慮另外一種情況:“如果關機時WinPopupEx
仍在運行,請在下次開機時自動運行它”。這就需要一點技巧,我們要注意兩條Windows
消息,一個是WM_QUERYENDSESSION,每當Windows
準備關閉時,它都會向所有運行的程序發送這條消息,通知系統準備關機,這時我們用一個BOOL
變量將這個信息保存起來,如:theApp.m_bShutDown = TRUE,并返回TRUE
同意關閉系統;另一個是WM_ENDSESSION,當Windows 從所有程序的WM_QUERYENDSESSION
處理結果那里都得到TRUE,它就將以TRUE 為參數再次廣播WM_ENDSESSION
消息,如果某個程序的WM_QUERYENDSESSION 處理返回FALSE,那么將以FALSE
為參數。在我們的WM_ENDSESSION
消息處理中,通過判斷那個參數就可以確定本次程序的退出是否是因為系統關機,這個信息被保留到WM_CLOSE
中處理,只有關機造成的退出才往系統注冊表中寫前面那個鍵值,這樣就達到了我們的目的?! ?/p>