spice-gtk源码分析1

 spice-gtk作为spice三方(spice guest,spice server,spice client)客户端,主要负责显示guest端的桌面图像信息,并获取client端输入(键盘,鼠标等)消息 。

client和server通信通过多个channel来传递消息。

主通道传输控制消息(鼠标模式,vdagent消息(剪切板)等);

显示通道传输图像数据

输入通道传输client端输入数据(键盘鼠标等)

鼠标通道传输鼠标显示数据

音频播放通道用来接收音频数据

录音通道用来传输录音

后面几个通道不常用,不说了

enum {
    SPICE_CHANNEL_MAIN = 1,      //    主通道
    SPICE_CHANNEL_DISPLAY,       //    显示通道
    SPICE_CHANNEL_INPUTS,        //    输入通道
    SPICE_CHANNEL_CURSOR,        //    鼠标通道
    SPICE_CHANNEL_PLAYBACK,      //    音频播放通道
    SPICE_CHANNEL_RECORD,        //    录音通道
    SPICE_CHANNEL_TUNNEL,
    SPICE_CHANNEL_SMARTCARD,
    SPICE_CHANNEL_USBREDIR,
    SPICE_CHANNEL_PORT,
    SPICE_CHANNEL_FILE,
    SPICE_CHANNEL_WEBDAV,

    SPICE_END_CHANNEL
};

客户端采用gtk+glib的框架,gtk负责界面,glib库是一个综合的C语言函数库,包括基本数据类型,基本数据结构,IO网络库。

int main(int argc, char *argv[])
{
    ...
    /* parse opts */
    gtk_init(&argc, &argv);    //    gtk框架初始化

    ...

    mainloop = g_main_loop_new(NULL, false);  

    conn = connection_new();    //    创建session(主要是session),并注册channel-new,channel-destroy几个event的回调函数
    spice_set_session_option(conn->session);    //    session填充
    spice_cmdline_session_setup(conn->session);

    ...

    connection_connect(conn);    //    连接spice server
    if (connections > 0)
        g_main_loop_run(mainloop);
    g_main_loop_unref(mainloop);

   ...
    return 0;

 

connection_connect,经过几次调用会到 spice_session_connect这里

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);    //    创建main channel

    glz_decoder_window_clear(s->glz_window);
    return spice_channel_connect(s->cmain);    //    连接spice server
}

spice_channel_connect经过一通调用最后到了spice_channel_coroutine

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:
    //    连接spice server
    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));

    ...

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));
    }

    //   send link中协商版本号,发送一些channel识别和支持的功能消息,recv_auth确认没问题后channel连接上
    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;
//    连接上后开始一个循环,循环中轮流写读channel消息
    while (spice_channel_iterate(channel))
        ;

cleanup:
    ...
}

到这里只启动了main channel,当main channel建立连接后,客户端channel初始化过程中,server会向客户端发送SPICE_MSG_MAIN_INIT,然后客户端回一个SPICE_MSGC_MAIN_ATTACH_CHANNELS,然后server会发送一个SPICE_MSG_MAIN_CHANNELS_LIST消息,数据中包含能支持建立连接的channel类别,消息处理函数如下,根据type的不同,调用_channel_new来创建其他几个channel,几个channel创建的时候,回向session发送一个channel-new的event,触发回调函数,display channel就是在回调函数里与server建立连接的

static void main_handle_channels_list(SpiceChannel *channel, SpiceMsgIn *in)
{
    SpiceMsgChannels *msg = spice_msg_in_parsed(in);
    SpiceSession *session;
    int i;

    session = spice_channel_get_session(channel);

    /* guarantee that uuid is notified before setting up the channels, even if
     * the server is older and doesn't actually send the uuid */
    g_coroutine_object_notify(G_OBJECT(session), "uuid");

    for (i = 0; i < msg->num_of_channels; i++) {
        channel_new_t *c;

        c = g_new(channel_new_t, 1);
        c->session = g_object_ref(session);
        c->type = msg->channels[i].type;
        c->id = msg->channels[i].id;
        /* no need to explicitely switch to main context, since
           synchronous call is not needed. */
        /* no need to track idle, session is refed */
        g_idle_add((GSourceFunc)_channel_new, c);
    }
}

 

 

你可能感兴趣的:(SPICE)