1、创建网络套接字
--> spice_channel_constructed
--> signals[SPICE_SESSION_CHANNEL_NEW]
--> g_signal_connect(session, "channel-new", G_CALLBACK(channel_new), NULL);
-->channel_new(d->session, it->data, display);
-->spice_channel_connect(channel);
--> return channel_connect(channel, FALSE);
--> c->connect_delayed_id = g_idle_add(connect_delayed, channel);
--> co->entry = spice_channel_coroutine;
--> c->conn = spice_session_channel_open_host(c->session, channel, &c->tls, &c->error);
/* coroutine context */
G_GNUC_INTERNAL
GSocketConnection* spice_session_channel_open_host(SpiceSession *session, SpiceChannel *channel,
gboolean *use_tls, GError **error)
{
g_return_val_if_fail(SPICE_IS_SESSION(session), NULL);
SpiceSessionPrivate *s = session->priv;
SpiceChannelPrivate *c = channel->priv;
spice_open_host open_host = { 0, };
gchar *port, *endptr;
// FIXME: make open_host() cancellable
open_host.from = coroutine_self();
open_host.session = session;
open_host.channel = channel;
const char *name = spice_channel_type_to_string(c->channel_type);
if (spice_strv_contains(s->secure_channels, "all") ||
spice_strv_contains(s->secure_channels, name))
*use_tls = TRUE;
if (s->unix_path) {
if (*use_tls) {
CHANNEL_DEBUG(channel, "No TLS for Unix sockets");
return NULL;
}
} else {
port = *use_tls ? s->tls_port : s->port;
if (port == NULL) {
SPICE_DEBUG("Missing port value, not attempting %s connection.",
*use_tls?"TLS":"unencrypted");
return NULL;
}
open_host.port = strtol(port, &endptr, 10);
if (*port == '\0' || *endptr != '\0' ||
open_host.port <= 0 || open_host.port > G_MAXUINT16) {
g_warning("Invalid port value %s", port);
return NULL;
}
}
if (*use_tls) {
CHANNEL_DEBUG(channel, "Using TLS, port %d", open_host.port);
} else {
CHANNEL_DEBUG(channel, "Using plain text, port %d", open_host.port);
}
open_host.client =g_socket_client_new();
g_socket_client_set_enable_proxy(open_host.client, s->proxy != NULL);
g_socket_client_set_timeout(open_host.client, SOCKET_TIMEOUT);
g_idle_add(open_host_idle_cb, &open_host);
/* switch to main loop and wait for connection */
coroutine_yield(NULL);//非阻塞同步
if (open_host.error != NULL) {
CHANNEL_DEBUG(channel, "open host: %s", open_host.error->message);
g_propagate_error(error, open_host.error);
} else if (open_host.connection != NULL) {
GSocket *socket;
socket = g_socket_connection_get_socket(open_host.connection);
g_socket_set_timeout(socket, 0);
g_socket_set_blocking(socket, FALSE);
g_socket_set_keepalive(socket, TRUE);
/* Make client timeouts a bit more responsive */
#if defined(_WIN32)
/* Windows does not support setting count */
struct tcp_keepalive keepalive = {
TRUE,
30 * 1000,
5 * 1000
};
DWORD written;
WSAIoctl(g_socket_get_fd(socket), SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &written, NULL, NULL);
#elif defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL)
g_socket_set_option(socket, SOL_TCP, TCP_KEEPIDLE, 30, NULL);
g_socket_set_option(socket, SOL_TCP, TCP_KEEPINTVL, 15, NULL);
g_socket_set_option(socket, SOL_TCP, TCP_KEEPCNT, 3, NULL);
#endif
}
g_clear_object(&open_host.client);
return open_host.connection;
}
2、open_host_idle_cb函数:挂起协程并建立连接
/* main context */
static gboolean open_host_idle_cb(gpointer data)
{
spice_open_host *open_host = data;
SpiceSessionPrivate *s;
g_return_val_if_fail(open_host != NULL, FALSE);
g_return_val_if_fail(open_host->connection == NULL, FALSE);
if (spice_channel_get_session(open_host->channel) != open_host->session)
return FALSE;
s = open_host->session->priv;
open_host->proxy = s->proxy;
if (open_host->error != NULL) {
coroutine_yieldto(open_host->from, NULL);//重新继续执行
return FALSE;
}
if (open_host->proxy) {
g_resolver_lookup_by_name_async(g_resolver_get_default(),
spice_uri_get_hostname(open_host->proxy),
open_host->cancellable,
proxy_lookup_ready, open_host);
} else {
GSocketConnectable *address = NULL;
if (s->unix_path) {
SPICE_DEBUG("open unix path %s", s->unix_path);
#ifdef G_OS_UNIX
address = G_SOCKET_CONNECTABLE(g_unix_socket_address_new(s->unix_path));
#else
g_set_error_literal(&open_host->error, SPICE_CLIENT_ERROR, SPICE_CLIENT_ERROR_FAILED,
"Unix path unsupported on this platform");
#endif
} else {
SPICE_DEBUG("open host %s:%d", s->host, open_host->port);
address = g_network_address_parse(s->host, open_host->port, &open_host->error);
}
if (address == NULL || open_host->error != NULL) {
coroutine_yieldto(open_host->from, NULL);
return FALSE;
}
open_host_connectable_connect(open_host, address);
g_object_unref(address);
}
if (open_host->proxy != NULL) {
gchar *str = spice_uri_to_string(open_host->proxy);
SPICE_DEBUG("(with proxy %s)", str);
g_free(str);
}
return FALSE;
}
3、g_socket_client_connect_async函数:这是异步版本g_socket_client_connect()。即使在同步命令行程序中,您也可能更喜欢异步版本,因为从 2.60 开始,它实现了 RFC 8305 “Happy Eyeballs”建议,以通过同时执行 IPv4 连接而不等待 IPv6 来解决 IPv6 断开的网络中的长连接超时超时,同步调用不支持。(这不是 API 保证,将来可能会更改。)
当操作完成callback 时将被调用。
/* main context */
static void open_host_connectable_connect(spice_open_host *open_host, GSocketConnectable *connectable)
{
CHANNEL_DEBUG(open_host->channel, "connecting %p...", open_host);
g_socket_client_connect_async(open_host->client, connectable,
open_host->cancellable,
socket_client_connect_ready, open_host);
}
然后您可以调用g_socket_client_connect_finish()以获取操作结果。
static void socket_client_connect_ready(GObject *source_object, GAsyncResult *result,
gpointer data)
{
GSocketClient *client = G_SOCKET_CLIENT(source_object);
spice_open_host *open_host = data;
GSocketConnection *connection = NULL;
CHANNEL_DEBUG(open_host->channel, "connect ready");
connection = g_socket_client_connect_finish(client, result, &open_host->error);
if (connection == NULL) {
g_warn_if_fail(open_host->error != NULL);
goto end;
}
open_host->connection = connection;
end:
coroutine_yieldto(open_host->from, NULL);
}