使用 RDP API 和套接字进行桌面共享






4.67/5 (18投票s)
您可以使用此软件与任何其他人共享对等方的桌面,您可以想象以任何方式指定共享者和查看者。详细的开发文档和用户手册可在下载中找到。
引言
在电子教室的情况下,教师可能希望将一个指定学生的桌面与一组其他人共享,将另一个指定学生的桌面与另一组共享,或者将自己的桌面与一组指定学生共享,以便这组学生知道如何在学习过程中操作某些问题。
而且,教师可能还想测试某个学生是否真正理解如何操作,以便他/她可以指定该学生作为具有控制共享者桌面的特权的角色。
另一种情况是,教师希望确保学生正在执行他/她分配给他们的任务,因此他/她可以监视一组指定学生的桌面。
环境
开发环境:Windows 8 上的 VS2012
运行环境:Windows 7 或更高版本,Windows 2012 或更高版本。请注意,需要 VC++11 运行时。如果还没有,请先安装。
背景
该软件采用服务器-客户端模型,使用套接字在服务器和客户端之间进行控制和命令传输。在底层,桌面传输功能通过 Microsoft 的 WDS API(http://msdn.microsoft.com/zh-cn/library/windows/desktop/aa373871(v=vs.85).aspx)使用 RDP 实现。
事实上,对于 RDP 服务器,我参考了此链接,甚至直接引用了“CMyRDPSessionEvents
”类来处理 RDP 事件。要理解代码,您需要具备 COM 的基本知识。对于 RDP 客户端,我参考了此链接。RDP 客户端应该是 ActiveX 控件,Microsoft 已经在 MFC 中提供了这样的控件。感谢这两篇文章的作者。
Using the Code
1. 简介
该桌面共享软件分为两部分:服务器端的控制中心和客户端的对等方。事实上,每个对等方都包含 RDP 服务器(作为共享者)和 RDP 客户端(作为查看者),这意味着它拥有共享桌面和查看他人桌面所需的一切。控制中心仅通过套接字命令控制对等方。
2. 控制中心
2.1 整体架构
“控制中心”是服务器端的名称。控制中心在逻辑上分为三个层次。底层包含两个派生自CSocket
的类,它们分别是CDoorSocket
和CListenSock
,分别与客户端和套接字通信。中间层只有一个服务类CService
,提供基本的套接字服务,如发送和接收数据。该类为上层的 UI 类提供服务,在接收到套接字命令或状态字符串时向它们发送消息。它还包含一个列表和一个映射,用于存储对应于客户端的套接字。顶层包含许多 UI 类和一些相关的辅助类。UI 类主要是派生自CDialogEx
的对话框类。
2.2 CService 详细信息
如上所述,CService
类主要通过提供套接字服务来为 UI 类提供服务。
主要方法
SockSend
:用于在需要时向客户端发送命令。OnSockReceive
:一旦套接字从客户端接收到命令或回复命令,将调用此方法。它将根据不同的接收字符串执行操作以更改映射中套接字的状态和/或向相应类发送自定义消息。此方法就像一个消息分发器。
该类还拥有列表和映射作为其属性。它们都用于存储连接到客户端的套接字。但为什么有两个容器呢?情况是当一个客户端连接到控制中心时,将在OnSockAccept
方法中创建一个新的CDoorSocket
实例,稍后当套接字发送一个带有标识客户端自身的名称的“logname
”命令时,该名称-套接字对将被添加到映射中。之后,通常会使用映射中的实体,并以名称作为 ID。
就该类的功能而言,其实例应该是唯一的,即单例。因此,将其作为主对话框的属性远远不够,我将其作为CControlCenterApp
的属性,以便任何类都可以通过单个“theApp
”来使用它。
请注意,该类还包含一些指向CWnd
的指针。它们用于在OnSockReceive
方法中指示消息将发送到何处。显然,这不是一个好的设计。为了在发送消息时传递一些参数,我使用了SendMessage
函数。这可能会对性能产生不良影响。
2.3 UI 设计
该软件是一个 MFC 对话框项目。因此,用户界面是对话框和它们上面的控件的集合。主窗口是一个对话框,其类为CControlCenterDlg
,其中包含以下元素:选项卡控件客户端区域中的三个对话框,左下角的static
控件和对应的隐藏“手动消息”对话框,最后是右下角代表在线客户端数量的static
控件。
2.4 关于 CPeerTreeDlg
此类可能是最复杂的,即使它只是一个包含树控件的对话框。我想让它成为一个可移植的控件,所以我将对话框资源的边框设置为“none
”。
其最基本的功能是显示一个包含按不同组划分的对等方的树。
请注意,有两种方法可以初始构建树。第一种方法是通过“BuildTree
”方法,首先将内存中的一组文件内容读入内存,形式为map<CString, list<CString>* >
,第一个元素是组名,第二个是指向对等方名称列表的指针。默认目录是“.\Group\”,其中的文件必须具有“.dat”扩展名。每个文件代表一个组,文件名就是组名,里面的内容是对等方名称。每个对等方名称占一行。数据加载到映射后,将根据映射创建树节点。最后释放映射的内存。在显示所有实际组之后,将添加一个“未知组”。这就是“管理组”面板中的树的构建方式。
第二种方法是通过“FilterTree
”函数。它根据节点所代表的对等方是否在线来过滤现有树。如果对等方在线,则保留相应的节点,否则将其删除。创建广播会话或监视会话时显示的“创建新会话”对话框中的树就是通过这种方式构建的。
2.5 会话列表对话框
在“广播桌面”和“监视桌面”中,分别是CShareSessionDlg
和CMonitorSessionDlg
。我发明了两种不同类型的会话,第一种是共享会话,用于CShareSessionDlg
,意思是将对等方的桌面广播给同一会话中的其他人,第二种是监视会话,用于CMonitorSessionDlg
,意思是监视一些对等方。类“CShareSession
”和“CMonitorSession
”分别对应它们。当你准备创建新的共享会话时,一个包含要选择的共享者和要从树中选择的查看者的对话框就是CShareSession
的用户界面。在这里,在这种情况下,CShareSession
似乎也是对话框的“托管 bean”,CMonitorSession
也是如此。创建会话时显示的对话框是模态对话框,在调用DoModal
之前,我传递一个CShareSession
或CMonitorSession
的实例,这样我就可以传入数据并让对话框显示,在DoModal
返回后,我可以通过用户输入通过对话框填写的实例获取新数据。这是一个重要的概念。然而,在软件中,我没有使用这种方式来传递数据。请注意,在代码级别上,监视会话的概念与 UI 上的概念略有不同,在 UI 上,一个监视会话只有一个对等方被监视。
3. 对等方
3.1 架构
架构与控制中心类似。底层是CDoorSocket
,中间层是CService
类,顶层是CPeerDlg
和CViewerDlg
。复杂程度大大降低。有些地方不同。与作为主对话框的CPeerDlg
对应的对话框在软件运行时从不显示。然而,它处理由托盘图标菜单或套接字触发的事件。CService
类提供三种服务:套接字服务、RDP 服务和系统管理服务。RDP 服务主要包括使用StartRDPService
启动 RDP 服务和使用StopRDPService
停止 RDP 服务。这里的系统管理服务只是使用SetHook
安装全局鼠标钩子和键盘钩子。在SetHook
函数中,我加载了一个名为“GlobalHook
”的库,其中安装了全局鼠标钩子和键盘钩子。
3.2 RDP 服务
这是软件的核心。Microsoft 在此处提供了 API,我也参考了此链接和此链接,这两篇文章对我帮助很大。我甚至直接引用了两个类“CMyRDPSessionEvents
”来提供事件处理,以及“B
”来将普通string
转换为 BSTR。
4. 命令系统
回复 | 自定义消息 | 类 |
“共享者已启动” | WM_MY_SHARERSTARTED |
CShareSessionDlg |
“共享者未启动” | WM_MY_SHARERNOTSTARTED |
CShareSessionDlg |
“共享者已停止” | WM_MY_SHARERSTOPPED |
CShareSessionDlg |
“共享者未停止” | ||
“查看者已启动” | ||
“查看者未启动” | ||
“查看者已停止” | ||
“查看者未停止” | ||
“监视器已打开” | WM_MY_MONITOROPENED |
CMonitorSessionDlg |
“监视器未打开” | WM_MY_MONITORNOTOPENED |
CMonitorSessionDlg |
“监视器已关闭” | WM_MY_MONITORCLOSED |
CMonitorSessionDlg |
“监视器未关闭” | ||
客户端命令“logname” | WM_MY_LOGNAME |
CPeerTreeDlg CControlCenterDlg |
套接字关闭事件 | WM_MY_SOCKCLOSE |
CPeerTreeDlg CShareSessionDlg CMonitorSessionDlg CControlCenterDlg |
客户端命令“举手” | WM_MY_HANDUP |
CControlCenterDlg CHandDlg |
下面列出了从服务器发送的命令
功能 | command | 成功回复 | 失败回复 |
启动 RDP 服务 | “启动共享者” | 共享者已启动 ticket:+ticket string | 共享者未启动 |
停止 RDP 服务 | “停止共享者” | 共享者已停止 | 共享者未停止 |
让 RDPViewer 连接 RDP 共享者,根据 ticket | “启动查看者” + ticket string | 查看者已启动 | 查看者未启动 |
断开 RDP 连接 | “停止查看者” | 查看者已停止 | 查看者未停止 |
与启动共享者相同 | “打开监视器” | 监视器已打开 ticket:+ticket string | 监视器未打开 |
与停止共享者相同 | “关闭监视器” | 监视器已关闭 | 监视器未关闭 |
锁定查看者 | “锁定” | ||
解锁查看者 | “解锁” | ||
禁用客户端上的“退出程序”菜单项 | “配置禁用退出” | ||
启用“退出程序”菜单项 | “配置启用退出” | ||
disconnect |
从客户端发送的命令(不期望服务器回复)
功能 | command |
登录 | “logname” + name |
电子举手 | “hand up” |
disconnect |
注意:白色表示无。
5. 语言
为了使软件国际化,多语言是一个问题。要提供不同语言的版本,我将源代码中的每个string
都转移到了资源脚本的string
表中。因此,所有与语言相关的资源都在资源脚本中。默认资源脚本是中文。将资源收集到另一个 DLL 项目中,构建一个仅包含资源的 DLL 作为独立的语言包。更多信息可以在此处找到。
关注点
我需要为软件提供不同语言的版本。项目类型是使用 VC++11 的 MFC 对话框项目。最初,我将所有资源创建为中文版,后来我想要英文版,只需将所有与资源相关的文件(包括res目录,resource.h和targetver.h)放入另一个 DLL 项目,编译并链接,您将获得一个仅包含资源的 DLL。系统可以根据仅包含资源的 DLL 的名称和系统配置来决定使用哪个资源。更多细节可以在此处和此处找到。这真的很强大。
历史
这是于 2014 年 1 月 20 日完成的第三版。
结论
我在开发 MFC 软件方面是新手。尽管软件的技术可以归结为 RDP+Socket,但对我来说并不容易。它还包含许多其他东西,如配置文件、全局钩子、资源国际化、COM 事件、MFC DLL 等。如果您有任何问题或想与我交流,请发送电子邮件至 nuclear.sun@qq.com。