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