在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 dwDesiredAccess,
// 存取模式,一般是: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 中處理,只有關機造成的退出才往系統注冊表中寫前面那個鍵值,這樣就達到了我們的目的。
處理中,通過判斷那個參數就可以確定本次程序的退出是否是因為系統關機,這個信息被保留到WM_CLOSE 中處理,只有關機造成的退出才往系統注冊表中寫前面那個鍵值,這樣就達到了我們的目的。