承接上文
https://blog.csdn.net/qq_38781075/article/details/106994385
这次我们来聊一聊创建guacd的user线程前后发生的事情。
这篇文章将会以SSH为例讲解,并且是以第一个连接远程桌面的人的视角,也就是这个远程桌面的拥有者。
这篇文章涉及的参数比较多,推荐使用ctrl+f加关键词能找到我说的参数,我用括号标注了。
先从guac_client_init函数讲起,每个不同的远程协议都有不同的guac_client_init函数的实现。
首先先拿到一些参数,这些都是定义好的。
client->args = GUAC_SSH_CLIENT_ARGS;
参数内容就是这样,这是SSH的。
const char* GUAC_SSH_CLIENT_ARGS[] = {
"hostname",
"host-key",
"port",
"username",
"password",
"font-name",
"font-size",
"enable-sftp",
"sftp-root-directory",
"private-key",
"passphrase",
#ifdef ENABLE_SSH_AGENT
"enable-agent",
#endif
"color-scheme",
"command",
"typescript-path",
"typescript-name",
"create-typescript-path",
"recording-path",
"recording-name",
"recording-exclude-output",
"recording-exclude-mouse",
"recording-include-keys",
"create-recording-path",
"read-only",
"server-alive-interval",
"backspace",
"terminal-type",
"scrollback",
"locale",
"timezone",
NULL
};
接着绑定一些回调函数,如
client->join_handler = guac_ssh_user_join_handler;
client->free_handler = guac_ssh_client_free_handler;
两个函数分别是用户加入时调用的函数和用户离开时释放的函数。
接着就是一个大循环了,这个循环之后就会创建user线程,之后的操作就跟这个远程桌面拥有者无关了,不管你是不是这个拥有者,接下来大家就一样了。像这样:
/* Add each received file descriptor as a new user */
int received_fd;
while ((received_fd = guacd_recv_fd(proc->fd_socket)) != -1) {
guacd_proc_add_user(proc, received_fd, owner);
/* Future file descriptors are not owners */
owner = 0;
}
guacd_proc_add_user函数就是用来添加新的用户的,里面会创建user线程。接下来就主要讲这个函数。
这个线程首先是把参数(GUAC_SSH_CLIENT_ARGS)发送给web应用,当然是以Guacamole协议的格式发送的(接下来说的所有的命令收发都是用了这个协议)。
看那些参数(GUAC_SSH_CLIENT_ARGS)的名字大概能猜到上面意思,就是向web应用请求一些如hostname、port之类的,不过web应用不会马上把内容发送给guacd,可能先把下面结构体(__guac_handshake_handler_map)里面的命令发过来。下面是一个结构体数组,也是命令加回调函数的形式早就定义了的。定义了web应用可能发来的命令,大概是跟用户端的浏览器有关,大小屏幕等一些适配。
__guac_instruction_handler_mapping __guac_handshake_handler_map[] = {
{"size", __guac_handshake_size_handler},
{"audio", __guac_handshake_audio_handler},
{"video", __guac_handshake_video_handler},
{"image", __guac_handshake_image_handler},
{"timezone", __guac_handshake_timezone_handler},
{NULL, NULL}
};
前面是命令的名称,后面是回调函数,上面的东西,所有的协议都是一样的。值得一提的是,根据代码,不一定会把上面所有命令(__guac_handshake_handler_map)都发过来,不像那个我前面提到的那一长串参数(GUAC_SSH_CLIENT_ARGS),那个参数是要全发的。
这个只要web应用发了这个命令——"connect",就代表要回应那一长串参数的内容了,接着只需要接收就行了,user线程会调用一个函数去处理web应用发来的参数。
接着还会发送一个ready的报文,参数是用户ID。
有一张图比较好,我就不盗了。推荐看看下面这个链接的第二张图,虽然讲的是RDP,但是各个协议差别不会太大。
链接:https://blog.csdn.net/cherrybomb1111/article/details/67633366
处理完参数之后,就会绑定一些用户操作的回调函数,比如键盘的输入、鼠标的点击等。像这样(以SSH为例,不同协议绑定的回调函数不一样):
/* General mouse/keyboard/clipboard events */
user->key_handler = guac_ssh_user_key_handler;
user->mouse_handler = guac_ssh_user_mouse_handler;
user->clipboard_handler = guac_ssh_clipboard_handler;
/* STDIN redirection */
user->pipe_handler = guac_ssh_pipe_handler;
/* Updates to connection parameters */
user->argv_handler = guac_ssh_argv_handler;
/* Display size change events */
user->size_handler = guac_ssh_user_size_handler;
当走到这一步时,用户与服务器的连接是真正完成了。
然后user线程又会创建一个新的线程(暂时叫做input线程),input线程就是最终循环的线程,而user线程此时的作用改变了,变成等待input线程结束将其回收。
input线程里面就是最终的循环了,里面也有类似的回调机制。下面也是一个结构体,web应用按照规则要发送下面这个结构体数组(__guac_instruction_handler_map)内容的命令。
__guac_instruction_handler_mapping __guac_instruction_handler_map[] = {
{"sync", __guac_handle_sync},
{"mouse", __guac_handle_mouse},
{"key", __guac_handle_key},
{"clipboard", __guac_handle_clipboard},
{"disconnect", __guac_handle_disconnect},
{"size", __guac_handle_size},
{"file", __guac_handle_file},
{"pipe", __guac_handle_pipe},
{"ack", __guac_handle_ack},
{"blob", __guac_handle_blob},
{"end", __guac_handle_end},
{"get", __guac_handle_get},
{"put", __guac_handle_put},
{"audio", __guac_handle_audio},
{"argv", __guac_handle_argv},
{"nop", __guac_handle_nop},
{NULL, NULL}
};
input线程就一直死循环等待web应用发来的命令,其实就是用户发来的命令,比如键盘的输入、鼠标的点击等,直到用户退出。
上面结构体数组(__guac_instruction_handler_map)回调函数的内部就会用到远程桌面协议的函数了,拿那个鼠标函数(__guac_handle_mouse)举例:
int __guac_handle_mouse(guac_user* user, int argc, char** argv) {
if (user->mouse_handler)
return user->mouse_handler(
user,
atoi(argv[0]), /* x */
atoi(argv[1]), /* y */
atoi(argv[2]) /* mask */
);
return 0;
}
最后调用mouse_handler的回调函数,这个函数是之前绑定过的,可以看我文章中间写的,用ctrl+f搜索mouse_handler,在我这篇文章。