Conexión a Redes
Versión: 1.0, Septiembre, 2003

Autor: Luis Javier Herrera Maldonado
Mail: jherrera@atc.ugr.es

IndiceInicioPerl

(C) Dpto. de CCIA
Web: http://decsai.ugr.es

1.- Introducción

1.1.- El GCF (Generic Connection Framework) de CLDC

    Debido a las dificultades para proveer soporte para funciones de red a nivel de configuración, por la variedad en los dispositivos, el CLDC delega esta parte del API a los perfiles. Para realizar esta delegación de forma satisfactoria, el CLDC ofrece un marco general de trabajo en red, conocido como el GCF (Generic Connection Framework). El GCF está compuesto básicamente por una serie de interfaces de conexión, junto con una clase "Conector" que es usada para establecer las diferentes conexiones. Todo esto, está dentro del paquete javax.microedition.io.

Las interfaces del paquete javax.microedition.io son:

o Connection – Una conexión básica que sólo puede ser abierta y cerrada

o ContentConnection – Un flujo (stream) de conexión que proporciona acceso a datos web

o DatagramConnection – Una conexión para manejar comunicaciones orientadas a paquetes

o InputConnection – Una conexión de entrada para las comunicaciones del dispositivo

o OutputConnection – Una conexión de salida para las comunicaciones del dispositivo

o StreamConnection – Una conexión en ambas direcciones para las comunicaciones del dispositivo

o StreamConnectionNotifier – Una conexión especial para notificaciones, que es usada para esperar que se establezca una conexión

 

1.2.- El paquete javax.microedition.io

El CLDC, descarga el trabajo con la red y la entrada/salida en al paquete java.io y en el Generic Connection Framework (GCF). El API de MIDP parte de esta base, añadiendo la interfaz HttpConnection, que pertenece al paquete javax.microedition.io.

 

1.3.- Conexión a Redes con MIDP

1.3.1.- Interfaz Connection

    Dada la diversidad de dispositivos y de su posible funcionalidad en conexiones de red, la especificación MIDP indica que las conexiones HTTP son el único tipo de conexiones obligatorio en las implementaciones de MIDP. Aun así veremos por encima los tres tipos de conexión posible con sus ventajas e inconvenientes, centrándonos en la conexión HTTP.

La clase fundamental para la conexión a redes es la clase "Connector". Siempre que queramos establecer una conexión, sea del tipo que sea, realizaremos una llamada al método estático open() de esta clase que nos devolverá un objeto que implementa la interfaz "Connection". El método open() tiene la siguiente sintaxis:

public static Connection open (String name)
public static Connection open (String name, int mode)
public static Connection open (String name, int mode, boolean timeouts)

donde:

    - "name" es la cadena de conexión que tiene el siguiente formato:

            protocolo: dirección; parámetros

  • protocolo indica si vamos a realizar una conexión de tipo http, socket o datagram por ejemplo
  • dirección indica la direción de red del destino
  • parámetros es una lista de parámetros asociados a la conexión.

    - "mode" indica si vamos a realizar una conexión para lectura (Connector.READ), para escritura (Connector.WRITE) o para lectura y escritura (Connector.READ_WRITE).

    - "timeouts" indica si el dispositivo o protocolo de destino admite timeouts. El objetivo de este parámetro es que la conexión no se quede permanentemente esperando. (Fijémonos que no hay forma de indicar el tiempo máximo de espera).

 

1.3.2.- Interfaces que amplian la interfaz genérica Connection:

    "InputConnection" y "OutputConnection" nos añaden la posibilidad de crear flujos de entrada y salida para acceder a los datos que se envían a través de la conexión. Los métodos que nos proporcionan estas dos interfaces, nos permitirán de hecho poder enviar/recibir los datos que necesitemos por la red.

    Para "InputConnection"

public InputStream openInputStream()
public DataInputStream openDataInputStream()

    Para "OutputConnection"

