1、主函数入口
int main(int argc, char *argv[])
{GError *error = NULL;GOptionContext *context;spice_connection *conn;gchar *conf_file, *conf;char *host = NULL, *port = NULL, *tls_port = NULL, *unix_path = NULL;keyfile = g_key_file_new();int mode = S_IRWXU;conf_file = g_build_filename(g_get_user_config_dir(), "spicy", NULL);if (g_mkdir_with_parents(conf_file, mode) == -1)SPICE_DEBUG("failed to create config directory");g_free(conf_file);conf_file = g_build_filename(g_get_user_config_dir(), "spicy", "settings", NULL);if (!g_key_file_load_from_file(keyfile, conf_file,G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, &error)) {SPICE_DEBUG("Couldn't load configuration: %s", error->message);g_clear_error(&error);}/* parse opts */gtk_init(&argc, &argv);context = g_option_context_new("- spice client test application");g_option_context_set_summary(context, "Gtk+ test client to connect to Spice servers.");g_option_context_set_description(context, "Report bugs to " PACKAGE_BUGREPORT ".");g_option_context_add_group(context, spice_get_option_group());g_option_context_set_main_group(context, spice_cmdline_get_option_group());g_option_context_add_main_entries(context, cmd_entries, NULL);g_option_context_add_group(context, gtk_get_option_group(TRUE));g_option_context_add_group(context, gst_init_get_option_group());if (!g_option_context_parse (context, &argc, &argv, &error)) {g_print("option parsing failed: %s\n", error->message);exit(1);}g_option_context_free(context);if (version) {g_print("spicy " PACKAGE_VERSION "\n");exit(0);}mainloop = g_main_loop_new(NULL, false);conn = connection_new();spice_set_session_option(conn->session);spice_cmdline_session_setup(conn->session);g_object_get(conn->session,"unix-path", &unix_path,"host", &host,"port", &port, "tls-port", &tls_port,NULL);/* If user doesn't provide hostname and port, show the dialog window instead of connecting to server automatically */if ((host == NULL || (port == NULL && tls_port == NULL)) && unix_path == NULL) {if (!spicy_connect_dialog(conn->session)) {exit(0);}}g_free(host);g_free(port);g_free(tls_port);g_free(unix_path);connection_connect(conn);if (connections > 0)g_main_loop_run(mainloop);g_main_loop_unref(mainloop);if ((conf = g_key_file_to_data(keyfile, NULL, &error)) == NULL ||!g_file_set_contents(conf_file, conf, -1, &error)) {SPICE_DEBUG("Couldn't save configuration: %s", error->message);g_error_free(error);error = NULL;}g_free(conf_file);g_free(conf);g_key_file_free(keyfile);g_free(spicy_title);setup_terminal(true);gst_deinit();return 0;
}
2、建立spice连接
static void connection_connect(spice_connection *conn)
{conn->disconnecting = false;spice_session_connect(conn->session);
}
3、跳转到spice-session.c中,先断开连接,创建主通道,调用主通道建立连接
gboolean spice_session_connect(SpiceSession *session)
{SpiceSessionPrivate *s;g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);s = session->priv;g_return_val_if_fail(!s->disconnecting, FALSE);session_disconnect(session, TRUE);s->client_provided_sockets = FALSE;if (s->cmain == NULL)s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);glz_decoder_window_clear(s->glz_window);return spice_channel_connect(s->cmain);
}
4、检查是否已经建立连接,调用channel_connect函数建立连接
gboolean spice_channel_connect(SpiceChannel *channel)
{g_return_val_if_fail(SPICE_IS_CHANNEL(channel), FALSE);SpiceChannelPrivate *c = channel->priv;if (c->state >= SPICE_CHANNEL_STATE_CONNECTING)return TRUE;g_return_val_if_fail(channel->priv->fd == -1, FALSE);return channel_connect(channel, FALSE);
}
5、是否通过TLS建立连接,主线程空闲时通过connect_delayed函数去建立连接
static gboolean channel_connect(SpiceChannel *channel, gboolean tls)
{SpiceChannelPrivate *c = channel->priv;g_return_val_if_fail(c != NULL, FALSE);if (c->session == NULL || c->channel_type == -1 || c->channel_id == -1) {/* unset properties or unknown channel type */g_warning("%s: channel setup incomplete", __FUNCTION__);return false;}c->state = SPICE_CHANNEL_STATE_CONNECTING;c->tls = tls;if (spice_session_get_client_provided_socket(c->session)) {if (c->fd == -1) {CHANNEL_DEBUG(channel, "requesting fd");/* FIXME: no way for client to provide fd atm. *//* It could either chain on parent channel.. *//* or register migration channel on parent session, or ? */g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);return true;}}c->xmit_queue_blocked = FALSE;g_return_val_if_fail(c->sock == NULL, FALSE);g_object_ref(G_OBJECT(channel)); /* Unref'd when co-routine exits *//* we connect in idle, to let previous coroutine exit, if present */c->connect_delayed_id = g_idle_add(connect_delayed, channel);return true;
}
6、创建协程,通过spice_channel_coroutine函数建立连接
static gboolean connect_delayed(gpointer data)
{SpiceChannel *channel = data;SpiceChannelPrivate *c = channel->priv;struct coroutine *co;CHANNEL_DEBUG(channel, "Open coroutine starting %p", channel);c->connect_delayed_id = 0;co = &c->coroutine.coroutine;co->stack_size = 16 << 20; /* 16Mb */co->entry = spice_channel_coroutine;coroutine_init(co);coroutine_yieldto(co, channel);return FALSE;
}
7、通过socket连接到服务器的session会话中,如果启动TLS就完成TLS握手通信,完成认证后通过spice_channel_iterate函数监听socket消息
/* coroutine context */
static void *spice_channel_coroutine(void *data)
{SpiceChannel *channel = SPICE_CHANNEL(data);SpiceChannelPrivate *c = channel->priv;guint verify;int rc, delay_val = 1;/* When some other SSL/TLS version becomes obsolete, add it to this variable. */long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;CHANNEL_DEBUG(channel, "Started background coroutine %p", &c->coroutine);if (spice_session_get_client_provided_socket(c->session)) {if (c->fd < 0) {g_critical("fd not provided!");c->event = SPICE_CHANNEL_ERROR_CONNECT;goto cleanup;}if (!(c->sock = g_socket_new_from_fd(c->fd, NULL))) {CHANNEL_DEBUG(channel, "Failed to open socket from fd %d", c->fd);c->event = SPICE_CHANNEL_ERROR_CONNECT;goto cleanup;}g_socket_set_blocking(c->sock, FALSE);g_socket_set_keepalive(c->sock, TRUE);c->conn = g_socket_connection_factory_create_connection(c->sock);goto connected;}reconnect:c->conn = spice_session_channel_open_host(c->session, channel, &c->tls, &c->error);if (c->conn == NULL) {if (!c->error && !c->tls) {CHANNEL_DEBUG(channel, "trying with TLS port");c->tls = true; /* FIXME: does that really work with provided fd */goto reconnect;} else {CHANNEL_DEBUG(channel, "Connect error");c->event = SPICE_CHANNEL_ERROR_CONNECT;goto cleanup;}}c->sock = g_object_ref(g_socket_connection_get_socket(c->conn));if (c->tls) {c->ctx = SSL_CTX_new(SSLv23_method());if (c->ctx == NULL) {g_critical("SSL_CTX_new failed");c->event = SPICE_CHANNEL_ERROR_TLS;goto cleanup;}SSL_CTX_set_options(c->ctx, ssl_options);verify = spice_session_get_verify(c->session);if (verify &(SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {rc = spice_channel_load_ca(channel);if (rc == 0) {g_warning("no cert loaded");if (verify & SPICE_SESSION_VERIFY_PUBKEY) {g_warning("only pubkey active");verify = SPICE_SESSION_VERIFY_PUBKEY;} else {c->event = SPICE_CHANNEL_ERROR_TLS;goto cleanup;}}}{const gchar *ciphers = spice_session_get_ciphers(c->session);if (ciphers != NULL) {rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);if (rc != 1)g_warning("loading cipher list %s failed", ciphers);}}c->ssl = SSL_new(c->ctx);if (c->ssl == NULL) {g_critical("SSL_new failed");c->event = SPICE_CHANNEL_ERROR_TLS;goto cleanup;}BIO *bio = bio_new_giostream(G_IO_STREAM(c->conn));SSL_set_bio(c->ssl, bio, bio);{guint8 *pubkey;guint pubkey_len;spice_session_get_pubkey(c->session, &pubkey, &pubkey_len);c->sslverify = spice_openssl_verify_new(c->ssl, verify,spice_session_get_host(c->session),(char*)pubkey, pubkey_len,spice_session_get_cert_subject(c->session));}
#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT){const char *hostname = spice_session_get_host(c->session);// check is not an ip addressGInetAddress * ip = g_inet_address_new_from_string(hostname);if (ip == NULL) {SSL_set_tlsext_host_name(c->ssl, hostname);} else {g_object_unref(ip);}}
#endif
ssl_reconnect:rc = SSL_connect(c->ssl);if (rc <= 0) {rc = SSL_get_error(c->ssl, rc);if (rc == SSL_ERROR_WANT_READ || rc == SSL_ERROR_WANT_WRITE) {g_coroutine_socket_wait(&c->coroutine, c->sock, ssl_error_to_cond(rc));goto ssl_reconnect;} else {g_warning("%s: SSL_connect: %s",c->name, ERR_error_string(rc, NULL));c->event = SPICE_CHANNEL_ERROR_TLS;goto cleanup;}}}connected:c->has_error = FALSE;c->in = g_io_stream_get_input_stream(G_IO_STREAM(c->conn));c->out = g_io_stream_get_output_stream(G_IO_STREAM(c->conn));rc = setsockopt(g_socket_get_fd(c->sock), IPPROTO_TCP, TCP_NODELAY,(const char*)&delay_val, sizeof(delay_val));if ((rc != 0)
#ifdef ENOTSUP&& (errno != ENOTSUP)
#endif) {g_warning("%s: could not set sockopt TCP_NODELAY: %s", c->name,strerror(errno));}spice_channel_send_link(channel);if (!spice_channel_recv_link_hdr(channel) ||!spice_channel_recv_link_msg(channel) ||!spice_channel_recv_auth(channel))goto cleanup;while (spice_channel_iterate(channel));
cleanup:CHANNEL_DEBUG(channel, "Coroutine exit %s", c->name);spice_channel_reset(channel, FALSE);if (c->state == SPICE_CHANNEL_STATE_RECONNECTING ||c->state == SPICE_CHANNEL_STATE_SWITCHING) {g_warn_if_fail(c->event == SPICE_CHANNEL_NONE);if (channel_connect(channel, c->tls)) {g_object_unref(channel);return NULL;}c->event = SPICE_CHANNEL_ERROR_CONNECT;}g_idle_add(spice_channel_delayed_unref, channel);/* Co-routine exits now - the SpiceChannel object may no longer exist,so don't do anything else now unless you like SEGVs */return NULL;
}
8、获取socket的读消息和者写消息,并调用相关的读函数iterate_write和写函数iterate_read
/* coroutine context */
static gboolean spice_channel_iterate(SpiceChannel *channel)
{SpiceChannelPrivate *c = channel->priv;if (c->state == SPICE_CHANNEL_STATE_MIGRATING &&!g_coroutine_condition_wait(&c->coroutine, wait_migration, channel))CHANNEL_DEBUG(channel, "migration wait cancelled");/* flush any pending write and read */if (!c->has_error)SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);if (!c->has_error)SPICE_CHANNEL_GET_CLASS(channel)->iterate_read(channel);if (c->has_error) {GIOCondition ret;if (!c->sock)return FALSE;/* We don't want to report an error if the socket was closed gracefully on the other end (VM shutdown) */ret = g_socket_condition_check(c->sock, G_IO_IN | G_IO_ERR);if (ret & G_IO_ERR) {CHANNEL_DEBUG(channel, "channel got error");if (c->state > SPICE_CHANNEL_STATE_CONNECTING) {if (c->state == SPICE_CHANNEL_STATE_READY)c->event = SPICE_CHANNEL_ERROR_IO;elsec->event = SPICE_CHANNEL_ERROR_LINK;}}return FALSE;}return TRUE;
}
9、设置的读写回调函数
klass->iterate_write --> spice_channel_iterate_write
klass->iterate_read -->spice_channel_iterate_read
klass->handle_msg  --> spice_channel_handle_msg
static void spice_channel_class_init(SpiceChannelClass *klass)
{GObjectClass *gobject_class = G_OBJECT_CLASS (klass);klass->iterate_write = spice_channel_iterate_write;klass->iterate_read  = spice_channel_iterate_read;klass->channel_reset = channel_reset;gobject_class->constructed  = spice_channel_constructed;gobject_class->dispose      = spice_channel_dispose;gobject_class->finalize     = spice_channel_finalize;gobject_class->get_property = spice_channel_get_property;gobject_class->set_property = spice_channel_set_property;klass->handle_msg           = spice_channel_handle_msg;...
}
10、以读函数为例子,通过g_pollable_input_stream_is_readable获取到是否有可读的消息,如果有消息,调用spice_channel_recv_msg函数处理
注意参数是一个函数指针:SPICE_CHANNEL_GET_CLASS(channel)->handle_msg 
/* coroutine context */
static void spice_channel_iterate_read(SpiceChannel *channel)
{SpiceChannelPrivate *c = channel->priv;g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_IN);/* treat all incoming data (block on message completion) */while (!c->has_error &&c->state != SPICE_CHANNEL_STATE_MIGRATING &&(g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(c->in))
#ifdef HAVE_SASL/* flush the sasl buffer too */|| c->sasl_decoded != NULL
#endif)) {spice_channel_recv_msg(channel, (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);}
}
11、通过spice_channel_read读取消息,然后通过msg_handler函数处理消息,msg_handler是一个函数指针
/* coroutine context */
G_GNUC_INTERNAL
void spice_channel_recv_msg(SpiceChannel *channel,handler_msg_in msg_handler, gpointer data)
{SpiceChannelPrivate *c = channel->priv;SpiceMsgIn *in;int msg_size;int msg_type;int sub_list_offset = 0;in = spice_msg_in_new(channel);/* receive message */spice_channel_read(channel, in->header,spice_header_get_header_size(c->use_mini_header));if (c->has_error)goto end;msg_size = spice_header_get_msg_size(in->header, c->use_mini_header);/* FIXME: do not allow others to take ref on in, and use realloc here? this would avoid malloc/free on each message? */in->data = g_malloc0(msg_size);spice_channel_read(channel, in->data, msg_size);if (c->has_error)goto end;in->dpos = msg_size;msg_type = spice_header_get_msg_type(in->header, c->use_mini_header);sub_list_offset = spice_header_get_msg_sub_list(in->header, c->use_mini_header);if (msg_type == SPICE_MSG_LIST || sub_list_offset) {SpiceSubMessageList *sub_list;SpiceSubMessage *sub;SpiceMsgIn *sub_in;int i;sub_list = (SpiceSubMessageList *)(in->data + sub_list_offset);for (i = 0; i < sub_list->size; i++) {sub = (SpiceSubMessage *)(in->data + sub_list->sub_messages[i]);sub_in = spice_msg_in_sub_new(channel, in, sub);sub_in->parsed = c->parser(sub_in->data, sub_in->data + sub_in->dpos,spice_header_get_msg_type(sub_in->header,c->use_mini_header),c->peer_hdr.minor_version,&sub_in->psize, &sub_in->pfree);if (sub_in->parsed == NULL) {g_critical("failed to parse sub-message: %s type %d",c->name, spice_header_get_msg_type(sub_in->header, c->use_mini_header));goto end;}msg_handler(channel, sub_in, data);spice_msg_in_unref(sub_in);}}/* ack message */if (c->message_ack_count) {c->message_ack_count--;if (!c->message_ack_count) {SpiceMsgOut *out = spice_msg_out_new(channel, SPICE_MSGC_ACK);spice_msg_out_send_internal(out);c->message_ack_count = c->message_ack_window;}}if (msg_type == SPICE_MSG_LIST) {goto end;}/* parse message */in->parsed = c->parser(in->data, in->data + msg_size, msg_type,c->peer_hdr.minor_version, &in->psize, &in->pfree);if (in->parsed == NULL) {g_critical("failed to parse message: %s type %d",c->name, msg_type);goto end;}/* process message *//* spice_msg_in_hexdump(in); */msg_handler(channel, in, data);end:/* If the server uses full header, the serial is not necessarily equal* to c->in_serial (the server can sometimes skip serials) */c->last_message_serial = spice_header_get_in_msg_serial(in);c->in_serial++;spice_msg_in_unref(in);
}
12、msg_handler调用本质就是spice_channel_handle_msg函数,而这个函数就是每个channel定义时会设置的const spice_msg_handler handlers[]函数指针数组。
/* coroutine context */
static void spice_channel_handle_msg(SpiceChannel *channel, SpiceMsgIn *msg)
{SpiceChannelClass *klass = SPICE_CHANNEL_GET_CLASS(channel);int type = spice_msg_in_type(msg);// SPICE_MSG_PLAYBACK_DATAspice_msg_handler handler;g_return_if_fail(type < klass->priv->handlers->len);if (type > SPICE_MSG_BASE_LAST && channel->priv->disable_channel_msg)return;handler = g_array_index(klass->priv->handlers, spice_msg_handler, type); //获取回调函数g_return_if_fail(handler != NULL);handler(channel, msg); //调用相应的回到函数
}
13、通过spice_protocol协议定义了枚举数值来找到相应的回调函数,如下图

SPICE_MSG_PLAYBACK_DATA --> playback_handle_data
enum {SPICE_MSG_PLAYBACK_DATA = 101,SPICE_MSG_PLAYBACK_MODE,SPICE_MSG_PLAYBACK_START,SPICE_MSG_PLAYBACK_STOP,SPICE_MSG_PLAYBACK_VOLUME,SPICE_MSG_PLAYBACK_MUTE,SPICE_MSG_PLAYBACK_LATENCY,SPICE_MSG_END_PLAYBACK
};static void channel_set_handlers(SpiceChannelClass *klass)
{static const spice_msg_handler handlers[] = {[ SPICE_MSG_PLAYBACK_DATA ]            = playback_handle_data,[ SPICE_MSG_PLAYBACK_MODE ]            = playback_handle_mode,[ SPICE_MSG_PLAYBACK_START ]           = playback_handle_start,[ SPICE_MSG_PLAYBACK_STOP ]            = playback_handle_stop,[ SPICE_MSG_PLAYBACK_VOLUME ]          = playback_handle_set_volume,[ SPICE_MSG_PLAYBACK_MUTE ]            = playback_handle_set_mute,[ SPICE_MSG_PLAYBACK_LATENCY ]         = playback_handle_set_latency,};spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
}

spice-gtk的spicy命令源码分析相关推荐

  1. docker stats命令源码分析结果

    2019独角兽企业重金招聘Python工程师标准>>> 本文是基于docker 1.10.3版本的源码,对docker stats命令进行源码分析,看看docker stats命令输 ...

  2. rm删除命令源码分析

    为什么看? 想要在删除文件前,先覆盖文件内容,防止他人恢复文件,从而得到文件原内容:并且需要支持rm命令原本的参数选项: NAME rm - remove files or directories S ...

  3. Django源码分析10:makemigrations命令概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-makemigrations命令概述 Django项目中的数据库管理命令就是通过makemig ...

  4. Django源码分析8:单元测试test命令浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-test命令分析 Django项目中提供了,test命令行命令来执行django的单元测试,该 ...

  5. Django源码分析7:migrate命令的浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-migrate命令分析 Django项目中提供了,通过migrations操作数据库的结构的命 ...

  6. celery源码分析:multi命令分析

    celery源码分析 本文环境python3.5.2,celery4.0.2,django1.10.x系列 celery简介 celery是一款异步任务框架,基于AMQP协议的任务调度框架.使用的场景 ...

  7. Docker源码分析(二):Docker Client创建与命令执行

    http://www.infoq.com/cn/articles/docker-source-code-analysis-part2 1. 前言 如今,Docker作为业界领先的轻量级虚拟化容器管理引 ...

  8. 如何将镜像烧写至iNand(fastboot命令的源码分析)

    以下内容源于网络资源的学习与整理,如有侵权请告知删除. 参考博客 u-boot sdfuse命令烧录分析----从SD卡加载内核_white_bugs的博客-CSDN博客 一.将镜像文件烧写至iNan ...

  9. WPF(六) Command 命令模型源码分析

    1.ICommand源码分析 ​ 在之前 WPF(三) WPF命令 中我们已经分析过了 WPF 的命令系统,包括WPF默认的 RoutedCommand 以及我们自定义的 ICommand 命令实现. ...

最新文章

  1. mysql 常用sql与命令
  2. python的运行窗口-Python初学——窗口视窗Tkinter
  3. 2020-11-12(JNI开发常见错误)
  4. Automatic Exploit Generation:漏洞利用自动化
  5. 2018.12.30|区块链技术头条
  6. mysql 表单属性_php表单常用属性有什么?
  7. hibernate Criteria(条件查询接口)
  8. 【PyQt5 知识点示例代码】布局、菜单、信号与槽、对话框、组件
  9. android渠道校验,Android渠道版本自动化校验
  10. SQL那些事儿(十三)--Oracle中varchar与varchar2区别
  11. KM算法实现带权匹配C#版本和C++两个版本实现O^3
  12. 工厂模式(包含3种工厂)
  13. 工控行业学什么编程语言比较好_中国工控|想学PLC编程?先弄清5种PLC专用语言 !...
  14. Linux服务器重启导致数据丢失问题解决
  15. cpu之ALUSrc_Reg
  16. Java接口练习(组装电脑)
  17. Python Network(一)基础入门(节点和边基本概念,网络统计量)
  18. Python基础语法(五)—常用模块和模块的安装和导入
  19. 一、初识Metasploit(MSF使用详解超详细)
  20. 计算机工作日志小学,班主任工作日志记录 小学班主任工作计划合集

热门文章

  1. 刚刚,2022中科院分区表发布(附下载)
  2. 困扰老子好久啊!!tomcat的两个错…
  3. 智能化引领中国铁路发展
  4. 智能CDN(上):CDN的访问过程和加速原理
  5. 听VOA还不如学这些
  6. 2004年中国企业自主创新宣言
  7. google翻译破解大综合
  8. Error:(63, 20) Failed to resolve: com.github.chrisbanes:PhotoView:2.0.0
  9. 边读边体验《大数据时代》
  10. 我所理解的高通UEFI之display的流程和移植