client running time broker问题
[MS-RDPBCGR]: Remote Desktop Protocol: Basic Connectivity and Graphics Remoting。要没意外,它应该是第一篇要看的RDP文档。
[MS-CSSP]: Credential Security Support Provider (CredSSP) Protocol。为了安全,RDP使用的是SSL增强版协议:CredSSP。图1指出了CredSSP何时被使用。
[MS-RDPECLIP]: Remote Desktop Protocol: Clipboard Virtual Channel Extensio。RDP以着静态虚拟通道方法扩展出更多功能,剪贴板可说是必须支持的扩展通道(通道名:cliprdr)。
二、顶层逻辑
可把整个client归为四个步骤。一是解析命令行参数,二是连接server、启动后台线程,三是不断运行、直到核心线程结束,四是退出app。
2.1 解析命令行参数
int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv, BOOL allowUnknown);
freerdp_client_settings_parse_command_line负责从argc、argv解析命令行参数,结果存储在rdp_settings类型的变量context->settings。
/u:macbookpro15.2 /p:000000 /v:192.168.1.102
以上是三个命令行必须给出的参数。/u、/p分别指示了要登陆到server的账号和密码,/v是server的IP地址。经过解析,形成的settings是以下样子。
struct rdp_settings {
UINT32 ServerPort: 3389
char* ServerHostname: 192.168.1.102
char* Username: macbookpro15.2
char* Password: 000000
...
char* HomePath: C:Usersancientcc
char* ConfigPath: C:UsersancientccAppDataRoamingfreerdp
};
由于命令行没给出RDP端口,用默认的3389。
2.2 连接server、启动后台线程
int freerdp_client_start(rdpContext* context)
它会调用wfreerdp_client_start,后者主要执行3个任务。
调用RegisterClassEx注册Windows窗口。
创建并运行键盘输入处理线程:wf_keyboard_thread,句柄赋给keyboardThread成员变量。
创建并运行client核心处理线程:wf_client_thread,句柄赋给thread成员变量。它的运行过程基本就是client的整个生命周期。
为什么FreeRDP没把主线程作为核心线程?猜测是和3389端口用阻塞式读写有关。当使用阻塞式,并且不能保证网络质量一直很好,就会导致主线程阻塞,从而造成界面上非常不好的用户体验。
2.3 不断等待,直到核心线程wf_client_thread退出。
核心线程调用freerdp_connect连接server后,就进入while循环。一旦退出while循环,意味着线程结束。
while (1) {
DWORD tmp = freerdp_get_event_handles(context, &handles[nCount], 64 - nCount);
...
if (MsgWaitForMultipleObjects(nCount, handles, FALSE, 1000, QS_ALLINPUT) == WAIT_FAILED) {
break;
}
if (!freerdp_check_event_handles(context)) {
if (client_auto_reconnect(instance)) {
break;
}
}
quit_msg = FALSE;
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
...
}
if (quit_msg) {
break;
}
}
freerdp_check_event_handles是主要处理函数,内部依次调用freerdp_check_fds、freerdp_channels_check_fds。
freerdp_check_fds。检查端口(主要是3389)是有否要接收的数据,有就接收并处理。数据包括图像(Server Fast-Path Update PDU发来)、虚拟通道PUD(像server执行“粘贴”产生File Contents Request的Virtual Channel),等。可能要支持好多个虚拟通道,它在收到虚拟通道PUD、提取出通道私有数据后,不是立即执行私有动作,而是把私有数据封装到一个wStream,接着创建一个wMessage,让wMessage,wParam指向这个wStream,最后调用MessageQueue_Dispatch投递到该通道的消息队列(剪贴板是rdpdrPlugin.queue)。至于后绪私有处理,每个通道有个专门线程(剪贴板是rdpdr_virtual_channel_client_thread),它从消息队列取出消息,并处理。
虚拟通道线程不是在收到server发来的MCS Connect Response PDU with GCC后就立即创建,而是要等到连接完成后的freerdp_channels_post_connect。
不论是在wf_client_thread内处理,还是通道专门线程内处理,处理后如果须要向server发应答,像File Contents Request须要有File Contents Response,并不是立即发送,而是形成消息(wMessage),然后调用MessageQueue_Dispatch投递到消息队列channels->queue(类型:wMessageQueue)。
freerdp_channels_check_fds。从channels->queue取出消息,并逐个处理。
执行freerdp_check_event_handles处理完socket事件后,调用PeekMessage处理操作系统消息。
2.4 退出app
int freerdp_client_stop(rdpContext* context);
void freerdp_client_context_free(rdpContext* context);
freerdp_client_stop断开和server连接,freerdp_client_context_free释放相关资源。