---- 在 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 中 处 理, 只 有 关 机 造 成 的 退 出 才 往 系 统 注 册 表 中 写 前 面 那 个 键 值, 这 样 就 达 到 了 我 们 的 目 的。