X-Git-Url: http://git.cascardo.info/?a=blobdiff_plain;f=gnio%2Fgsocket.c;h=1d54f1c97babc33ed4200c678ed9fe4219d80205;hb=16c893bba43ba9f720ad26250ba8bbc8fd9ba96c;hp=c0a8a1aa091c8be0a4107064735da956db7e3848;hpb=11ee7b45fdb2583ccd2e30305e71175c20a0fdb1;p=cascardo%2Fgnio.git diff --git a/gnio/gsocket.c b/gnio/gsocket.c index c0a8a1a..1d54f1c 100644 --- a/gnio/gsocket.c +++ b/gnio/gsocket.c @@ -24,18 +24,18 @@ #include #include #include +#include #include #ifndef G_OS_WIN32 # include # include # include +# include +# include +# include #else -# include -# include -# include -# undef HAVE_GETADDRINFO -# define HAVE_GETHOSTBYNAME_THREADSAFE 1 + #endif #include @@ -48,35 +48,538 @@ G_DEFINE_TYPE (GSocket, g_socket, G_TYPE_OBJECT); +enum +{ + PROP_0, + PROP_FD, + PROP_BLOCKING, + PROP_BACKLOG, + PROP_REUSE_ADDRESS, + PROP_LOCAL_ADDRESS, + PROP_REMOTE_ADDRESS +}; + struct _GSocketPrivate { - int fd; + gint fd; + gboolean blocking; + gint backlog; + gboolean reuse_address; + GSocketAddress *local_address; + GSocketAddress *remote_address; }; +static void +g_socket_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GSocket *socket = G_SOCKET (object); + + switch (prop_id) + { + case PROP_FD: + g_value_set_int (value, socket->priv->fd); + break; + + case PROP_BLOCKING: + g_value_set_boolean (value, socket->priv->blocking); + break; + + case PROP_BACKLOG: + g_value_set_int (value, socket->priv->backlog); + break; + + case PROP_REUSE_ADDRESS: + g_value_set_boolean (value, socket->priv->reuse_address); + break; + + case PROP_LOCAL_ADDRESS: + g_value_set_object (value, socket->priv->local_address); + break; + + case PROP_REMOTE_ADDRESS: + g_value_set_object (value, socket->priv->remote_address); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_socket_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GSocket *socket = G_SOCKET (object); + + switch (prop_id) + { + case PROP_FD: + socket->priv->fd = g_value_get_int (value); + break; + + case PROP_BLOCKING: + g_socket_set_blocking (socket, g_value_get_boolean (value)); + break; + + case PROP_BACKLOG: + socket->priv->backlog = g_value_get_int (value); + break; + + case PROP_REUSE_ADDRESS: + g_socket_set_reuse_address (socket, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +g_socket_finalize (GObject *object) +{ + GSocket *socket = G_SOCKET (object); + + if (socket->priv->local_address) + g_object_unref (socket->priv->local_address); + + if (socket->priv->remote_address) + g_object_unref (socket->priv->remote_address); + + if (G_OBJECT_CLASS (g_socket_parent_class)->finalize) + (*G_OBJECT_CLASS (g_socket_parent_class)->finalize) (object); +} + +static void +g_socket_dispose (GObject *object) +{ + GSocket *socket = G_SOCKET (object); + + g_socket_close (socket); + + if (G_OBJECT_CLASS (g_socket_parent_class)->dispose) + (*G_OBJECT_CLASS (g_socket_parent_class)->dispose) (object); +} + static void g_socket_class_init (GSocketClass *klass) { GObjectClass *gobject_class G_GNUC_UNUSED = G_OBJECT_CLASS (klass); + // TODO: WSAStartup + g_type_class_add_private (klass, sizeof (GSocketPrivate)); + + gobject_class->finalize = g_socket_finalize; + gobject_class->dispose = g_socket_dispose; + gobject_class->set_property = g_socket_set_property; + gobject_class->get_property = g_socket_get_property; + + g_object_class_install_property (gobject_class, PROP_FD, + g_param_spec_int ("fd", + "file descriptor", + "the socket's file descriptor", + G_MININT, + G_MAXINT, + -1, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + + g_object_class_install_property (gobject_class, PROP_BLOCKING, + g_param_spec_boolean ("blocking", + "blocking", + "whether or not this socket is blocking", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + + g_object_class_install_property (gobject_class, PROP_BACKLOG, + g_param_spec_int ("backlog", + "listen backlog", + "outstanding connections in the listen queue", + 0, + SOMAXCONN, + 10, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + + g_object_class_install_property (gobject_class, PROP_REUSE_ADDRESS, + g_param_spec_boolean ("reuse-address", + "reuse address", + "allow reuse of local addresses when binding", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + + g_object_class_install_property (gobject_class, PROP_LOCAL_ADDRESS, + g_param_spec_object ("local-address", + "local address", + "the local address the socket is bound to", + G_TYPE_SOCKET_ADDRESS, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); + + g_object_class_install_property (gobject_class, PROP_REMOTE_ADDRESS, + g_param_spec_object ("remote-address", + "remote address", + "the remote address the socket is connected to", + G_TYPE_SOCKET_ADDRESS, + G_PARAM_READABLE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NICK)); } static void -g_socket_init (GSocket *address) +g_socket_init (GSocket *socket) +{ + socket->priv = G_TYPE_INSTANCE_GET_PRIVATE (socket, G_TYPE_SOCKET, GSocketPrivate); + + socket->priv->fd = -1; + socket->priv->blocking = TRUE; + socket->priv->backlog = 10; + socket->priv->remote_address = NULL; + socket->priv->local_address = NULL; +} + +GSocket * +g_socket_new (GSocketDomain domain, GSocketType type, const gchar *protocol, GError **error) +{ + static GStaticMutex getprotobyname_mutex = G_STATIC_MUTEX_INIT; + gint fd, native_domain, native_type, native_protocol; + + switch (domain) + { + case G_SOCKET_DOMAIN_INET: + native_domain = PF_INET; + break; + + case G_SOCKET_DOMAIN_INET6: + native_domain = PF_INET6; + break; + + case G_SOCKET_DOMAIN_UNIX: + native_domain = PF_UNIX; + break; + + default: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported socket domain"); + return NULL; + } + + switch (type) + { + case G_SOCKET_TYPE_STREAM: + native_type = SOCK_STREAM; + break; + + case G_SOCKET_TYPE_DATAGRAM: + native_type = SOCK_DGRAM; + break; + + case G_SOCKET_TYPE_SEQPACKET: + native_type = SOCK_SEQPACKET; + break; + + default: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported socket type"); + return NULL; + } + + if (protocol == NULL) + native_protocol = 0; + else + { + struct protoent *ent; + g_static_mutex_lock (&getprotobyname_mutex); + if (!(ent = getprotobyname (protocol))) + { + g_static_mutex_unlock (&getprotobyname_mutex); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "unsupported socket protocol"); + return NULL; + } + native_protocol = ent->p_proto; + g_static_mutex_unlock (&getprotobyname_mutex); + } + + fd = socket(native_domain, native_type, native_protocol); + + if (fd < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "unable to create socket: %s", g_strerror (errno)); + return NULL; + } + + return G_SOCKET (g_object_new (G_TYPE_SOCKET, "fd", fd, "blocking", TRUE, NULL)); +} + +GSocket * +g_socket_new_from_fd (gint fd) +{ + glong arg; + gboolean blocking; + + if ((arg = fcntl (fd, F_GETFL, NULL)) < 0) + g_warning ("Error getting socket status flags: %s", g_strerror (errno)); + + blocking = ((arg & O_NONBLOCK) == 0); + + return G_SOCKET (g_object_new (G_TYPE_SOCKET, "fd", fd, "blocking", blocking, NULL)); +} + +void +g_socket_set_blocking (GSocket *socket, + gboolean blocking) +{ + glong arg; + + g_return_if_fail (G_IS_SOCKET (socket)); + + if ((arg = fcntl (socket->priv->fd, F_GETFL, NULL)) < 0) + g_warning ("Error getting socket status flags: %s", g_strerror (errno)); + + arg = blocking ? arg & ~O_NONBLOCK : arg | O_NONBLOCK; + + if (fcntl (socket->priv->fd, F_SETFL, arg) < 0) + g_warning ("Error setting socket status flags: %s", g_strerror (errno)); + + socket->priv->blocking = blocking; + + g_object_notify (G_OBJECT (socket), "blocking"); +} + +gboolean +g_socket_get_blocking (GSocket *socket) +{ + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + + return socket->priv->blocking; +} + +void +g_socket_set_reuse_address (GSocket *socket, + gboolean reuse) +{ + gint value = (gint) reuse; + + g_return_if_fail (G_IS_SOCKET (socket)); + + if (setsockopt (socket->priv->fd, SOL_SOCKET, SO_REUSEADDR, (gpointer) &value, sizeof (value)) < 0) + g_warning ("error setting reuse address: %s", g_strerror (errno)); + + socket->priv->reuse_address = reuse; + + g_object_notify (G_OBJECT (socket), "reuse-address"); +} + +gboolean +g_socket_get_reuse_address (GSocket *socket) +{ + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + + return socket->priv->reuse_address; +} + +gboolean +g_socket_has_socket_error (GSocket *socket, + GError **error) +{ + gint sockerr; + gsize sockerr_size = sizeof (sockerr); + + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + + if (getsockopt (socket->priv->fd, SOL_SOCKET, SO_ERROR, (gpointer) &sockerr, &sockerr_size) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not get socket error: %s", g_strerror (errno)); + return TRUE; + } + + if (sockerr != 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (sockerr), "error connecting: %s", g_strerror (sockerr)); + return TRUE; + } + + return FALSE; +} + +GSocketAddress * +g_socket_get_local_address (GSocket *socket, + GError **error) +{ + gchar buffer[256]; + gsize len = 256; + + g_return_val_if_fail (G_IS_SOCKET (socket), NULL); + + if (socket->priv->local_address) + return socket->priv->local_address; + + if (getsockname (socket->priv->fd, (struct sockaddr *) buffer, &len) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not get local address: %s", g_strerror (errno)); + return NULL; + } + + return (socket->priv->local_address = g_object_ref_sink (g_socket_address_from_native (buffer, len))); +} + +GSocketAddress * +g_socket_get_remote_address (GSocket *socket, + GError **error) +{ + gchar buffer[256]; + gsize len = 256; + + g_return_val_if_fail (G_IS_SOCKET (socket), NULL); + + if (socket->priv->remote_address) + return socket->priv->remote_address; + + if (getpeername (socket->priv->fd, (struct sockaddr *) buffer, &len) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not get remote address: %s", g_strerror (errno)); + return NULL; + } + + return (socket->priv->remote_address = g_object_ref_sink (g_socket_address_from_native (buffer, len))); +} + +gboolean +g_socket_listen (GSocket *socket, + GError **error) +{ + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + + if (listen (socket->priv->fd, socket->priv->backlog) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "could not listen: %s", g_strerror (errno)); + return FALSE; + } + + return TRUE; +} + +gboolean +g_socket_bind (GSocket *socket, + GSocketAddress *address, + GError **error) { - address->priv = G_TYPE_INSTANCE_GET_PRIVATE (address, G_TYPE_SOCKET, GSocketPrivate); + g_return_val_if_fail (G_IS_SOCKET (socket) && G_IS_SOCKET_ADDRESS (address), FALSE); + + { + gchar addr[256]; + + if (!g_socket_address_to_native (address, addr)) + return FALSE; + + if (bind (socket->priv->fd, (struct sockaddr *) addr, g_socket_address_native_size (address)) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "error binding to address: %s", g_strerror (errno)); + return FALSE; + } + + g_object_ref_sink (address); + + socket->priv->local_address = address; + + return TRUE; + } } GSocket * -g_socket_new () +g_socket_accept (GSocket *socket, + GError **error) +{ + gint ret; + + if ((ret = accept (socket->priv->fd, NULL, 0)) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "error accepting connection: %s", g_strerror (errno)); + return NULL; + } + + return g_socket_new_from_fd (ret); +} + +gboolean +g_socket_connect (GSocket *socket, + GSocketAddress *address, + GError **error) +{ + gchar buffer[256]; + + g_return_val_if_fail (G_IS_SOCKET (socket) && G_IS_SOCKET_ADDRESS (address), FALSE); + + g_socket_address_to_native (address, buffer); + + if (connect (socket->priv->fd, (struct sockaddr *) buffer, g_socket_address_native_size (address)) < 0) + { + if (errno == EINPROGRESS) + g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING, "connection in progress"); + else + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "error connecting: %s", g_strerror (errno)); + return FALSE; + } + + socket->priv->remote_address = g_object_ref_sink (address); + + return TRUE; +} + +gssize +g_socket_receive (GSocket *socket, + gchar *buffer, + gsize size, + GError **error) +{ + gssize ret; + + g_return_val_if_fail (G_IS_SOCKET (socket) && buffer != NULL, FALSE); + + if ((ret = recv (socket->priv->fd, buffer, size, 0)) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "error receiving data: %s", g_strerror (errno)); + return -1; + } + + return ret; +} + +gssize +g_socket_send (GSocket *socket, + const gchar *buffer, + gsize size, + GError **error) { - return G_SOCKET (g_object_new (G_TYPE_SOCKET, NULL)); + gssize ret; + + g_return_val_if_fail (G_IS_SOCKET (socket) && buffer != NULL, FALSE); + + if ((ret = send (socket->priv->fd, buffer, size, 0)) < 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "error sending data: %s", g_strerror (errno)); + return -1; + } + + return ret; } void -g_socket_listen (GSocket *socket, gint backlog) +g_socket_close (GSocket *socket) { g_return_if_fail (G_IS_SOCKET (socket)); - listen (socket->priv->fd, backlog); +#ifdef G_OS_WIN32 + closesocket (socket->priv->fd); +#else + close (socket->priv->fd); +#endif +} + +GSource * +g_socket_create_source (GSocket *socket, + GIOCondition condition, + GCancellable *cancellable) +{ + g_return_val_if_fail (G_IS_SOCKET (socket) && (cancellable == NULL || G_IS_CANCELLABLE (cancellable)), NULL); + + return _g_fd_source_new (socket->priv->fd, G_IO_IN | G_IO_HUP | G_IO_ERR, cancellable); }