1、主函数入口
int main(int argc, char *argv[])
char *host = NULL, *port = NULL, *tls_port = NULL, *unix_path = NULL;
keyfile = g_key_file_new();
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");
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);
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);
g_option_context_free(context);
g_print("spicy " PACKAGE_VERSION "\n");
mainloop = g_main_loop_new(NULL, false);
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 ((host == NULL || (port == NULL && tls_port == NULL)) && unix_path == NULL) {
if (!spicy_connect_dialog(conn->session)) {
connection_connect(conn);
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_key_file_free(keyfile);

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)
g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE)
g_return_val_if_fail(!s->disconnecting, FALSE)
session_disconnect(session, TRUE)
s->client_provided_sockets = FALSE
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)
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) {
g_warning("%s: channel setup incomplete", __FUNCTION__);
c->state = SPICE_CHANNEL_STATE_CONNECTING;
if (spice_session_get_client_provided_socket(c->session)) {
CHANNEL_DEBUG(channel, "requesting fd");
g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);
c->xmit_queue_blocked = FALSE;
g_return_val_if_fail(c->sock == NULL, FALSE);
g_object_ref(G_OBJECT(channel));
c->connect_delayed_id = g_idle_add(connect_delayed, channel);
6、创建协程,通过spice_channel_coroutine函数建立连接
static gboolean connect_delayed(gpointer data)
SpiceChannel *channel = data;
SpiceChannelPrivate *c = channel->priv;
CHANNEL_DEBUG(channel, "Open coroutine starting %p", channel);
c->connect_delayed_id = 0;
co = &c->coroutine.coroutine;
co->stack_size = 16 << 20;
co->entry = spice_channel_coroutine;
coroutine_yieldto(co, channel);
7、通过socket连接到服务器的session会话中,如果启动TLS就完成TLS握手通信,完成认证后通过spice_channel_iterate函数监听socket消息
static void *spice_channel_coroutine(void *data)
SpiceChannel *channel = SPICE_CHANNEL(data);
SpiceChannelPrivate *c = channel->priv;
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)) {
g_critical("fd not provided!");
c->event = SPICE_CHANNEL_ERROR_CONNECT;
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;
g_socket_set_blocking(c->sock, FALSE);
g_socket_set_keepalive(c->sock, TRUE);
c->conn = g_socket_connection_factory_create_connection(c->sock);
c->conn = spice_session_channel_open_host(c->session, channel, &c->tls, &c->error);
if (!c->error && !c->tls) {
CHANNEL_DEBUG(channel, "trying with TLS port");
CHANNEL_DEBUG(channel, "Connect error");
c->event = SPICE_CHANNEL_ERROR_CONNECT;
c->sock = g_object_ref(g_socket_connection_get_socket(c->conn));
c->ctx = SSL_CTX_new(SSLv23_method());
g_critical("SSL_CTX_new failed");
c->event = SPICE_CHANNEL_ERROR_TLS;
SSL_CTX_set_options(c->ctx, ssl_options);
verify = spice_session_get_verify(c->session);
(SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {
rc = spice_channel_load_ca(channel);
g_warning("no cert loaded");
if (verify & SPICE_SESSION_VERIFY_PUBKEY) {
g_warning("only pubkey active");
verify = SPICE_SESSION_VERIFY_PUBKEY;
c->event = SPICE_CHANNEL_ERROR_TLS;
const gchar *ciphers = spice_session_get_ciphers(c->session);
rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);
g_warning("loading cipher list %s failed", ciphers);
c->ssl = SSL_new(c->ctx);
g_critical("SSL_new failed");
c->event = SPICE_CHANNEL_ERROR_TLS;
BIO *bio = bio_new_giostream(G_IO_STREAM(c->conn));
SSL_set_bio(c->ssl, bio, bio);
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);
GInetAddress * ip = g_inet_address_new_from_string(hostname);
SSL_set_tlsext_host_name(c->ssl, hostname);
rc = SSL_connect(c->ssl);
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));
g_warning("%s: SSL_connect: %s",
c->name, ERR_error_string(rc, NULL));
c->event = SPICE_CHANNEL_ERROR_TLS;
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));
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))
while (spice_channel_iterate(channel));
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)) {
c->event = SPICE_CHANNEL_ERROR_CONNECT;
g_idle_add(spice_channel_delayed_unref, channel);

8、获取socket的读消息和者写消息,并调用相关的读函数iterate_write和写函数iterate_read
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");
SPICE_CHANNEL_GET_CLASS(channel)->iterate_write(channel);
SPICE_CHANNEL_GET_CLASS(channel)->iterate_read(channel);
ret = g_socket_condition_check(c->sock, G_IO_IN | 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;
c->event = SPICE_CHANNEL_ERROR_LINK;
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
static void spice_channel_iterate_read(SpiceChannel *channel)
SpiceChannelPrivate *c = channel->priv;
g_coroutine_socket_wait(&c->coroutine, c->sock, G_IO_IN);
c->state != SPICE_CHANNEL_STATE_MIGRATING &&
(g_pollable_input_stream_is_readable(G_POLLABLE_INPUT_STREAM(c->in))
|| c->sasl_decoded != NULL
spice_channel_recv_msg(channel, (handler_msg_in)SPICE_CHANNEL_GET_CLASS(channel)->handle_msg, NULL);
11、通过spice_channel_read读取消息,然后通过msg_handler函数处理消息,msg_handler是一个函数指针
void spice_channel_recv_msg(SpiceChannel *channel,
handler_msg_in msg_handler, gpointer data)
SpiceChannelPrivate *c = channel->priv;
in = spice_msg_in_new(channel);
spice_channel_read(channel, in->header,spice_header_get_header_size(c->use_mini_header));
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);
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;
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));
msg_handler(channel, sub_in, data);
spice_msg_in_unref(sub_in);
if (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) {
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",
/* spice_msg_in_hexdump(in); */
msg_handler(channel, in, data);
/* 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);

12、msg_handler调用本质就是
spice_channel_handle_msg函数,而这个函数就是每个channel定义时会设置的const spice_msg_handler handlers[]函数指针数组。
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_handler handler;
g_return_if_fail(type < klass->priv->handlers->len);
if (type > SPICE_MSG_BASE_LAST && channel->priv->disable_channel_msg)
handler = g_array_index(klass->priv->handlers, spice_msg_handler, type);
g_return_if_fail(handler != NULL);
13、通过spice_protocol协议定义了枚举数值来找到相应的回调函数,如下图
SPICE_MSG_PLAYBACK_DATA --> playback_handle_data
SPICE_MSG_PLAYBACK_DATA = 101,
SPICE_MSG_PLAYBACK_START,
SPICE_MSG_PLAYBACK_VOLUME,
SPICE_MSG_PLAYBACK_LATENCY,
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));