天天看点

spice-gtk网络套接字创建与连接

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