glib/gio/gtcpconnection.c
2010-11-03 14:25:35 +01:00

368 lines
9.9 KiB
C

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright © 2008, 2009 Codethink Limited
*
* This program 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 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*/
/**
* SECTION: gtcpconnection
* @title: GTcpConnection
* @short_description: a TCP GSocketConnection
* @see_also: #GSocketConnection.
*
* This is the subclass of #GSocketConnection that is created
* for TCP/IP sockets.
*
* Since: 2.22
*/
#include "config.h"
#include "gtcpconnection.h"
#include "gasyncresult.h"
#include "gsimpleasyncresult.h"
#include "giostream.h"
#include "glibintl.h"
G_DEFINE_TYPE_WITH_CODE (GTcpConnection, g_tcp_connection,
G_TYPE_SOCKET_CONNECTION,
g_socket_connection_factory_register_type (g_define_type_id,
G_SOCKET_FAMILY_IPV4,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT);
g_socket_connection_factory_register_type (g_define_type_id,
G_SOCKET_FAMILY_IPV6,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT);
g_socket_connection_factory_register_type (g_define_type_id,
G_SOCKET_FAMILY_IPV4,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_TCP);
g_socket_connection_factory_register_type (g_define_type_id,
G_SOCKET_FAMILY_IPV6,
G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_TCP);
);
static gboolean g_tcp_connection_close (GIOStream *stream,
GCancellable *cancellable,
GError **error);
static void g_tcp_connection_close_async (GIOStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
struct _GTcpConnectionPrivate
{
guint graceful_disconnect : 1;
};
enum
{
PROP_0,
PROP_GRACEFUL_DISCONNECT
};
static void
g_tcp_connection_init (GTcpConnection *connection)
{
connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection,
G_TYPE_TCP_CONNECTION,
GTcpConnectionPrivate);
connection->priv->graceful_disconnect = FALSE;
}
static void
g_tcp_connection_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GTcpConnection *connection = G_TCP_CONNECTION (object);
switch (prop_id)
{
case PROP_GRACEFUL_DISCONNECT:
g_value_set_boolean (value, connection->priv->graceful_disconnect);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_tcp_connection_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GTcpConnection *connection = G_TCP_CONNECTION (object);
switch (prop_id)
{
case PROP_GRACEFUL_DISCONNECT:
g_tcp_connection_set_graceful_disconnect (connection,
g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
g_tcp_connection_class_init (GTcpConnectionClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GIOStreamClass *stream_class = G_IO_STREAM_CLASS (class);
g_type_class_add_private (class, sizeof (GTcpConnectionPrivate));
gobject_class->set_property = g_tcp_connection_set_property;
gobject_class->get_property = g_tcp_connection_get_property;
stream_class->close_fn = g_tcp_connection_close;
stream_class->close_async = g_tcp_connection_close_async;
g_object_class_install_property (gobject_class, PROP_GRACEFUL_DISCONNECT,
g_param_spec_boolean ("graceful-disconnect",
P_("Graceful Disconnect"),
P_("Whether or not close does a graceful disconnect"),
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
static gboolean
g_tcp_connection_close (GIOStream *stream,
GCancellable *cancellable,
GError **error)
{
GTcpConnection *connection = G_TCP_CONNECTION (stream);
GSocket *socket;
char buffer[1024];
gssize ret;
GError *my_error;
gboolean had_error;
socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
had_error = FALSE;
if (connection->priv->graceful_disconnect &&
!g_cancellable_is_cancelled (cancellable) /* Cancelled -> close fast */)
{
if (!g_socket_shutdown (socket, FALSE, TRUE, error))
{
error = NULL; /* Ignore further errors */
had_error = TRUE;
}
else
{
while (TRUE)
{
my_error = NULL;
ret = g_socket_receive (socket, buffer, sizeof (buffer),
cancellable, &my_error);
if (ret < 0)
{
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
g_error_free (my_error);
else
{
had_error = TRUE;
g_propagate_error (error, my_error);
error = NULL;
break;
}
}
if (ret == 0)
break;
}
}
}
return G_IO_STREAM_CLASS (g_tcp_connection_parent_class)
->close_fn (stream, cancellable, error) && !had_error;
}
typedef struct {
GSimpleAsyncResult *res;
GCancellable *cancellable;
} CloseAsyncData;
static void
close_async_data_free (CloseAsyncData *data)
{
g_object_unref (data->res);
if (data->cancellable)
g_object_unref (data->cancellable);
g_free (data);
}
static void
async_close_finish (CloseAsyncData *data,
GError *error /* consumed */,
gboolean in_mainloop)
{
GIOStreamClass *parent = G_IO_STREAM_CLASS (g_tcp_connection_parent_class);
GIOStream *stream;
GError *my_error;
stream = G_IO_STREAM (g_async_result_get_source_object (G_ASYNC_RESULT (data->res)));
/* Doesn't block, ignore error */
if (error)
{
parent->close_fn (stream, data->cancellable, NULL);
g_simple_async_result_take_error (data->res, error);
}
else
{
my_error = NULL;
parent->close_fn (stream, data->cancellable, &my_error);
if (my_error)
g_simple_async_result_take_error (data->res, my_error);
}
if (in_mainloop)
g_simple_async_result_complete (data->res);
else
g_simple_async_result_complete_in_idle (data->res);
}
static gboolean
close_read_ready (GSocket *socket,
GIOCondition condition,
CloseAsyncData *data)
{
GError *error = NULL;
char buffer[1024];
gssize ret;
ret = g_socket_receive (socket, buffer, sizeof (buffer),
data->cancellable, &error);
if (ret < 0)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
g_error_free (error);
else
{
async_close_finish (data, error, TRUE);
return FALSE;
}
}
if (ret == 0)
{
async_close_finish (data, NULL, TRUE);
return FALSE;
}
return TRUE;
}
static void
g_tcp_connection_close_async (GIOStream *stream,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTcpConnection *connection = G_TCP_CONNECTION (stream);
CloseAsyncData *data;
GSocket *socket;
GSource *source;
GError *error;
if (connection->priv->graceful_disconnect &&
!g_cancellable_is_cancelled (cancellable) /* Cancelled -> close fast */)
{
data = g_new (CloseAsyncData, 1);
data->res =
g_simple_async_result_new (G_OBJECT (stream), callback, user_data,
g_tcp_connection_close_async);
if (cancellable)
data->cancellable = g_object_ref (cancellable);
else
data->cancellable = NULL;
socket = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
error = NULL;
if (!g_socket_shutdown (socket, FALSE, TRUE, &error))
{
async_close_finish (data, error, FALSE);
close_async_data_free (data);
return;
}
source = g_socket_create_source (socket, G_IO_IN, cancellable);
g_source_set_callback (source,
(GSourceFunc) close_read_ready,
data, (GDestroyNotify)close_async_data_free);
g_source_attach (source, g_main_context_get_thread_default ());
g_source_unref (source);
return;
}
G_IO_STREAM_CLASS (g_tcp_connection_parent_class)
->close_async (stream, io_priority, cancellable, callback, user_data);
}
/**
* g_tcp_connection_set_graceful_disconnect:
* @connection: a #GTcpConnection
* @graceful_disconnect: Whether to do graceful disconnects or not
*
* This enabled graceful disconnects on close. A graceful disconnect
* means that we signal the recieving end that the connection is terminated
* and wait for it to close the connection before closing the connection.
*
* A graceful disconnect means that we can be sure that we successfully sent
* all the outstanding data to the other end, or get an error reported.
* However, it also means we have to wait for all the data to reach the
* other side and for it to acknowledge this by closing the socket, which may
* take a while. For this reason it is disabled by default.
*
* Since: 2.22
*/
void
g_tcp_connection_set_graceful_disconnect (GTcpConnection *connection,
gboolean graceful_disconnect)
{
graceful_disconnect = !!graceful_disconnect;
if (graceful_disconnect != connection->priv->graceful_disconnect)
{
connection->priv->graceful_disconnect = graceful_disconnect;
g_object_notify (G_OBJECT (connection), "graceful-disconnect");
}
}
/**
* g_tcp_connection_get_graceful_disconnect:
* @connection: a #GTcpConnection
*
* Checks if graceful disconnects are used. See
* g_tcp_connection_set_graceful_disconnect().
*
* Returns: %TRUE if graceful disconnect is used on close, %FALSE otherwise
*
* Since: 2.22
*/
gboolean
g_tcp_connection_get_graceful_disconnect (GTcpConnection *connection)
{
return connection->priv->graceful_disconnect;
}