spice-gtk源码分析(一) | 马犇-技术博客

spice-gtk源码分析(一)

来源:本站原创 C/C++, 原创 超过10,652 views围观 4条评论
// spice.c
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;

    /*
     * 中间是一些各种变量的初始化、GTK界面相关参数、读取配置文件等等
     */
    g_type_init();  // 初始化类型系统
    mainloop = g_main_loop_new(NULL, false);    // glib的主事件循环,暂理解为界面的事件循环吧

    conn = connection_new();  
    /* connection_new() 创建一个新的连接
     * 主要操作就是一些注册一些回调函数(如新建通道、销毁通道、初始化USB设备管理等等)
     * 定义如下:
    static spice_connection *connection_new(void)
    {
        spice_connection *conn;
        SpiceUsbDeviceManager *manager;

        conn = g_new0(spice_connection, 1); // 相当于C++的new一个对象
        conn->session = spice_session_new(); // 同样,new一个SpiceSession
        conn->gtk_session = spice_gtk_session_get(conn->session);
        g_signal_connect(conn->session, "channel-new", 
                         G_CALLBACK(channel_new), conn); // 注册新建通道的回调
        g_signal_connect(conn->session, "channel-destroy",
                         G_CALLBACK(channel_destroy), conn); // 注册销毁通道的回调
        g_signal_connect(conn->session, "notify::migration-state",
                         G_CALLBACK(migration_state), conn); // 这个还未深入看,猜测应该是虚拟机迁移相关的回调

        //初始化USB相关
        manager = spice_usb_device_manager_get(conn->session, NULL);
        if (manager) {
            g_signal_connect(manager, "auto-connect-failed",
                             G_CALLBACK(usb_connect_failed), NULL);
            g_signal_connect(manager, "device-error",
                             G_CALLBACK(usb_connect_failed), NULL);
        }

        connections++;
        SPICE_DEBUG("%s (%d)", __FUNCTION__, connections);
        return conn;
    }

     */
    spice_set_session_option(conn->session);
    spice_cmdline_session_setup(conn->session);

    g_object_get(conn->session,
                 "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)) {
        // 通过gdb调试,这个地方会打开连接界面,阻塞在这
        int ret = connect_dialog(conn->session);
        if (ret != 0) {
            exit(0);
        }
    }
    g_free(host);
    g_free(port);
    g_free(tls_port);

    watch_stdin();
    /*
     * watch_stdin() 初始化标准输入的监听
    static void watch_stdin(void)
    {
        int stdinfd = fileno(stdin);
        GIOChannel *gin;

        setup_terminal(false);
        gin = g_io_channel_unix_new(stdinfd); // 新建unix通道
        g_io_channel_set_flags(gin, G_IO_FLAG_NONBLOCK, NULL);
        g_io_add_watch(gin, G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, input_cb, NULL); // 注册input_cb回调
    }
     *
     */

    // 真正进行连接过程,下面分析
    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);
    return 0;
}

// connection_connect定义,继续跟进spice_session_connect
static void connection_connect(spice_connection *conn)
{
    conn->disconnecting = false;
    spice_session_connect(conn->session);
}

// spice-session.c
gboolean spice_session_connect(SpiceSession *session)
{
    //此处的SpiceSessionPrivate相当于C++的私有成员(用C来实现的面向对象)
    SpiceSessionPrivate *s = SPICE_SESSION_GET_PRIVATE(session);

    g_return_val_if_fail(s != NULL, FALSE);

    spice_session_disconnect(session);
    s->disconnecting = FALSE;

    s->client_provided_sockets = FALSE;

    g_warn_if_fail(s->cmain == NULL);
    // 此处创建与服务端的主通道,在这还并没有真正与服务端连接,里面又是一个g_object_new。。。
    s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0); 

    glz_decoder_window_clear(s->glz_window);
    return spice_channel_connect(s->cmain); // 继续跟进
}

// spice-channel.c
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;

    return channel_connect(channel); // 继续跟进
}

// 通道连接过程
static gboolean channel_connect(SpiceChannel *channel)
{
    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;
    }
    if (c->state != SPICE_CHANNEL_STATE_UNCONNECTED) {
        g_warning("Invalid channel_connect state: %d", c->state);
        return true;
    }

    if (spice_session_get_client_provided_socket(c->session)) {
        if (c->fd == -1) {
            g_signal_emit(channel, signals[SPICE_CHANNEL_OPEN_FD], 0, c->tls);
            return true;
        }
    }
    c->state = SPICE_CHANNEL_STATE_CONNECTING;
    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 */
    // g_idle_add() 这个函数官方给的文档是Adds a function to be called whenever there are no higher priority events pending to the default main loop. The function is given the default idle priority, G_PRIORITY_DEFAULT_IDLE. If the function returns FALSE it is automatically removed from the list of event sources and will not be called again.
    // 也就是在main_loop中,当CPU空闲时会自动执行connect_delayed回调,再跟进connect_delayed
    c->connect_delayed_id = g_idle_add(connect_delayed, channel);

    return true;
}

//在这个函数中可以看到使用的是协程的工作方式
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; //注册入口函数,跟进
    co->release = NULL;

    coroutine_init(co);
    coroutine_yieldto(co, channel);

    return FALSE;
}

// 这个函数就是真正通过socket去连接服务端的地方了
static void *spice_channel_coroutine(void *data)
{
    SpiceChannel *channel = SPICE_CHANNEL(data);
    SpiceChannelPrivate *c = channel->priv;
    guint verify;
    int rc, delay_val = 1;
    gboolean switch_tls = FALSE;
    gboolean switch_protocol = FALSE;
    /* When some other SSL/TLS version becomes obsolete, add it to this
     * variable. */
    long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;

    CHANNEL_DEBUG(channel, "Started background coroutine %p", &c->coroutine);

    // 各种socket的相关操作
    if (spice_session_get_client_provided_socket(c->session)) {
        if (c->fd < 0) {
            g_critical("fd not provided!");
            emit_main_context(channel, SPICE_CHANNEL_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);
                emit_main_context(channel, SPICE_CHANNEL_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:
    // 代码略

ssl_reconnect:
    // 代码略

// 已连接
connected:
    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, &switch_protocol) == FALSE)
        goto cleanup;
    spice_channel_recv_link_msg(channel, &switch_tls);
    if (switch_tls)
        goto cleanup;
    spice_channel_recv_auth(channel);

    // 最终程序会一直阻塞在这个位置,监听通道相关的所有操作
    while (spice_channel_iterate(channel))
        ;

cleanup:
    // 清理回收资源,代码略
    return NULL;
}
本文链接:http://www.maben.com.cn/archives/732.html转载请注明出处