mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-09 02:46:16 +01:00
22ba4411cc
Using the generic marshaller has drawbacks beyond performance. One such drawback is that it breaks the stack unwinding from the Linux kernel due to having unsufficient data to walk past ffi_call_unixt64. That means that performance profiling by application developers looks grouped among seemingly unrelated code paths. While we can't fix the kernel unwinding here, we can provide proper c_marshallers and va_marshallers for objects within Gio so that performance profiling of applications is more reliable. Related to GNOME/Initiatives#10
1282 lines
39 KiB
C
1282 lines
39 KiB
C
/* GIO - GLib Input, Output and Streaming Library
|
||
*
|
||
* Copyright © 2008 Christian Kellner, Samuel Cormier-Iijima
|
||
* Copyright © 2009 codethink
|
||
* Copyright © 2009 Red Hat, Inc
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Lesser General Public
|
||
* License as published by the Free Software Foundation; either
|
||
* version 2.1 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Lesser General
|
||
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
* Authors: Christian Kellner <gicmo@gnome.org>
|
||
* Samuel Cormier-Iijima <sciyoshi@gmail.com>
|
||
* Ryan Lortie <desrt@desrt.ca>
|
||
* Alexander Larsson <alexl@redhat.com>
|
||
*/
|
||
|
||
#include "config.h"
|
||
#include "gsocketlistener.h"
|
||
|
||
#include <gio/gioenumtypes.h>
|
||
#include <gio/gtask.h>
|
||
#include <gio/gcancellable.h>
|
||
#include <gio/gsocketaddress.h>
|
||
#include <gio/ginetaddress.h>
|
||
#include <gio/gioerror.h>
|
||
#include <gio/gsocket.h>
|
||
#include <gio/gsocketconnection.h>
|
||
#include <gio/ginetsocketaddress.h>
|
||
#include "glibintl.h"
|
||
#include "gmarshal-internal.h"
|
||
|
||
|
||
/**
|
||
* SECTION:gsocketlistener
|
||
* @title: GSocketListener
|
||
* @short_description: Helper for accepting network client connections
|
||
* @include: gio/gio.h
|
||
* @see_also: #GThreadedSocketService, #GSocketService.
|
||
*
|
||
* A #GSocketListener is an object that keeps track of a set
|
||
* of server sockets and helps you accept sockets from any of the
|
||
* socket, either sync or async.
|
||
*
|
||
* Add addresses and ports to listen on using g_socket_listener_add_address()
|
||
* and g_socket_listener_add_inet_port(). These will be listened on until
|
||
* g_socket_listener_close() is called. Dropping your final reference to the
|
||
* #GSocketListener will not cause g_socket_listener_close() to be called
|
||
* implicitly, as some references to the #GSocketListener may be held
|
||
* internally.
|
||
*
|
||
* If you want to implement a network server, also look at #GSocketService
|
||
* and #GThreadedSocketService which are subclasses of #GSocketListener
|
||
* that make this even easier.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
|
||
enum
|
||
{
|
||
PROP_0,
|
||
PROP_LISTEN_BACKLOG
|
||
};
|
||
|
||
enum
|
||
{
|
||
EVENT,
|
||
LAST_SIGNAL
|
||
};
|
||
|
||
static guint signals[LAST_SIGNAL] = { 0 };
|
||
|
||
static GQuark source_quark = 0;
|
||
|
||
struct _GSocketListenerPrivate
|
||
{
|
||
GPtrArray *sockets;
|
||
GMainContext *main_context;
|
||
int listen_backlog;
|
||
guint closed : 1;
|
||
};
|
||
|
||
G_DEFINE_TYPE_WITH_PRIVATE (GSocketListener, g_socket_listener, G_TYPE_OBJECT)
|
||
|
||
static void
|
||
g_socket_listener_finalize (GObject *object)
|
||
{
|
||
GSocketListener *listener = G_SOCKET_LISTENER (object);
|
||
|
||
if (listener->priv->main_context)
|
||
g_main_context_unref (listener->priv->main_context);
|
||
|
||
/* Do not explicitly close the sockets. Instead, let them close themselves if
|
||
* their final reference is dropped, but keep them open if a reference is
|
||
* held externally to the GSocketListener (which is possible if
|
||
* g_socket_listener_add_socket() was used).
|
||
*/
|
||
g_ptr_array_free (listener->priv->sockets, TRUE);
|
||
|
||
G_OBJECT_CLASS (g_socket_listener_parent_class)
|
||
->finalize (object);
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_get_property (GObject *object,
|
||
guint prop_id,
|
||
GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GSocketListener *listener = G_SOCKET_LISTENER (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_LISTEN_BACKLOG:
|
||
g_value_set_int (value, listener->priv->listen_backlog);
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_set_property (GObject *object,
|
||
guint prop_id,
|
||
const GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GSocketListener *listener = G_SOCKET_LISTENER (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_LISTEN_BACKLOG:
|
||
g_socket_listener_set_backlog (listener, g_value_get_int (value));
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_class_init (GSocketListenerClass *klass)
|
||
{
|
||
GObjectClass *gobject_class G_GNUC_UNUSED = G_OBJECT_CLASS (klass);
|
||
|
||
gobject_class->finalize = g_socket_listener_finalize;
|
||
gobject_class->set_property = g_socket_listener_set_property;
|
||
gobject_class->get_property = g_socket_listener_get_property;
|
||
g_object_class_install_property (gobject_class, PROP_LISTEN_BACKLOG,
|
||
g_param_spec_int ("listen-backlog",
|
||
P_("Listen backlog"),
|
||
P_("outstanding connections in the listen queue"),
|
||
0,
|
||
2000,
|
||
10,
|
||
G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
||
/**
|
||
* GSocketListener::event:
|
||
* @listener: the #GSocketListener
|
||
* @event: the event that is occurring
|
||
* @socket: the #GSocket the event is occurring on
|
||
*
|
||
* Emitted when @listener's activity on @socket changes state.
|
||
* Note that when @listener is used to listen on both IPv4 and
|
||
* IPv6, a separate set of signals will be emitted for each, and
|
||
* the order they happen in is undefined.
|
||
*
|
||
* Since: 2.46
|
||
*/
|
||
signals[EVENT] =
|
||
g_signal_new (I_("event"),
|
||
G_TYPE_FROM_CLASS (gobject_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (GSocketListenerClass, event),
|
||
NULL, NULL,
|
||
_g_cclosure_marshal_VOID__ENUM_OBJECT,
|
||
G_TYPE_NONE, 2,
|
||
G_TYPE_SOCKET_LISTENER_EVENT,
|
||
G_TYPE_SOCKET);
|
||
g_signal_set_va_marshaller (signals[EVENT],
|
||
G_TYPE_FROM_CLASS (gobject_class),
|
||
_g_cclosure_marshal_VOID__ENUM_OBJECTv);
|
||
|
||
source_quark = g_quark_from_static_string ("g-socket-listener-source");
|
||
}
|
||
|
||
static void
|
||
g_socket_listener_init (GSocketListener *listener)
|
||
{
|
||
listener->priv = g_socket_listener_get_instance_private (listener);
|
||
listener->priv->sockets =
|
||
g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
|
||
listener->priv->listen_backlog = 10;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_new:
|
||
*
|
||
* Creates a new #GSocketListener with no sockets to listen for.
|
||
* New listeners can be added with e.g. g_socket_listener_add_address()
|
||
* or g_socket_listener_add_inet_port().
|
||
*
|
||
* Returns: a new #GSocketListener.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocketListener *
|
||
g_socket_listener_new (void)
|
||
{
|
||
return g_object_new (G_TYPE_SOCKET_LISTENER, NULL);
|
||
}
|
||
|
||
static gboolean
|
||
check_listener (GSocketListener *listener,
|
||
GError **error)
|
||
{
|
||
if (listener->priv->closed)
|
||
{
|
||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
|
||
_("Listener is already closed"));
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_socket:
|
||
* @listener: a #GSocketListener
|
||
* @socket: a listening #GSocket
|
||
* @source_object: (nullable): Optional #GObject identifying this source
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Adds @socket to the set of sockets that we try to accept
|
||
* new clients from. The socket must be bound to a local
|
||
* address and listened to.
|
||
*
|
||
* @source_object will be passed out in the various calls
|
||
* to accept to identify this particular source, which is
|
||
* useful if you're listening on multiple addresses and do
|
||
* different things depending on what address is connected to.
|
||
*
|
||
* The @socket will not be automatically closed when the @listener is finalized
|
||
* unless the listener held the final reference to the socket. Before GLib 2.42,
|
||
* the @socket was automatically closed on finalization of the @listener, even
|
||
* if references to it were held elsewhere.
|
||
*
|
||
* Returns: %TRUE on success, %FALSE on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
gboolean
|
||
g_socket_listener_add_socket (GSocketListener *listener,
|
||
GSocket *socket,
|
||
GObject *source_object,
|
||
GError **error)
|
||
{
|
||
if (!check_listener (listener, error))
|
||
return FALSE;
|
||
|
||
/* TODO: Check that socket it is bound & not closed? */
|
||
|
||
if (g_socket_is_closed (socket))
|
||
{
|
||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||
_("Added socket is closed"));
|
||
return FALSE;
|
||
}
|
||
|
||
g_object_ref (socket);
|
||
g_ptr_array_add (listener->priv->sockets, socket);
|
||
|
||
if (source_object)
|
||
g_object_set_qdata_full (G_OBJECT (socket), source_quark,
|
||
g_object_ref (source_object), g_object_unref);
|
||
|
||
|
||
if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
|
||
G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_address:
|
||
* @listener: a #GSocketListener
|
||
* @address: a #GSocketAddress
|
||
* @type: a #GSocketType
|
||
* @protocol: a #GSocketProtocol
|
||
* @source_object: (nullable): Optional #GObject identifying this source
|
||
* @effective_address: (out) (optional): location to store the address that was bound to, or %NULL.
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Creates a socket of type @type and protocol @protocol, binds
|
||
* it to @address and adds it to the set of sockets we're accepting
|
||
* sockets from.
|
||
*
|
||
* Note that adding an IPv6 address, depending on the platform,
|
||
* may or may not result in a listener that also accepts IPv4
|
||
* connections. For more deterministic behavior, see
|
||
* g_socket_listener_add_inet_port().
|
||
*
|
||
* @source_object will be passed out in the various calls
|
||
* to accept to identify this particular source, which is
|
||
* useful if you're listening on multiple addresses and do
|
||
* different things depending on what address is connected to.
|
||
*
|
||
* If successful and @effective_address is non-%NULL then it will
|
||
* be set to the address that the binding actually occurred at. This
|
||
* is helpful for determining the port number that was used for when
|
||
* requesting a binding to port 0 (ie: "any port"). This address, if
|
||
* requested, belongs to the caller and must be freed.
|
||
*
|
||
* Call g_socket_listener_close() to stop listening on @address; this will not
|
||
* be done automatically when you drop your final reference to @listener, as
|
||
* references may be held internally.
|
||
*
|
||
* Returns: %TRUE on success, %FALSE on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
gboolean
|
||
g_socket_listener_add_address (GSocketListener *listener,
|
||
GSocketAddress *address,
|
||
GSocketType type,
|
||
GSocketProtocol protocol,
|
||
GObject *source_object,
|
||
GSocketAddress **effective_address,
|
||
GError **error)
|
||
{
|
||
GSocketAddress *local_address;
|
||
GSocketFamily family;
|
||
GSocket *socket;
|
||
|
||
if (!check_listener (listener, error))
|
||
return FALSE;
|
||
|
||
family = g_socket_address_get_family (address);
|
||
socket = g_socket_new (family, type, protocol, error);
|
||
if (socket == NULL)
|
||
return FALSE;
|
||
|
||
g_socket_set_listen_backlog (socket, listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket);
|
||
|
||
if (!g_socket_bind (socket, address, TRUE, error))
|
||
{
|
||
g_object_unref (socket);
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket);
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket);
|
||
|
||
if (!g_socket_listen (socket, error))
|
||
{
|
||
g_object_unref (socket);
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket);
|
||
|
||
local_address = NULL;
|
||
if (effective_address)
|
||
{
|
||
local_address = g_socket_get_local_address (socket, error);
|
||
if (local_address == NULL)
|
||
{
|
||
g_object_unref (socket);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
if (!g_socket_listener_add_socket (listener, socket,
|
||
source_object,
|
||
error))
|
||
{
|
||
if (local_address)
|
||
g_object_unref (local_address);
|
||
g_object_unref (socket);
|
||
return FALSE;
|
||
}
|
||
|
||
if (effective_address)
|
||
*effective_address = local_address;
|
||
|
||
g_object_unref (socket); /* add_socket refs this */
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_inet_port:
|
||
* @listener: a #GSocketListener
|
||
* @port: an IP port number (non-zero)
|
||
* @source_object: (nullable): Optional #GObject identifying this source
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Helper function for g_socket_listener_add_address() that
|
||
* creates a TCP/IP socket listening on IPv4 and IPv6 (if
|
||
* supported) on the specified port on all interfaces.
|
||
*
|
||
* @source_object will be passed out in the various calls
|
||
* to accept to identify this particular source, which is
|
||
* useful if you're listening on multiple addresses and do
|
||
* different things depending on what address is connected to.
|
||
*
|
||
* Call g_socket_listener_close() to stop listening on @port; this will not
|
||
* be done automatically when you drop your final reference to @listener, as
|
||
* references may be held internally.
|
||
*
|
||
* Returns: %TRUE on success, %FALSE on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
gboolean
|
||
g_socket_listener_add_inet_port (GSocketListener *listener,
|
||
guint16 port,
|
||
GObject *source_object,
|
||
GError **error)
|
||
{
|
||
gboolean need_ipv4_socket = TRUE;
|
||
GSocket *socket4 = NULL;
|
||
GSocket *socket6;
|
||
|
||
g_return_val_if_fail (listener != NULL, FALSE);
|
||
g_return_val_if_fail (port != 0, FALSE);
|
||
|
||
if (!check_listener (listener, error))
|
||
return FALSE;
|
||
|
||
/* first try to create an IPv6 socket */
|
||
socket6 = g_socket_new (G_SOCKET_FAMILY_IPV6,
|
||
G_SOCKET_TYPE_STREAM,
|
||
G_SOCKET_PROTOCOL_DEFAULT,
|
||
NULL);
|
||
|
||
if (socket6 != NULL)
|
||
/* IPv6 is supported on this platform, so if we fail now it is
|
||
* a result of being unable to bind to our port. Don't fail
|
||
* silently as a result of this!
|
||
*/
|
||
{
|
||
GInetAddress *inet_address;
|
||
GSocketAddress *address;
|
||
|
||
inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
|
||
address = g_inet_socket_address_new (inet_address, port);
|
||
g_object_unref (inet_address);
|
||
|
||
g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket6);
|
||
|
||
if (!g_socket_bind (socket6, address, TRUE, error))
|
||
{
|
||
g_object_unref (address);
|
||
g_object_unref (socket6);
|
||
return FALSE;
|
||
}
|
||
|
||
g_object_unref (address);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket6);
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket6);
|
||
|
||
if (!g_socket_listen (socket6, error))
|
||
{
|
||
g_object_unref (socket6);
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket6);
|
||
|
||
if (source_object)
|
||
g_object_set_qdata_full (G_OBJECT (socket6), source_quark,
|
||
g_object_ref (source_object),
|
||
g_object_unref);
|
||
|
||
/* If this socket already speaks IPv4 then we are done. */
|
||
if (g_socket_speaks_ipv4 (socket6))
|
||
need_ipv4_socket = FALSE;
|
||
}
|
||
|
||
if (need_ipv4_socket)
|
||
/* We are here for exactly one of the following reasons:
|
||
*
|
||
* - our platform doesn't support IPv6
|
||
* - we successfully created an IPv6 socket but it's V6ONLY
|
||
*
|
||
* In either case, we need to go ahead and create an IPv4 socket
|
||
* and fail the call if we can't bind to it.
|
||
*/
|
||
{
|
||
socket4 = g_socket_new (G_SOCKET_FAMILY_IPV4,
|
||
G_SOCKET_TYPE_STREAM,
|
||
G_SOCKET_PROTOCOL_DEFAULT,
|
||
error);
|
||
|
||
if (socket4 != NULL)
|
||
/* IPv4 is supported on this platform, so if we fail now it is
|
||
* a result of being unable to bind to our port. Don't fail
|
||
* silently as a result of this!
|
||
*/
|
||
{
|
||
GInetAddress *inet_address;
|
||
GSocketAddress *address;
|
||
|
||
inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
|
||
address = g_inet_socket_address_new (inet_address, port);
|
||
g_object_unref (inet_address);
|
||
|
||
g_socket_set_listen_backlog (socket4,
|
||
listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket4);
|
||
|
||
if (!g_socket_bind (socket4, address, TRUE, error))
|
||
{
|
||
g_object_unref (address);
|
||
g_object_unref (socket4);
|
||
if (socket6 != NULL)
|
||
g_object_unref (socket6);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
g_object_unref (address);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket4);
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket4);
|
||
|
||
if (!g_socket_listen (socket4, error))
|
||
{
|
||
g_object_unref (socket4);
|
||
if (socket6 != NULL)
|
||
g_object_unref (socket6);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket4);
|
||
|
||
if (source_object)
|
||
g_object_set_qdata_full (G_OBJECT (socket4), source_quark,
|
||
g_object_ref (source_object),
|
||
g_object_unref);
|
||
}
|
||
else
|
||
/* Ok. So IPv4 is not supported on this platform. If we
|
||
* succeeded at creating an IPv6 socket then that's OK, but
|
||
* otherwise we need to tell the user we failed.
|
||
*/
|
||
{
|
||
if (socket6 != NULL)
|
||
g_clear_error (error);
|
||
else
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
g_assert (socket6 != NULL || socket4 != NULL);
|
||
|
||
if (socket6 != NULL)
|
||
g_ptr_array_add (listener->priv->sockets, socket6);
|
||
|
||
if (socket4 != NULL)
|
||
g_ptr_array_add (listener->priv->sockets, socket4);
|
||
|
||
if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
|
||
G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static GList *
|
||
add_sources (GSocketListener *listener,
|
||
GSocketSourceFunc callback,
|
||
gpointer callback_data,
|
||
GCancellable *cancellable,
|
||
GMainContext *context)
|
||
{
|
||
GSocket *socket;
|
||
GSource *source;
|
||
GList *sources;
|
||
int i;
|
||
|
||
sources = NULL;
|
||
for (i = 0; i < listener->priv->sockets->len; i++)
|
||
{
|
||
socket = listener->priv->sockets->pdata[i];
|
||
|
||
source = g_socket_create_source (socket, G_IO_IN, cancellable);
|
||
g_source_set_callback (source,
|
||
(GSourceFunc) callback,
|
||
callback_data, NULL);
|
||
g_source_attach (source, context);
|
||
|
||
sources = g_list_prepend (sources, source);
|
||
}
|
||
|
||
return sources;
|
||
}
|
||
|
||
static void
|
||
free_sources (GList *sources)
|
||
{
|
||
GSource *source;
|
||
while (sources != NULL)
|
||
{
|
||
source = sources->data;
|
||
sources = g_list_delete_link (sources, sources);
|
||
g_source_destroy (source);
|
||
g_source_unref (source);
|
||
}
|
||
}
|
||
|
||
struct AcceptData {
|
||
GMainLoop *loop;
|
||
GSocket *socket;
|
||
};
|
||
|
||
static gboolean
|
||
accept_callback (GSocket *socket,
|
||
GIOCondition condition,
|
||
gpointer user_data)
|
||
{
|
||
struct AcceptData *data = user_data;
|
||
|
||
data->socket = socket;
|
||
g_main_loop_quit (data->loop);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_socket:
|
||
* @listener: a #GSocketListener
|
||
* @source_object: (out) (transfer none) (optional) (nullable): location where #GObject pointer will be stored, or %NULL.
|
||
* @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Blocks waiting for a client to connect to any of the sockets added
|
||
* to the listener. Returns the #GSocket that was accepted.
|
||
*
|
||
* If you want to accept the high-level #GSocketConnection, not a #GSocket,
|
||
* which is often the case, then you should use g_socket_listener_accept()
|
||
* instead.
|
||
*
|
||
* If @source_object is not %NULL it will be filled out with the source
|
||
* object specified when the corresponding socket or address was added
|
||
* to the listener.
|
||
*
|
||
* If @cancellable is not %NULL, then the operation can be cancelled by
|
||
* triggering the cancellable object from another thread. If the operation
|
||
* was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
|
||
*
|
||
* Returns: (transfer full): a #GSocket on success, %NULL on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocket *
|
||
g_socket_listener_accept_socket (GSocketListener *listener,
|
||
GObject **source_object,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GSocket *accept_socket, *socket;
|
||
|
||
g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), NULL);
|
||
|
||
if (!check_listener (listener, error))
|
||
return NULL;
|
||
|
||
if (listener->priv->sockets->len == 1)
|
||
{
|
||
accept_socket = listener->priv->sockets->pdata[0];
|
||
if (!g_socket_condition_wait (accept_socket, G_IO_IN,
|
||
cancellable, error))
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
GList *sources;
|
||
struct AcceptData data;
|
||
GMainLoop *loop;
|
||
|
||
if (listener->priv->main_context == NULL)
|
||
listener->priv->main_context = g_main_context_new ();
|
||
|
||
loop = g_main_loop_new (listener->priv->main_context, FALSE);
|
||
data.loop = loop;
|
||
sources = add_sources (listener,
|
||
accept_callback,
|
||
&data,
|
||
cancellable,
|
||
listener->priv->main_context);
|
||
g_main_loop_run (loop);
|
||
accept_socket = data.socket;
|
||
free_sources (sources);
|
||
g_main_loop_unref (loop);
|
||
}
|
||
|
||
if (!(socket = g_socket_accept (accept_socket, cancellable, error)))
|
||
return NULL;
|
||
|
||
if (source_object)
|
||
*source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark);
|
||
|
||
return socket;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept:
|
||
* @listener: a #GSocketListener
|
||
* @source_object: (out) (transfer none) (optional) (nullable): location where #GObject pointer will be stored, or %NULL
|
||
* @cancellable: (nullable): optional #GCancellable object, %NULL to ignore.
|
||
* @error: #GError for error reporting, or %NULL to ignore.
|
||
*
|
||
* Blocks waiting for a client to connect to any of the sockets added
|
||
* to the listener. Returns a #GSocketConnection for the socket that was
|
||
* accepted.
|
||
*
|
||
* If @source_object is not %NULL it will be filled out with the source
|
||
* object specified when the corresponding socket or address was added
|
||
* to the listener.
|
||
*
|
||
* If @cancellable is not %NULL, then the operation can be cancelled by
|
||
* triggering the cancellable object from another thread. If the operation
|
||
* was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
|
||
*
|
||
* Returns: (transfer full): a #GSocketConnection on success, %NULL on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocketConnection *
|
||
g_socket_listener_accept (GSocketListener *listener,
|
||
GObject **source_object,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GSocketConnection *connection;
|
||
GSocket *socket;
|
||
|
||
socket = g_socket_listener_accept_socket (listener,
|
||
source_object,
|
||
cancellable,
|
||
error);
|
||
if (socket == NULL)
|
||
return NULL;
|
||
|
||
connection = g_socket_connection_factory_create_connection (socket);
|
||
g_object_unref (socket);
|
||
|
||
return connection;
|
||
}
|
||
|
||
typedef struct
|
||
{
|
||
GList *sources; /* (element-type GSource) */
|
||
gboolean returned_yet;
|
||
} AcceptSocketAsyncData;
|
||
|
||
static void
|
||
accept_socket_async_data_free (AcceptSocketAsyncData *data)
|
||
{
|
||
free_sources (data->sources);
|
||
g_free (data);
|
||
}
|
||
|
||
static gboolean
|
||
accept_ready (GSocket *accept_socket,
|
||
GIOCondition condition,
|
||
gpointer user_data)
|
||
{
|
||
GTask *task = user_data;
|
||
GError *error = NULL;
|
||
GSocket *socket;
|
||
GObject *source_object;
|
||
AcceptSocketAsyncData *data = g_task_get_task_data (task);
|
||
|
||
/* Don’t call g_task_return_*() multiple times if we have multiple incoming
|
||
* connections in the same #GMainContext iteration. */
|
||
if (data->returned_yet)
|
||
return G_SOURCE_REMOVE;
|
||
|
||
socket = g_socket_accept (accept_socket, g_task_get_cancellable (task), &error);
|
||
if (socket)
|
||
{
|
||
source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark);
|
||
if (source_object)
|
||
g_object_set_qdata_full (G_OBJECT (task),
|
||
source_quark,
|
||
g_object_ref (source_object), g_object_unref);
|
||
g_task_return_pointer (task, socket, g_object_unref);
|
||
}
|
||
else
|
||
{
|
||
g_task_return_error (task, error);
|
||
}
|
||
|
||
data->returned_yet = TRUE;
|
||
g_object_unref (task);
|
||
|
||
return G_SOURCE_REMOVE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_socket_async:
|
||
* @listener: a #GSocketListener
|
||
* @cancellable: (nullable): a #GCancellable, or %NULL
|
||
* @callback: (scope async): a #GAsyncReadyCallback
|
||
* @user_data: (closure): user data for the callback
|
||
*
|
||
* This is the asynchronous version of g_socket_listener_accept_socket().
|
||
*
|
||
* When the operation is finished @callback will be
|
||
* called. You can then call g_socket_listener_accept_socket_finish()
|
||
* to get the result of the operation.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_accept_socket_async (GSocketListener *listener,
|
||
GCancellable *cancellable,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
GTask *task;
|
||
GError *error = NULL;
|
||
AcceptSocketAsyncData *data = NULL;
|
||
|
||
task = g_task_new (listener, cancellable, callback, user_data);
|
||
g_task_set_source_tag (task, g_socket_listener_accept_socket_async);
|
||
|
||
if (!check_listener (listener, &error))
|
||
{
|
||
g_task_return_error (task, error);
|
||
g_object_unref (task);
|
||
return;
|
||
}
|
||
|
||
data = g_new0 (AcceptSocketAsyncData, 1);
|
||
data->returned_yet = FALSE;
|
||
data->sources = add_sources (listener,
|
||
accept_ready,
|
||
task,
|
||
cancellable,
|
||
g_main_context_get_thread_default ());
|
||
g_task_set_task_data (task, g_steal_pointer (&data),
|
||
(GDestroyNotify) accept_socket_async_data_free);
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_socket_finish:
|
||
* @listener: a #GSocketListener
|
||
* @result: a #GAsyncResult.
|
||
* @source_object: (out) (transfer none) (optional) (nullable): Optional #GObject identifying this source
|
||
* @error: a #GError location to store the error occurring, or %NULL to
|
||
* ignore.
|
||
*
|
||
* Finishes an async accept operation. See g_socket_listener_accept_socket_async()
|
||
*
|
||
* Returns: (transfer full): a #GSocket on success, %NULL on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocket *
|
||
g_socket_listener_accept_socket_finish (GSocketListener *listener,
|
||
GAsyncResult *result,
|
||
GObject **source_object,
|
||
GError **error)
|
||
{
|
||
g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), NULL);
|
||
g_return_val_if_fail (g_task_is_valid (result, listener), NULL);
|
||
|
||
if (source_object)
|
||
*source_object = g_object_get_qdata (G_OBJECT (result), source_quark);
|
||
|
||
return g_task_propagate_pointer (G_TASK (result), error);
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_async:
|
||
* @listener: a #GSocketListener
|
||
* @cancellable: (nullable): a #GCancellable, or %NULL
|
||
* @callback: (scope async): a #GAsyncReadyCallback
|
||
* @user_data: (closure): user data for the callback
|
||
*
|
||
* This is the asynchronous version of g_socket_listener_accept().
|
||
*
|
||
* When the operation is finished @callback will be
|
||
* called. You can then call g_socket_listener_accept_socket()
|
||
* to get the result of the operation.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_accept_async (GSocketListener *listener,
|
||
GCancellable *cancellable,
|
||
GAsyncReadyCallback callback,
|
||
gpointer user_data)
|
||
{
|
||
g_socket_listener_accept_socket_async (listener,
|
||
cancellable,
|
||
callback,
|
||
user_data);
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_accept_finish:
|
||
* @listener: a #GSocketListener
|
||
* @result: a #GAsyncResult.
|
||
* @source_object: (out) (transfer none) (optional) (nullable): Optional #GObject identifying this source
|
||
* @error: a #GError location to store the error occurring, or %NULL to
|
||
* ignore.
|
||
*
|
||
* Finishes an async accept operation. See g_socket_listener_accept_async()
|
||
*
|
||
* Returns: (transfer full): a #GSocketConnection on success, %NULL on error.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
GSocketConnection *
|
||
g_socket_listener_accept_finish (GSocketListener *listener,
|
||
GAsyncResult *result,
|
||
GObject **source_object,
|
||
GError **error)
|
||
{
|
||
GSocket *socket;
|
||
GSocketConnection *connection;
|
||
|
||
socket = g_socket_listener_accept_socket_finish (listener,
|
||
result,
|
||
source_object,
|
||
error);
|
||
if (socket == NULL)
|
||
return NULL;
|
||
|
||
connection = g_socket_connection_factory_create_connection (socket);
|
||
g_object_unref (socket);
|
||
return connection;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_set_backlog:
|
||
* @listener: a #GSocketListener
|
||
* @listen_backlog: an integer
|
||
*
|
||
* Sets the listen backlog on the sockets in the listener. This must be called
|
||
* before adding any sockets, addresses or ports to the #GSocketListener (for
|
||
* example, by calling g_socket_listener_add_inet_port()) to be effective.
|
||
*
|
||
* See g_socket_set_listen_backlog() for details
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_set_backlog (GSocketListener *listener,
|
||
int listen_backlog)
|
||
{
|
||
GSocket *socket;
|
||
int i;
|
||
|
||
if (listener->priv->closed)
|
||
return;
|
||
|
||
listener->priv->listen_backlog = listen_backlog;
|
||
|
||
for (i = 0; i < listener->priv->sockets->len; i++)
|
||
{
|
||
socket = listener->priv->sockets->pdata[i];
|
||
g_socket_set_listen_backlog (socket, listen_backlog);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_close:
|
||
* @listener: a #GSocketListener
|
||
*
|
||
* Closes all the sockets in the listener.
|
||
*
|
||
* Since: 2.22
|
||
*/
|
||
void
|
||
g_socket_listener_close (GSocketListener *listener)
|
||
{
|
||
GSocket *socket;
|
||
int i;
|
||
|
||
g_return_if_fail (G_IS_SOCKET_LISTENER (listener));
|
||
|
||
if (listener->priv->closed)
|
||
return;
|
||
|
||
for (i = 0; i < listener->priv->sockets->len; i++)
|
||
{
|
||
socket = listener->priv->sockets->pdata[i];
|
||
g_socket_close (socket, NULL);
|
||
}
|
||
listener->priv->closed = TRUE;
|
||
}
|
||
|
||
/**
|
||
* g_socket_listener_add_any_inet_port:
|
||
* @listener: a #GSocketListener
|
||
* @source_object: (nullable): Optional #GObject identifying this source
|
||
* @error: a #GError location to store the error occurring, or %NULL to
|
||
* ignore.
|
||
*
|
||
* Listens for TCP connections on any available port number for both
|
||
* IPv6 and IPv4 (if each is available).
|
||
*
|
||
* This is useful if you need to have a socket for incoming connections
|
||
* but don't care about the specific port number.
|
||
*
|
||
* @source_object will be passed out in the various calls
|
||
* to accept to identify this particular source, which is
|
||
* useful if you're listening on multiple addresses and do
|
||
* different things depending on what address is connected to.
|
||
*
|
||
* Returns: the port number, or 0 in case of failure.
|
||
*
|
||
* Since: 2.24
|
||
**/
|
||
guint16
|
||
g_socket_listener_add_any_inet_port (GSocketListener *listener,
|
||
GObject *source_object,
|
||
GError **error)
|
||
{
|
||
GSList *sockets_to_close = NULL;
|
||
guint16 candidate_port = 0;
|
||
GSocket *socket6 = NULL;
|
||
GSocket *socket4 = NULL;
|
||
gint attempts = 37;
|
||
|
||
/*
|
||
* multi-step process:
|
||
* - first, create an IPv6 socket.
|
||
* - if that fails, create an IPv4 socket and bind it to port 0 and
|
||
* that's it. no retries if that fails (why would it?).
|
||
* - if our IPv6 socket also speaks IPv4 then we are done.
|
||
* - if not, then we need to create a IPv4 socket with the same port
|
||
* number. this might fail, of course. so we try this a bunch of
|
||
* times -- leaving the old IPv6 sockets open so that we get a
|
||
* different port number to try each time.
|
||
* - if all that fails then just give up.
|
||
*/
|
||
|
||
while (attempts--)
|
||
{
|
||
GInetAddress *inet_address;
|
||
GSocketAddress *address;
|
||
gboolean result;
|
||
|
||
g_assert (socket6 == NULL);
|
||
socket6 = g_socket_new (G_SOCKET_FAMILY_IPV6,
|
||
G_SOCKET_TYPE_STREAM,
|
||
G_SOCKET_PROTOCOL_DEFAULT,
|
||
NULL);
|
||
|
||
if (socket6 != NULL)
|
||
{
|
||
inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
|
||
address = g_inet_socket_address_new (inet_address, 0);
|
||
g_object_unref (inet_address);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket6);
|
||
|
||
result = g_socket_bind (socket6, address, TRUE, error);
|
||
g_object_unref (address);
|
||
|
||
if (!result ||
|
||
!(address = g_socket_get_local_address (socket6, error)))
|
||
{
|
||
g_object_unref (socket6);
|
||
socket6 = NULL;
|
||
break;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket6);
|
||
|
||
g_assert (G_IS_INET_SOCKET_ADDRESS (address));
|
||
candidate_port =
|
||
g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
|
||
g_assert (candidate_port != 0);
|
||
g_object_unref (address);
|
||
|
||
if (g_socket_speaks_ipv4 (socket6))
|
||
break;
|
||
}
|
||
|
||
g_assert (socket4 == NULL);
|
||
socket4 = g_socket_new (G_SOCKET_FAMILY_IPV4,
|
||
G_SOCKET_TYPE_STREAM,
|
||
G_SOCKET_PROTOCOL_DEFAULT,
|
||
socket6 ? NULL : error);
|
||
|
||
if (socket4 == NULL)
|
||
/* IPv4 not supported.
|
||
* if IPv6 is supported then candidate_port will be non-zero
|
||
* (and the error parameter above will have been NULL)
|
||
* if IPv6 is unsupported then candidate_port will be zero
|
||
* (and error will have been set by the above call)
|
||
*/
|
||
break;
|
||
|
||
inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
|
||
address = g_inet_socket_address_new (inet_address, candidate_port);
|
||
g_object_unref (inet_address);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BINDING, socket4);
|
||
|
||
/* a note on the 'error' clause below:
|
||
*
|
||
* if candidate_port is 0 then we report the error right away
|
||
* since it is strange that this binding would fail at all.
|
||
* otherwise, we ignore the error message (ie: NULL).
|
||
*
|
||
* the exception to this rule is the last time through the loop
|
||
* (ie: attempts == 0) in which case we want to set the error
|
||
* because failure here means that the entire call will fail and
|
||
* we need something to show to the user.
|
||
*
|
||
* an english summary of the situation: "if we gave a candidate
|
||
* port number AND we have more attempts to try, then ignore the
|
||
* error for now".
|
||
*/
|
||
result = g_socket_bind (socket4, address, TRUE,
|
||
(candidate_port && attempts) ? NULL : error);
|
||
g_object_unref (address);
|
||
|
||
if (candidate_port)
|
||
{
|
||
g_assert (socket6 != NULL);
|
||
|
||
if (result)
|
||
/* got our candidate port successfully */
|
||
{
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket4);
|
||
break;
|
||
}
|
||
else
|
||
/* we failed to bind to the specified port. try again. */
|
||
{
|
||
g_object_unref (socket4);
|
||
socket4 = NULL;
|
||
|
||
/* keep this open so we get a different port number */
|
||
sockets_to_close = g_slist_prepend (sockets_to_close,
|
||
socket6);
|
||
candidate_port = 0;
|
||
socket6 = NULL;
|
||
}
|
||
}
|
||
else
|
||
/* we didn't tell it a port. this means two things.
|
||
* - if we failed, then something really bad happened.
|
||
* - if we succeeded, then we need to find out the port number.
|
||
*/
|
||
{
|
||
g_assert (socket6 == NULL);
|
||
|
||
if (!result ||
|
||
!(address = g_socket_get_local_address (socket4, error)))
|
||
{
|
||
g_object_unref (socket4);
|
||
socket4 = NULL;
|
||
break;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_BOUND, socket4);
|
||
|
||
g_assert (G_IS_INET_SOCKET_ADDRESS (address));
|
||
candidate_port =
|
||
g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
|
||
g_assert (candidate_port != 0);
|
||
g_object_unref (address);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* should only be non-zero if we have a socket */
|
||
g_assert ((candidate_port != 0) == (socket4 || socket6));
|
||
|
||
while (sockets_to_close)
|
||
{
|
||
g_object_unref (sockets_to_close->data);
|
||
sockets_to_close = g_slist_delete_link (sockets_to_close,
|
||
sockets_to_close);
|
||
}
|
||
|
||
/* now we actually listen() the sockets and add them to the listener */
|
||
if (socket6 != NULL)
|
||
{
|
||
g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket6);
|
||
|
||
if (!g_socket_listen (socket6, error))
|
||
{
|
||
g_object_unref (socket6);
|
||
if (socket4)
|
||
g_object_unref (socket4);
|
||
|
||
return 0;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket6);
|
||
|
||
if (source_object)
|
||
g_object_set_qdata_full (G_OBJECT (socket6), source_quark,
|
||
g_object_ref (source_object),
|
||
g_object_unref);
|
||
|
||
g_ptr_array_add (listener->priv->sockets, socket6);
|
||
}
|
||
|
||
if (socket4 != NULL)
|
||
{
|
||
g_socket_set_listen_backlog (socket4, listener->priv->listen_backlog);
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENING, socket4);
|
||
|
||
if (!g_socket_listen (socket4, error))
|
||
{
|
||
g_object_unref (socket4);
|
||
if (socket6)
|
||
g_object_unref (socket6);
|
||
|
||
return 0;
|
||
}
|
||
|
||
g_signal_emit (listener, signals[EVENT], 0,
|
||
G_SOCKET_LISTENER_LISTENED, socket4);
|
||
|
||
if (source_object)
|
||
g_object_set_qdata_full (G_OBJECT (socket4), source_quark,
|
||
g_object_ref (source_object),
|
||
g_object_unref);
|
||
|
||
g_ptr_array_add (listener->priv->sockets, socket4);
|
||
}
|
||
|
||
if ((socket4 != NULL || socket6 != NULL) &&
|
||
G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
|
||
G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
|
||
|
||
return candidate_port;
|
||
}
|