public OutputStream openOutputStream()
public DataOutputStream openDataOutputStream()

    La clase "InputStream" ("OutputStream") tiene métodos para recibir (enviar) datos, bien sea byte por byte o enviando un buffer completo, así como un método para cerrar la conexión (fundamental en términos de velocidad del dispositivo). Además la clase "InputStream" tiene un método para ver cuantos bytes están disponibles en espera en el flujo de entrada para controlar adecuadamente la lectura de datos.

    Una superinterfaz de estas dos interfaces vistas es "StreamConnection" que contiene los métodos de ambas "InputConnectio" y "OutputConnection" para una comunicación bidireccional (por ejemplo en el uso de sockets). Tenemos la posibilidad por tanto de realizar un intercambio de datos desde el emisor al receptor, pero dejamos la interpretación de estos datos a los comunicantes.

    Una cuarta interfaz que podemos utilizar caso de que estemos transmitiendo información más estructurada es "ContentConnection" que añade los métodos:

        public long getLength(): longitud del siguiente mensaje en el flujo de entrada

        public String getType(): distingue entre tipos de datos en el flujo de entrada

        public String getEncoding(): permite el uso de diferentes esquemas para la codificación de caracteres de 16bits de Unicode en un flujo de 8bits de datos.

    Por último la interfaz "HttpConnection" extiende esta última con métodos necesarios para la transmisión de información utilizando este protocolo.

    [Otras dos interfaces son "DatagramConnection" para la transmisión de datagramas y "StreamConnectionNotifier" que se utiliza para implementar un servidor utilizando sockets].

 

2.- Comunicación por Sockets

    Los sockets son en la práctica el nivel más bajo de comunicación que se puede utilizar en la programación. Son fáciles de utilizar y resultan útiles para aplicaciones distribuidas entre uno o más clientes y un solo servidor, intercambiando información utilizando un protocolo de muy bajo nivel. De hecho son la base para protocolos de más alto nivel como TCP/IP.

    CLDC ni MIDP contemplan los sockets en sus requerimientos. El acceso a la red en los dispositivos móviles está muy limitado y a veces no llega siquiera a existir. En un futuro es muy posible que se llegue a tener una integración completa de los sockets en estos protocolos, aunque a mi parecer la tendencia d e estas APIs, será la de incorporar protocolos de más alto nivel, más seguros y con mayor control sobre el tipo y cantidad de datos transmitidos, en vez de éstos más inseguros y de bajo nivel.

 

2.1.- Cliente en la comunicación por sockets

    Los pasos en la comunicación que realizaría un cliente serían los siguientes:   

        1.- Construir la cadena de conexión y llamar al método open() de la clase "Connector" para abrir la conexión.

        2.- Obtener un flujo de salida y enviar el mensaje de petición al servidor.

        3.- Abrir un flujo de entrada y leer la respuesta.

        4.- Cerrar ambos flujos, entrada y salida (recursos limitados!).

    Ver el ejemplo SocketMIDlet.java.

 

2.2.- Servidores en la comunicación por sockets

    Los pasos en la comunicación que realizaría un servidor serían los siguientes:   

        1.- Construir la cadena de conexión, que en este caso contiene simplemente el puerto por el que vamos a escuchar las conexiones: 

             * socket://:80

        2.- Al llamar al método open(), obtenemos un objeto del tipo "StreamConnectionNotifier". Esta interfaz implementa el método

public StreamConnection acceptAndOpen(). 

        Por tanto el servidor entra en un bucle que llama a este método. Dicho método continua la ejecución cuando un cliente se conecta y devuelve un "StreamConnection" con el que nos podremos comunicar con el cliente de la misma forma que explicamos antes.

    Puesto que los servidores normalmente responden a llamadas de varios clientes, éstos implementan una hebra una vez que se ha aceptado una conexión con un cliente de manera que se puedan seguir escuchando conexiones mientras se atiende a cada cliente que lo ha solicitado.

 

