diff --git a/ibis/ibisclient.h b/ibis/ibisclient.h
--- a/ibis/ibisclient.h
+++ b/ibis/ibisclient.h
@@ -65,6 +65,21 @@
 #include "ibiscapabilities.h"
 
 /**
+ * ibis_client_connect:
+ * @client: The instance.
+ * @hostname: The hostname or IP address to connect to.
+ * @port: The port to connect to.
+ * @tls: Whether or not to use a TLS connection.
+ * @proxy_resolver: (transfer none) (nullable): A [iface@Gio.ProxyResolver].
+ *
+ * Attempts to connect to @hostname on @port.
+ *
+ * Since: 0.1
+ */
+IBIS_AVAILABLE_IN_0_1
+void ibis_client_connect(IbisClient *client, const char *hostname, guint16 port, gboolean tls, GProxyResolver *proxy_resolver);
+
+/**
  * ibis_client_disconnect:
  * @client: The instance.
  * @error: (transfer full) (nullable): A #GError.
# HG changeset patch
# Node ID 66489aaafaa7
# Parent  964e6ade0d38
diff --git a/ibis/ibisclient.c b/ibis/ibisclient.c
--- a/ibis/ibisclient.c
+++ b/ibis/ibisclient.c
@@ -189,6 +189,27 @@
 	                                    client);
 }
 
+static void
+ibis_client_connect_cb(GObject *source, GAsyncResult *result, gpointer data) {
+	IbisClient *client = data;
+	GError *error = NULL;
+	GSocketClient *socket_client = G_SOCKET_CLIENT(source);
+	GSocketConnection *connection = NULL;
+
+	connection = g_socket_client_connect_to_host_finish(socket_client, result,
+	                                                    &error);
+	if(error != NULL) {
+		g_prefix_error_literal(&error, _("Unable to connect: "));
+		ibis_client_disconnect(client, error, NULL);
+	} else {
+		ibis_client_start(client, G_IO_STREAM(connection));
+	}
+
+	g_clear_error(&error);
+	g_clear_object(&socket_client);
+	g_clear_object(&connection);
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -352,6 +373,29 @@
  * Public API
  *****************************************************************************/
 void
+ibis_client_connect(IbisClient *client, const char *hostname, guint16 port,
+                    gboolean tls, GProxyResolver *proxy_resolver)
+{
+	GSocketClient *socket_client = NULL;
+
+	g_return_if_fail(IBIS_IS_CLIENT(client));
+	g_return_if_fail(ibis_str_is_empty(hostname));
+
+	socket_client = g_socket_client_new();
+	g_socket_client_set_proxy_resolver(socket_client, proxy_resolver);
+
+	if(tls) {
+		g_socket_client_set_tls(socket_client, tls);
+	}
+
+	client->cancellable = g_cancellable_new();
+
+	g_socket_client_connect_to_host_async(socket_client, hostname, port,
+	                                      client->cancellable,
+	                                      ibis_client_connect_cb, client);
+}
+
+void
 ibis_client_disconnect(IbisClient *client, GError *error, const char *message)
 {
 	GError *close_error = NULL;
@@ -372,24 +416,36 @@
 
 	ibis_client_set_error(client, error);
 
-	/* If closing the stream fails, the stream still gets closed, but we can
-	 * update the error message if it wasn't already set.
-	 */
-	if(!g_io_stream_close(client->stream, client->cancellable, &close_error)) {
-		if(close_error != NULL) {
-			g_warning("failed to close stream: %s", close_error->message);
-		} else {
-			g_warning("failed to close stream: unknown error");
+	if(G_IS_IO_STREAM(client->stream)) {
+		if(!g_io_stream_is_closed(client->stream)) {
+			gboolean success = FALSE;
+
+			/* If closing the stream fails, the stream still gets closed, but
+			 * we can update the error message if it wasn't already set.
+			 */
+			success = g_io_stream_close(client->stream, client->cancellable,
+			                           &close_error);
+
+			if(!success) {
+				if(close_error != NULL) {
+					g_warning("failed to close stream: %s",
+					          close_error->message);
+				} else {
+					g_warning("failed to close stream: unknown error");
+				}
+
+				if(client->error == NULL) {
+					ibis_client_set_error(client, close_error);
+				}
+
+				g_clear_error(&close_error);
+			}
 		}
-
-		if(client->error == NULL) {
-			ibis_client_set_error(client, close_error);
-		}
-
-		g_clear_error(&close_error);
 	}
 
 	ibis_client_set_connected(client, FALSE);
+
+	g_clear_object(&client->cancellable);
 }
 
 GCancellable *
