Tratamiento de ficheros y canales de entrada/salida

TableOfContents()

Para el tratamiento de ficheros y canales de entrada/salida en general, GLib provee un tipo de dato llamado GIOChannel, que permite encapsular ficheros, sockets y pipes. Esta abstracción no sólo posibilita el acceso uniforme a todos estos recursos, sino que también asegura portabilidad e integración al bucle de eventos.

Actualmente sólo hay soporte completo para UNIX, aunque el tratamiento con ficheros puede hacerse independientemente de la plataforma.

Obtención de un GIOChannel.

Para crear un GIOChannel, primero se debe crear el descriptor de fichero de UNIX por los métodos convencionales (open, socket, etc.) y luego utilizar [#g_io_channel_unix_new g_io_channel_unix_new] o bien [#g_io_channel_new_file g_io_channel_new_file] para acceder a ficheros directamente.

Anchor(g_io_channel_unix_new)

GIOChannel *g_io_channel_unix_new(int fd);

Esta función devuelve el canal creado a partir del descriptor. Dicho parámetro se puede recuperar después utilizando la función [#g_io_channel_unix_get_fd g_io_channel_unix_get_fd].

Anchor(g_io_channel_new_file)

GIOChannel *g_io_channel_new_file(const gchar *nombre_fichero,
                                   const gchar *modo, GError **error);

Devuelve un nuevo canal para acceder al archivo nombre_fichero. El modo tiene la misma forma que el parámetro modo de la función de C estándar fopen. Esto es:

El parámetro error debe apuntar a una estructura tipo GError previamente creada y es aquí donde se informa de cualquier problema al abrir el fichero. En este último caso la función retorna NULL.

Generalidades en el trabajo con GIOChannels.

Una vez obtenido el GIOChannel, en general, todas las llamadas a funciones para realizar alguna operación sobre el canal (lectura, escritura, etc.) tomarán como primer parámetro al propio canal y como último un puntero, error, a una estructura GError, donde se informará de cualquier inconveniente ocurrido al intentar la operación. Además, la mayoría de las funciones retornan un código del tipo GIOStatus que puede tomar los siguientes valores:

En caso de que el código de retorno sea G_IO_STATUS_ERROR, en la estructura GError se almacena otro código, indicando con mayor detalle la naturaleza del problema. Este nuevo código es de tipo GIOChannelError y puede tomar los siguientes valores:

Los primeros valores tienen su correspondencia directa con los valores de la variable errno estándar de C. El último se reserva para otra clase de errores no identificados.

Los canales tienen un tiempo de vida controlado por conteo de referencias. Inicialmente, los canales se crean con una cuenta de 1 y, cuando la misma cae a 0, se destruyen. Para obtener una referencia se utiliza la función [#g_io_channel_ref g_io_channel_ref] y para liberar una referencia [#g_io_channel_unref g_io_channel_unref]. Cabe aclarar que, aunque el objeto GIOChannel se destruya, no necesariamente se cierra el canal. En concreto, si el objeto fue creado a partir de un descriptor de UNIX, el programador es responsable de cerrarlo (a menos que se utilice la función g_io_channel_set_close_on_unref.

Las funciones mencionadas tienen la siguiente forma:

Anchor(g_io_channel_ref)

void g_io_channel_ref(GIOChannel *canal);

Anchor(g_io_channel_unref)

void g_io_channel_unref(GIOChannel *canal);

Para cerrar de un canal se utiliza [#g_io_channel_shutdown g_io_channel_shutdown]:

Anchor(g_io_channel_shutdown)

GIOStatus g_io_channel_shutdown(GIOChannel *canal, gboolean descarga,
                                GError **error);

Aquí, el parámetro descarga controla si se escriben los datos que pueda haber en el buffer interno del canal antes de cerrarlo (para forzar una descarga en cualquier momento se puede utilizar [#g_io_channel_flush g_io_channel_flush].

Operaciones básicas.

GLib provee una serie de funciones para acceder a un canal abierto de diversas maneras: por caracteres individuales, por bloques de cantidad de bytes fijos y por líneas.

Si el tipo de canal lo permite, la función [#g_io_channel_seek_position g_io_channel_seek_position] sirve para establecer la siguiente posición donde se leerá o escribirá. Normalmente sólo los ficheros permiten posicionamiento aleatorio.

Anchor(g_io_channel_seek_position)

GIOStatus g_io_channel_seek_position(GIOChannel *canal, glong posicion,
                                     GSeekType tipo_posicion, GError **error);

La semántica de posición (que es un número con signo) queda determinada por el tipo_posicion, que puede tomar los siguientes valores:

Para efectuar lecturas en un canal se utilizan algunas de las siguientes funciones:

Anchor(g_io_channel_read_chars)

GIOStatus g_io_channel_read_chars(GIOChannel *canal, gchar *buffer,
                                  gsize tamaño_buffer, gsize *bytes_leidos,
                                  GError **error);

Lee una cantidad máxima (tamaño_buffer) de bytes del canal como caracteres y los almacena en buffer. Éste debe estar previamente reservado (p.e. con [#g_malloc g_malloc]). bytes_leidos contiene la cantidad de bytes efectivamente leídos (puede ser menor que la solicitada).

Es importante notar que si el canal está configurado para trabajar con caracteres Unicode, un carácter puede ocupar más de un byte y, si el buffer no es lo suficientemente grande, puede que no sea posible almacenar ningún carácter. En estas condiciones bytes_leidos es cero, pero no hay indicación de error.

Anchor(g_io_channel_read_line)

GIOStatus g_io_channel_read_line(GIOChannel *canal, gchar **puntero_cadena,
                                 gsize *bytes_leidos, gsize *terminador,
                                 GError **error);

Lee una línea completa del canal y almacena el puntero a la misma en puntero_cadena. Una vez finalizada la operación con la línea, es responsabilidad del programador liberar la memoria con [#g_free g_free]. En bytes_leidos se devuelve la cantidad total de bytes leídos (incluyendo caracteres de fin de línea) y en terminador, sólo la cantidad perteneciente a la línea en sí. Tanto bytes_leidos como terminador pueden ser nulos si la información no es de interés.

Anchor(g_io_channel_read_line_string)

GIOStatus g_io_channel_read_line_string(GIOChannel *canal, GString *cadena,
                                        gsize *terminador, GError **error);

Similar a [#g_io_channel_read_line g_io_channel_read_line], sólo que, para devolver el resultado, utiliza una estructura GString. terminador puede ser nulo.

Anchor(g_io_channel_read_to_end)

GIOStatus g_io_channel_read_to_end(GIOChannel *canal, gchar **puntero_cadena,
                                   gsize *bytes_leidos, GError **error);

Lee todos los bytes disponibles en el canal hasta encontrar el fin de fichero. En puntero_cadena se devuelve un puntero al espacio de memoria asignado a tal fin que luego debe ser liberado con [#g_free g_free].

Para la escritura se utiliza la contraparte de [#g_io_channel_read_chars g_io_channel_read_chars]:

Anchor(g_io_channel_write_chars)

GIOStatus g_io_channel_write_chars(GIOChannel *canal, gchar *buffer,
                                   gsize cantidad_bytes, gsize *bytes_escritos,
                                   GError **error);

Como su propio nombre indica, esta función escribe en el canal cantidad_bytes del buffer. Puede que no lleguen a escribirse todos los bytes que se solicitaron, por lo que la función devuelve la cantidad de bytes efectivamente escritos. Si cantidad_bytes es -1 se considera al buffer como terminado en nulo.

Se aplican las mismas restricciones en cuanto a codificación del canal que para [#g_io_channel_read_chars g_io_channel_read_chars].

Integración de canales al bucle de eventos.

Uno de los aspectos más interesantes de los GIOChannels es que es posible integrarlos fácilmente al bucle de eventos y así, por ejemplo, hacer que la aplicación reaccione ante la llegada de datos.

Los posibles tipos de eventos que puede generar un canal están dados por el tipo GIOCondition que toma los siguientes valores:

La forma básica de integrar un objeto generador de eventos al bucle es obtener una estructura GSource. En el caso de los canales, la función para ello es [#g_io_create_watch g_io_create_watch]:

Anchor(g_io_create_watch)

GSource *g_io_create_watch(GIOChannel *canal, GIOCondition condición);

Esta función devuelve un GSource que luego se registra en algún bucle de eventos con [#g_source_attach g_source_attach]. Sin embargo, por conveniencia, GLib provee un par adicional de funciones para integrar directamente un canal al bucle principal: g_io_watch y g_io_add_watch_full.

Anchor(g_io_add_watch)

guint g_io_add_watch(GIOChannel *canal, GIOCondition condición,
                     GIOFunc función, gpointer datos_usuario);

Esta función, que devuelve el identificador de evento, registra el canal para que sea controlado según las condiciones especificadas. En caso de que se cumpla alguna de las condiciones, se llama a la función con los datos_usuario adicionales.

La función especificada en el registro debe tener la siguiente forma:

gboolean (* GIOFunc)(GIOChannel *canal, GIOCondition condición, gpointer datos_usuario);

La condición que la función recibe es aquella que causó el evento en primer lugar. datos_usario es lo que se especificó en el momento del registro.

La otra función, [#g_io_add_watch_full g_io_add_watch_full] permite además especificar la prioridad del canal y una función de notificación cuando el canal se desconecta del bucle de eventos.

Configuración avanzada de canales.

Codificación

Los GIOChannels son capaces de codificar o decodificar los caracteres que se escriben o leen del mismo. La codificación que se utiliza internamente es UTF-8, por lo que, eventualmente, se puede transformar de UTF-8 a alguna otra codificación para escribir a un fichero, o viceversa.

Si se necesita que un canal no efectúe transformación alguna a los datos (por ejemplo, para tratamiento de datos binarios) se debe optar por la codificación nula (NULL).

Las funciones para manipular la codificación son [#g_io_channel_set_encoding g_io_channel_set_encoding] y [#g_io_channel_get_encoding g_io_channel_get_encoding].

Anchor(g_io_channel_set_encoding)

GIOStatus g_io_channel_set_encoding(GIOChannel *canal,
                                    const gchar *codificación, GError **error);

Anchor(g_io_channel_get_encoding)

G_CONST_RETURN gchar *g_io_channel_get_encoding(GIOChannel *canal);

Por defecto, la codificación externa utilizada para ficheros es UTF-8. Para cambiarla se deben tener en cuenta las siguientes condiciones:

Terminador de línea.

En UNIX, el fin de línea se indica con un carácter de linefeed (código ASCII 10), pero esto puede no ser cierto para otras plataformas (p.e. Win32).

Para que las funciones de lectura y escritura por líneas den los resultados esperados para cada plataforma, en caso de ser necesario el acceso a ficheros escritos en otra plataforma, se debe configurar el terminador de línea en el canal. Las funciones para ello son:

Anchor(g_io_channel_set_line_term)

void g_io_channel_set_line_term(GIOChannel *canal, const gchar *terminador,
                                gint longitud);

Anchor(g_io_channel_get_line_term)

G_CONST_RETURN gchar *g_io_channel_get_line_term(GIOChannel *canal,
                                                 gint *longitud);

Configuración de buffer interno.

La API de GIOChannel nos permite modificar alguno de los parámetros de funcionamiento interno del canal. Es posible modificar el tamaño del buffer interno del mismo para optimizar el rendimiento de lectura y escritura en casos especiales. Para ello se utilizan las funciones [#g_io_channel_get_buffer_size g_io_channel_get_buffer_size] y [#g_io_channel_set_buffer_size g_io_channel_set_buffer_size].

Anchor(g_io_channel_get_buffer_size)

gsize g_io_channel_get_buffer_size(GIOChannel *canal);

Anchor(g_io_channel_set_buffer_size)

void g_io_channel_set_buffer_size(GIOChannel *canal, gsize tamaño);

Si en [#g_io_channel_set_buffer_size g_io_channel_set_buffer_size] se especifica un tamaño 0, GLib elegirá un tamaño adecuado al canal.

Hemos visto antes que, utilizando el bucle de eventos, se puede monitorear un canal y producir llamadas a funciones ante determinados eventos. También es posible verificar manualmente si un canal tiene datos listos para la lectura o si se pueden escribir datos en él. Para ello se utiliza la función [#g_io_channel_get_buffer_condition g_io_channel_get_buffer_condition].

Anchor(g_io_channel_get_buffer_condition)

GIOCondition g_io_channel_get_buffer_condition(GIOChannel *canal);

Esta función puede devolver solamente G_IO_IN y G_IO_OUT y, por supuesto, su significado es exactamente el mismo que se explicó antes.

Por último, con las funciones [#g_io_channel_set_flags g_io_channel_set_flags] y [#g_io_channel_get_flags g_io_channel_get_flags], es posible saber y, en parte, modificar el comportamiento que tiene un canal.

Anchor(g_io_channel_get_flags)

GIOFlags g_io_channel_get_flags(GIOChannel *canal);

Anchor(g_io_channel_set_flags)

GIOStatus g_io_channel_set_flags(GIOChannel *canal, GIOFlags banderas, GError **error);

Los valores que puede tomar el parámetro banderas son G_IO_FLAG_APPEND y/o G_IO_FLAG_NONBLOCK (los demás valores son de sólo lectura). El valor obtenido en [#g_io_channel_get_flags g_io_channel_get_flags] es una composición de:


Volver a: [:Documentacion/Desarrollo/Glib:GLib]