3.- Comunicación por Datagramas

    La comunicación por datagramas es una comunicación de paquetes con las siguientes características:

    - No hay ningún tipo de conexión permanente. Los datos enviados en un paquete nada tienen que ver con los enviados en el siguiente paquete, que tiene que volver a ser enviado de manera completamente independiente.

    - La comunicación no es uno a uno como con los sockets. Un mismo emisor puede enviar varios paquetes a varios destinatarios -según se indique en la dirección de los mimos- y un receptor puede recibir varios paquetes de varios emisores.

    - No hay confianza en el envío. Un paquete puede no llegar o puede llegar duplicado, e incluso varios paquetes enviados pueden llegar en un orden distinto del que se enviaron.

    Tanto para el receptor como para el emisor, se utiliza la interfaz "DatagramConnection". El receptor indica en el método open() el puerto de entrada y el emisor indica también la dirección del receptor.

    Para el envío de paquetes tenemos la clase "Datagram" que contendrá los datos que serán enviados con el método send(). Un datagrama concreto puede tener especificado internamente la dirección a la que se enviará, sobreescribiendo por tanto a la dirección especificada en el método open() de la clase "Connector" cuando se utilice el método send() de la clase DatagramConnection().

    Un servidor abrirá su "DatagramConnection", creará un buffer para almacenar el datagrama a recibir y esperará mediante el método receive() a que llegue el datagrama para ser procesado.

    Notar que el mismo "DatagramConnection" puede ser utilizado para recibir y enviar datos.

 

4.- Comunicación por HTTP

4.1.- Introducción

    El protocolo HTTP utiliza comunicación mediante sockets para llevar los mensajes de un cliente (navegador de internet) a un servidor y viceversa, enviando este último como respuesta a la petición del cliente una página web. Esto funciona bien para los computadores normales, pero nos podemos encontrar con problemas a la hora de trabajar con los dispositivos móviles:

        - Muchos dispositivos de este tipo no tienen conexión directa a internet y por tanto no pueden trabajar con sockets.

        - La interfaz de usuario de MIDP no proporciona ningún soporte directo para la visualización de código HTML, con lo que no disponemos de la capacidad de proporcionar navegación por internet directamente.

   1.-  Es responsabilidad del proveedor de los dispositivos móviles el dar la capacidad a estos de soportar HTML, incluso pese a no tener conexión directa con Internet. En estos casos, el dispositivo puede por ejemplo utilizar WSP (Wireless Session Protocol) para conectarse a una puerta de enlace WAP que se encargue de conectar la red inalámbrica con Internet. Se haga como se haga la conexión, hay que hacerla de tal modo que la aplicación MIDP desconozca el modo en que se ha conectado a Internet.

    2.- El hecho de que MIDP no soporte directamente el código HTML no es tal desventaja. Sencillamente para aplicaciones privadas, basta con aprovechar la funcionalidad de transmisión por HTTP de MIDP para enviar información privada de la aplicación, sin necesidad de que esté codificada con HTML. Incluso caso de que la información obtenida esté en HTML, el dispositivo sencillamente podría buscar y extraer l información que necesita en dicho código HTML y mostrarla al usuario como si fuera cualquier otro tipo de información.

4.2.- Utilización del la conexión HTTP

    Como vimos anteriormente para realizar cualquier tipo de conexión, primeramente tenemos que llamar al método open() de la clase "Connector", que como dijimos anteriormente, para el caso de HTTP, nos devolverá un objeto de tipo "HttpConnection". Este objeto tipo de objeto viene derivado de "ContentConnection" con lo que podremos usar flujos de entrada y salida de datos, además de que los datos devueltos por el servidor tendrán una longitud, tipo y codificación que podemos determinar.

    Recordemos también que con HTTP no solo podemos enviar datos en formato HTML sino que también podemos enviar datos en XML, imágenes o incluso datos en binario.

    El parámetro "nombre" tendrá la siguiente sintaxis en general:

