| /* 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 receiving 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; |
| } |