使用过spice-gtk进行远程连接虚拟机的朋友应该都知道,正在连接虚拟机的过程中,如果网络断开,虚拟机窗口界面就会卡死,也不会给出任何提示信息,那么下面我们来实现spice-gtk网络超时后自动退出。
首先找到spice-channel.c这个是spice-gtk的核心网络层,找到setsockopt函数所在位置加入:
int keepAlive = 1; // 设定 KeepAlive
int keepIdle = 3; // 开始首次 KeepAlive 探测前的 TCP 空闭时间
int keepInterval = 3; // 两次 KeepAlive 探测间的时间间隔
int keepCount = 1; // 判定断开前的 KeepAlive 探测次数
setsockopt(g_socket_get_fd(c->sock), SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
setsockopt(g_socket_get_fd(c->sock), SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
setsockopt(g_socket_get_fd(c->sock), SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
setsockopt(g_socket_get_fd(c->sock), SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
int keepAlive = 1; // 设定 KeepAlive
int keepIdle = 3; // 开始首次 KeepAlive 探测前的 TCP 空闭时间
int keepInterval = 3; // 两次 KeepAlive 探测间的时间间隔
int keepCount = 1; // 判定断开前的 KeepAlive 探测次数
if (setsockopt(g_socket_get_fd(c->sock), SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive)) == -1)
printf("keepAlive error\n");
if (setsockopt(g_socket_get_fd(c->sock), SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle)) == -1)
printf("keepIdle error\n");
if (setsockopt(g_socket_get_fd(c->sock), SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval)) == -1)
printf("keepInterval error\n");
if (setsockopt(g_socket_get_fd(c->sock), SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount)) == -1)
printf("keepCount error\n");
rc = setsockopt(g_socket_get_fd(c->sock), IPPROTO_TCP, TCP_NODELAY,
(const char*)&delay_val, sizeof(delay_val));
超时候虚拟机会在下面地方作出反应:spice_channel_iterate这个函数是处理所有信息的循环,一旦socket出现问题,这边都是会做出反应的。在ret = g_socket_condition_check(c->sock, G_IO_IN | G_IO_ERR);这个函数中添加一个参数变成:
ret = g_socket_condition_check(c->sock, G_IO_IN | G_IO_ERR | G_IO_HUP); G_IO_HUP就代表着我们io超时的情况,所有这边加了之后一旦网络超时了就会在这边触发,触发之后设置c->event = SPICE_CHANNEL_ERROR_IO;这个信号是传输到spicy.c里面的,毕竟spice-channel.c是库,spicy.c是主界面客户端,是spicy.c调用spice-channel.c库的,一旦库里面的socket出现了问题,会通过信号传输给spicy.c。
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 | G_IO_HUP);
if ((ret & G_IO_ERR) || (ret & G_IO_HUP)) {
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;
else
c->event = SPICE_CHANNEL_ERROR_LINK;
}
}
return FALSE;
}
return TRUE;
}
spice-channel.c里面设置c->event = SPICE_CHANNEL_ERROR_IO;之后会在spicy.c里面接收到这个信号,位置在:
static void main_channel_event(SpiceChannel *channel, SpiceChannelEvent event,
gpointer data)
{
const GError *error = NULL;
spice_connection *conn = data;
char password[64];
int rc;
switch (event) {
。
。
。
case SPICE_CHANNEL_ERROR_IO:
g_message("main channel: error io");
connection_disconnect(conn);
break;
。
。
。
}
}
可以直接调用connection_disconnect结束官方版spice-gtk的进程,或者调用自己写的界面,提示用户此时网络已经断开,作出友好提醒。