http://host:puerto/path?parametros#referencia

        - El puerto puede ser omitido siendo por defecto el puerto 80 el que se toma.

        - Los parámetros se refiere a los servidores web de contenido dinámico basados en los parámetros proporcionados por el cliente. Estos parámetros se ofrecen, por ejemplo añadiéndolos a la URL como una serie de "nombre = valor" separados por el signo &.  Otra forma de enviar los parámetros es en el mismo contenido del mensaje.

        - En "referencia" podemos indicar una marca <A> dentro del código HTML que queremos. Así podremos visualizar directamente una parte en concreto de la página web que queremos recuperar.

    Repasemos según lo estudiado ahora como sería el ciclo de utilización de una conexión HTTP:

  • Construir la URL de la página web a la que vamos a acceder y llamar al método open() de la clase "Connector" para obtener la instancia de "HttpConnection" con la que vamos a conectar.   
  • Envio de datos al servidor (solo cuando sea necesario)
    • Construir la petición al servidor.
    • Construir las cabeceras de las peticiones que se necesiten.
    • Llamar al método openOutputStream() o openDataOutputStream() para obtener un flujo en el que escribir cualquier dato que se necesite enviar.
    • Enviar los datos de petición al servidor
  • Llamar al método openInputStream() o openDataInputStream() para obtener un flujo de entrada desde el que leer la respuesta del servidor.
  • Obtener los datos de respuesta del servidor llamando al método getResponseCode(). Comprobar si la petición tuvo éxito y leer los datos devueltos desde el flujo de entrada.
  • Finalmente cerrar los flujos de entrada y salida y la conexión.

  EJERCICIO: Crear un MIDlet que conecte con una página dada y lea los 128 primeros bytes de la misma y los muestre por pantalla.

    Notas sobre el ejercicio:

        Es similar al ejemplo visto para los sockets pero con la gran diferencia de que no necesitamos enviar ninguna petición al servidor como hacíamos con los sockets:

String request = "GET /HTTP/1.0 \n \n"

    El código para la conexión quedaría de la siguiente forma:

String url = "http://www.ugr.com/";
conn = (HttpConnection)Connector.open(url, Connector.READ_WRITE);

if (conn.getResponseCode( ) == HttpConnection.HTTP_OK) {
   is = conn.openInputStream( );
   final int MAX_LENGTH = 128;
   byte[] buf = new byte[MAX_LENGTH];
   int total = 0;

   while (total < MAX_LENGTH) {
	   int count = is.read(buf, total, MAX_LENGTH - total);
	   if (count < 0) {
	       break;
	   }
         total += count;                                                                       
   }

   is.close( );
   String reply = new String(buf, 0, total);
}


    Podríamos haber utilizado el método getLength para examinar inicialmente el número de bytes de toda la página y leerlos utilizando un bucle o bien utilizar DataInputStream en vez de InputStream y usando el método readFully() de la misma.

dis = conn.openDataInputStream( );
byte[] buf = new byte[conn.getLength( )];
dis.readFully(buf); 

    También cabe notar que quizás el mensaje de respuesta del HTTP  no incluya el tamaño completo del mensaje con lo que el método getLength devolverá -1, con lo que deberemos hacer un bucle utilizando el método read() hasta que éste devuelva -1.

    No olvidemos tampoco que debemos de comprobar el código que nos devuelve el servidor antes de leer ningún dato. Hay casos en los que hará falta realizar una segunda petición al servidor.

Código de respuesta Valor Significado
HTTP_OK 200 La petición tuvo éxito
HTTP_MOVED_PERM 301 El recurso solicitado se ha movido permanentemente a la URL especificada en la cabecera LOCATION.
HTTP_MOVED_TEMP 302 El recurso solicitado se ha movido temporalmente URL especificada en la cabecera LOCATION.
HTTP_SEE_OTHER 303 El recurso solicitado puede obtenerse realizando una petición GET en la URL especificada en la cabecera LOCATION.
HTTP_BAD_REQUEST 400 Petición mal formada
HTTP_FORBIDDEN 403 Típica en casos de control de acceso, falta de permisos para acceder al recurso.
HTTP_NOT_FOUND 404 El recurso solicitado no existe.

 

4.3.- Mensaje de petición HTTP al servidor

    Veamos ahora como trabajar más profundamente con la interfaz HttpConnection con los mensajes de petición al servidor y las respuestas de éste.

    Un mensaje de petición HTTP tiene tres partes:

        - una línea de petición seguida de un carácter de retorno de carro. Esta parte siempre está presente.

        - una serie de cabeceras de petición opcionales, cada una en una línea, terminando en una línea en blanco.

        - el cuerpo del mensaje, que puede tener datos necesarios para llevar a cabo la petición al servidor. Esta parte también es opcional, siendo solo rellenada cuando sea necesario.

 
4.3.1 Método de petición   

        Una línea de petición contiene el método de petición y la ruta relativa de la página web que deseamos leer en el servidor, junto con parámetros de petición y la versión del protocolo a utilizar. Véase como ejemplo:

GET /index.html HTTP/1.1

        HTTP 1.1 admite siete métodos distintos de petición, sin embargo, MIDP solo garantiza tres. Para cambiar de método de petición, utilizaremos el método setRequestMethod().

conexion.setRequestMethod(HttpConnection.HEAD);
MétodoConstante simbólica Significado
GET HttpConnection.GET Solicita la transferencia del recurso dado en la ruta relativa a la raiz del servidor. Dicha ruta puede tener parámetros "nombre=valor&nombre2=valor2..." concatenados con la ruta relativa. Cuando se utiliza GET, no hacen falta datos adicionales para enviar, pero si pueden enviarse datos de cabecera.
HEAD HttpConnection.HEAD HEAD es similar a GET. Se utiliza como método de test para ver si el método GET funciona correctamente. Su sintaxis y utilización son idénticas, con la salvedad, de que el servidor no nos devolverá la página web con HEAD, tan solo los datos de cabecera. Por tanto este método nos servirá como test para ver si un recurso solicitado existe, para ver el tamaño del mismo, etc. pero si que el recurso mismo sea transmitido.
POST HttpConnection.POST POST es igual que GET, con la salvedad de que los parámetros no se envían en la línea de petición sino en el cuerpo del mensaje. Esta forma de envío tiene dos ventajas: el mejor manejo para un gran número de parámetros en el envío, así como la ocultación a nivel de usuario de dichos parámetros.
 
4.3.2.- Cabeceras de la petición

        La forma típica de una línea de cabecera de la petición es (por ejemplo para el envío de parámetros en el cuerpo de la petición para el envío mediante POST):

Content-Length: 256

        Mostramos un pequeño resumen de las cabeceras más utilizadas. Para modificar o introducir un valor de cabecera, utilizamos el método setRequestProperty(), indicando el nombre de la cabecera y su valor:

conexion.setRequestProperty("Content-Type","application/x-www-form-urlencoded");

 

Nombre Significado
Connection Si esta cabecera está presente y tiene el valor "close" la conexión se cerrará una vez el servidor responda el mensaje.
Content-Length Número de bytes en el cuerpo del mensaje.
Content-Type Describe la codificación del cuerpo del mensaje. Ejemplos:

Content-Type: text/html
Content-Type: text/html; charset = ISO-8859-1
Date Fecha y hora en la que se envió el mensaje.
Last-Modified Fecha y hora en la que los datos fueron modificados por última vez en el servidor.
Location Utilizada por el servidor para indicar la dirección donde el recurso soliticado puede encontrase. Típico de la redirección en las paginas web.
Server Identificación del servidor. Solo informativo:

Server: Apache/1.3.14 (Unix) PHP/4.0.4
UserAgent Idenficicación del cliente. Solo informativo:

User-Agent: Profile/MIDP-1.0 Configuration/CLDC-1.0

 

4.3.- Mensaje de respuesta HTTP del servidor

    El formato de las respuestas HTTP es similar al de las peticiones consistiendo en:

        - Una línea de respuesta terminada en un retorno de carro.

        - Un conjunto de cabeceras, terminadas con retorno de carro. La última seguida de una línea en blanco para separarla del cuerpo del mensaje.

        - El cuerpo del mensaje con los datos solicitados.

 

4.4.1.- Línea de respuesta   

        Tiene tres partes, la versión del protocolo utilizada. Normalmente HTTP/1.1. Un código numérico indicando el resultado de la respuesta -tabla vista anteriormente- (obtenemos el código mediante getResponseCode()), y un mensaje calificando el código de la respuesta (obtenemos este mensaje mediante getResponseMessage()).

 

4.4.2.- Cabeceras de la respuesta

        Para obtener los valores devueltos por el servidor, habrá que ejecutar cualquiera de los métodos

public String getHeaderField(String name)
public int getHeaderFieldInt(String name, int def)
public long getHeaderFieldDate(String name, long def)

        Finalmente para obtener todas las cabeceras que ha enviado el servidor podemos utilizar el método getHeaderFieldKey(int index) que nos devolverá las cabeceras enviadas con el índice empezando en cero y devolviendo null cuando ha llegado al final.

for (int i= 0; i++){
    String name = conexion.getHeaderFieldKey(i);
    String value = conexion.getHeaderField(i);
    if (name == null){
        break;
    }
    .......
}