Logo Studenta

Sockets Java

¡Estudia con miles de materiales!

Vista previa del material en texto

Práctica 3: Introducción a los sockets 
en Java 
 
En esta práctica se va a tener una primera toma de contacto con la 
interfaz de los sockets en Java. Para ello plantearemos una serie de 
ejercicios muy sencillos que ilustran algunos conceptos básicos del 
funcionamiento de los sockets TCP en Java. 
1. Entorno de trabajo 
Java está disponible para diferentes sistemas operativos y con diversos 
entornos de programación. Nosotros vamos a trabajar en Linux. Para editar 
el programa os sugerimos el uso del editor kwrite. 
Por otra parte, recuerda que si el nombre del fichero fuente de nuestro 
programa es Ejemplo.java lo compilaremos mediante la orden “javac 
Ejemplo.java ”, y lo ejecutaremos tecleando “java Ejemplo ”. 
Además, el lenguaje Java distingue entre mayúsculas y minúsculas, y el 
nombre del fichero debe coincidir con el de la clase principal. 
Toda la información sobre las clases de Java puede encontrarse en la 
página Web de Sun: http://java.sun.com/j2se/1.4/docs/api. 
Por otra parte, en http://www.ulpgc.es/otros/tutoriales/java 
se puede consultar un curso de Java. 
P3-2 Prácticas de Redes de Computadores 
2. Cómo establecer conexión con un servidor 
La clase java.net.Socket es la clase Java fundamental para 
realizar operaciones TCP desde el extremo cliente. A partir de aquí nos 
referiremos a ella como clase Socket . Esta clase posee varios constructores 
que permiten especificar el host destino y el número de puerto al que 
queremos conectarnos. El host puede especificarse como un objeto 
InetAddress (que corresponde a una dirección IP) o como un String . El 
número de puerto siempre se indica como un valor int que puede variar 
desde el 1 al 65.535. 
Empezaremos probando el costructor más sencillo: ”public Socket 
(String host, int puerto) throws IOException, 
UnknownHostException” 
Este constructor crea un socket TCP e intenta conectarlo al host y 
puerto remotos especificados como parámetros. Si el servidor DNS no está 
en funcionamiento o no puede resolver el nombre, el constructor lanzará una 
excepción UnknownHostException . Si el nombre se resuelve pero el 
socket no puede conectar por alguna otra razón, se lanzará una excepción 
IOException . Esto puede deberse, entre otras razones, a que el puerto 
destino en el servidor no esté abierto, existan problemas de encaminamiento 
en la red para alcanzar el destino o simplemente el servidor especificado 
esté apagado. 
Ejercicio 1: 
Escribe un programa en java, “ ClienteTCP1.java ” , que sea capaz de aceptar 
dos parámetros de entrada en línea de órdenes para establecer una conexión: el 
nombre de un servidor y un número de puerto al que conectar en ese servidor. El 
programa debe visualizar un mensaje por pantalla indicando si la conexión se ha 
establecido o no. En caso de éxito debe mostrar también el número de puerto y 
el nombre del servidor con el que ha conectado, y en caso de fallo indicar el 
motivo: “Nombre de servidor desconocido” o “No es posible realizar conexión”, 
dependiendo el tipo de excepción ocurrida (ver notas en página siguiente). 
Prueba tu programa con los siguientes servidores y puertos, comprobando lo que 
sucede: 
1. mail.redes.upv.es 25 
2. herodes.redes.upv.es 25 
3. zoltar.redes.upv.es 25 
Introducción a los sockets en Java P3-3 
 
Ejercicio 1 (NOTAS): 
Para facilitar la depuración, el programa debe comprobar si hay parámetros de 
entrada y, si no se han suministrado, intentar conectarse al puerto 25 del servidor 
“zoltar.redes.upv.es”. 
Por otra parte, recuerda que los parámetros de entrada en Java son de tipo 
“String” y dado que el puerto en el constructor Socket es un “int” , es 
necesario realizar una conversión de tipo. De este modo, si args[1] es el 
parámetro correspondiente al puerto, la conversión puede hacerse, por ejemplo, 
como: int puerto = Integer.parseInt(args[1]) . 
Otra opción para crear el socket es pasarle como parámetro la 
dirección IP mediante un objeto InetAddress , en lugar del nombre de 
dominio del servidor. En este caso se podría generar una excepción 
IOException si no se puede conectar pero no una UnknownHostExcep-
tion . Ahora la consulta al DNS se lleva a cabo al crear el objeto 
InetAddress y es, en este momento, cuando pueden surgir los problemas 
relacionados con la traducción nombre-dirección IP. La clase Inet-
Address posee varios métodos estáticos que permiten crear objetos Inet-
Address inicializados de forma adecuada. El más popular es: 
public static InetAddress InetAddress.getByName(String HostName) 
que se utiliza de la forma: 
InetAddress dirIP = InetAddress.getByName(“www.upv.es”) 
Si se van a abrir varias conexiones al mismo host (como haremos en el 
último apartado de la práctica) es más eficiente emplear este constructor, 
para resolver únicamente una vez la dirección IP a la que se desea conectar e 
indicarla directamente en el constructor Socket . 
Ejercicio 2: 
Copia el “ClienteTCP1.java” a “ClienteTCP2.java” . Modifícalo para 
que traduzca el nombre del host antes de crear el socket. Comprueba que funcio-
na de forma equivalente. 
3. Cómo obtener información sobre la conexión establecida 
La clase Socket dispone de varios métodos que permiten obtener 
información sobre la conexión establecida entre el cliente y el servidor. 
P3-4 Prácticas de Redes de Computadores 
Entre ellos podemos citar getLocalAddress() y getInetAddress() , 
que devuelven las direcciones IP local y remota, respectivamente, y 
getLocalPort() y getPort() , que devuelven los puertos local y 
remoto, respectivamente. A continuación se detallan brevemente los méto-
dos mencionados. Puedes consultar más información sobre ellos en la web 
de Sun, que se cita al inicio de la práctica 
• public int getPort() : devuelve el puerto remoto al que el 
socket está conectado. 
• public InetAddress getInetAddress() : devuelve la direc-
ción IP remota a la que el socket está conectado. 
• public int getLocalPort() : devuelve el puerto local al que el 
socket está ligado. 
• public InetAddress getLocalAddress() : Devuelve la di-
rección IP local a la que el socket está ligado. 
Ejercicio 3: 
Modifica el cliente “ClienteTCP2.java” del ejercicio anterior para que 
muestre información en la pantalla del cliente sobre la conexión que establece 
(direcciones IP y números de puerto locales y remotos). 
Ejecútalo cuatro veces seguidas, conectándote con el servidor del laboratorio 
“zoltar.redes.upv.es” en el puerto 25 y comprueba qué valores se modifican. 
Interpreta el resultado obtenido. 
4. Cómo leer los datos que se reciben a través de la conexión 
Para leer los datos que se van recibiendo a través del socket 
utilizaremos el método getInputStream de la clase Socket . Este método 
devuelve un objeto del tipo InputStream (flujo de octetos de entrada), lo 
que se ajusta bien a la filosofía TCP de transmisión orientada a flujo conti-
nuo de datos, pero no resulta cómodo para leer los mensajes del servidor. Lo 
que nos conviene, para facilitar nuestro trabajo, es un método que propor-
cione un flujo de caracteres y, a ser posible, en líneas de texto completas. 
La clase InputStreamReader es un “puente” desde los flujos de 
bytes a los de caracteres: lee octetos y los codifica en caracteres empleando 
un código de caracteres determinado. Adicionalmente, para mejorar la 
eficiencia en la conversión pueden leerse varios octetos en cada operación 
Introducción a los sockets en Java P3-5 
 
de lectura anidando esta clase dentro de una BufferedReader . Por 
ejemplo, de la forma siguiente: 
BufferedReader lee = new BufferedReader(new 
InputStreamReader(s.getInputStream())); 
siendo “s” el objeto de la clase Socket definido previamente. 
Cuando un programa lee de un BufferedReader , el texto se extrae 
de un buffer en lugar de acceder al flujo de entrada directamente. Cuando el 
buffer se vacía, vuelve a llenarse con tanto texto como sea posible, incluso 
aunque no todo sea aún necesario. Esto hará que las futuras lecturasse 
lleven a cabo más rápidamente. 
En nuestro caso, empleando el método readLine de la clase 
BufferedReader podremos leer las respuestas del servidor como líneas 
completas de texto. Este método lee una línea de texto y la devuelve como 
un String . 
public String readLine() throws IOException 
Ejercicio 4: 
Renombra el cliente “ClienteTCP2.java” del ejercicio anterior como 
“ClienteSMTP.java” . Modíficalo para que se conecte siempre al puerto 25 y 
muestre la primera línea de texto que recibe del servidor. 
5. Cómo enviar datos a través de la conexión 
Para escribir a través de un socket también es más eficiente utilizar 
una clase que proporcione cierta capacidad de almacenamiento (buffering). 
Por este motivo, para escribir a través del socket, utilizaremos un objeto de 
la clase java.io.PrintWriter conectado al flujo de salida del socket. 
Este objeto proporciona la capacidad de almacenamiento deseada. Además, 
esta clase permite manejar adecuadamente conjuntos de caracteres y texto 
internacional. 
 Uno de sus constructores (el que utilizaremos habitualmente) es: 
public PrintWriter(OutputStream out, boolean autoFlush) 
que además posee una ventaja importante como comprobaremos a 
continuación. Para ello el segundo parámetro del constructor debe invocarse 
con el valor true . 
P3-6 Prácticas de Redes de Computadores 
6. Vaciado del buffer TCP (flush) 
Aunque las ventajas de emplear para la escritura una clase con 
almacenamiento son claras, también puede plantear algunos inconvenientes 
si uno no es cuidadoso, como vamos a ver en el ejercicio siguiente. 
Ejercicio 5: 
Copia el programa “ClienteSMTP.java ” y renómbralo como 
“ClienteSMTPej5.java” . Añade, al final de tu programa, el código 
siguiente: 
 PrintWriter esc = new PrintWriter 
(s.getOutputStream()); 
 salida.print("EHLO redesXX.redes.upv.es\r\n"); 
 System.out.println(“lectura 2: “ + lee.readLine()); 
 s.close(); 
NOTA: Se ha supuesto que el objeto BufferedReader definido en el ejercicio 
anterior se llama “lee” y el objeto Socket se llama “s” . 
Ejecútalo para que se conecte al puerto 25 de “zoltar.redes.upv.es”. ¿Qué es lo 
que ocurre?¿Aparece la segunda respuesta del servidor en tu pantalla? (Lo 
normal será que no aparezca nada). 
 
Este cliente SMTP debería enviar un mensaje correcto al servidor 
SMTP de zoltar y recibir su respuesta, pero no recibe nada. ¿Por qué? 
Porque él tampoco le envía nada. Para mejorar la eficiencia, el stream de 
salida intenta llenar su buffer tanto como sea posible antes de enviar los 
datos, pero como el cliente no tiene más datos que enviar (de momento) su 
petición no llega a enviarse nunca. 
La solución a este problema la da el método flush () de la clase 
PrintWriter . Este método fuerza a que se envíen los datos aunque el 
buffer no esté aún lleno. En caso de duda acerca de si resulta o no necesario 
utilizarlo, es mejor invocarlo, ya que realizar un flush innecesario 
consume pocos recursos, pero no utilizarlo cuando se necesita puede 
provocar bloqueos en el programa. 
Ejercicio 6: 
Modifica el cliente SMTP para que utilice el método flush y comprueba que 
ahora funciona correctamente. 
Introducción a los sockets en Java P3-7 
 
Podemos realizar el vaciado del buffer de forma automática al escribir 
en éste (sin tener que emplear el método flush explícitamente). Para ello 
necesitamos dos cosas: 
• El constructor de la clase PrintWriter debe emplearse tal y como 
se ha mostrado antes, con un segundo parámetro adicional a true . 
• La escritura debe realizarse mediante el método println() , en 
lugar del método print . Además el método println añade el final 
de línea con lo que no necesitamos escribirlo como parte de la orden 
que envía el cliente (a diferencia de lo que hemos hecho en el 
programa anterior). 
Vamos a modificar nuestro cliente para comprobar este funciona-
miento. 
Ejercicio 7: 
Modifica el cliente SMTP para que utilice el método println en lugar del 
print (elimina lo que sobra, como la secuencia \r\n y el flush ). Comprueba 
que funciona volviendo a establecer conexión con el servidor zoltar en el puerto 
25. 
NOTA: Hay que tener en cuenta que cuando usamos el método println , lo 
que se envía como final de línea es \n . El estándar especifica que debe enviarse 
\r\n . No obstante, como podéis haber comprobado al usar println , el 
programa funciona correctamente. Esto es debido a la generosidad del servidor, 
que contesta a la petición a pesar de que no se ajusta completamente al estándar. 
Podemos ajustarnos fácilmente al estándar definiendo los finales de línea como 
\r\n con la sentencia System.setProperty (“line.separador”, ”\r\n”); 
7. Cierre de la conexión 
Las conexiones se cierran mediante el método close() de la clase 
Socket . Una vez que un socket se ha cerrado, ya no está disponible para 
volverlo a utilizar (por ejemplo, no puede ser reconectado). En caso 
necesario, hay que volver a crear un nuevo socket. Si este socket tenía un 
flujo asociado, el flujo se cierra también. 
 
P3-8 Prácticas de Redes de Computadores 
8. Explorador de puertos 
¿Está seguro nuestro ordenador? ¿Es fácil entrar en él de forma no 
autorizada desde otro ordenador? Responder a esta pregunta es complicado. 
En general, sabemos que para entrar en nuestro ordenador desde otro equipo 
es necesario que en alguno de los puertos de nuestro ordenador haya un 
servidor escuchando. En el caso más extremo, si todos los puertos de 
nuestro ordenador están cerrados, tenemos la seguridad de que no vamos a 
aceptar datos que provengan de la red, por lo que evitamos la entrada no 
deseada de intrusos. 
Cerrar todos los puertos del ordenador puede no ser una buena idea, 
pues seguramente algunos de los servicios que usamos habitualmente (y de 
los cuales incluso no somos conscientes) dejarán de funcionar, con la 
correspondiente molestia. 
Una política más acertada es mantener abiertos únicamente los puertos 
que necesitamos y cerrar el resto. No obstante, a menudo el sistema 
operativo se configura con determinadas opciones por defecto, dejando 
abiertos algunos puertos que seguramente no deseamos que estén abiertos. 
En otros casos, es posible que un intruso haya abierto un puerto en nuestro 
ordenador y haya dejado en él un servidor, del cual no tenemos 
conocimiento. 
La mejor manera de saber qué puertos están abiertos en nuestro 
ordenador es usar un explorador de puertos. Realizar esta tarea en TCP es 
muy sencillo. Basta con recorrer los puertos de nuestro ordenador 
intentando conectarnos a ellos. Si un puerto nos permite conectarnos, 
significa que hay un servidor escuchando en él. Si rechaza la conexión, 
entonces el puerto está cerrado. En el caso de UDP, hacer la exploración no 
es tan sencilla, pues al ser un servicio sin conexión, si no recibimos 
contestación, nunca vamos a tener la seguridad de qué es lo que ha pasado 
con nuestro mensaje. 
Para crear el explorador de puertos nos vamos a valer de la clase 
Socket , que ya hemos empleado en los ejercicios anteriores. Como hemos 
visto, su constructor puede lanzar dos excepciones diferentes: 
• UnknownHostException , que se genera cuando el nombre del orde-
nador con el que queremos crear la conexión no puede ser resuelto a una 
dirección IP. 
• IOException , que se genera cuando no se puede establecer la co-
nexión por cualquier otro motivo, como por ejemplo que no haya un 
Introducción a los sockets en Java P3-9 
 
servidor escuchando en el puerto especificado en los parámetros de 
Socket . 
Utilizaremos esta segunda excepción para crear el explorador de 
puertos, de manera que si mientras barremos los puertos de nuestro equipo 
se genera esta excepción, sabremos que el puerto está cerrado. Si no se 
genera, entonces es que hay un servidor en ese puerto. Como lo habitual es 
que la mayoría de los puertos se encuentren cerrados, nuestro explorador 
debe mostrar por pantalla un mensaje únicamente si el puerto está abierto. 
En caso contrario,suelen salir tantos mensajes que resulta difícil averiguar 
todos los puertos abiertos. 
Ejercicio 8: 
Teclea y completa el siguiente explorador de puertos. Ejecútalo. ¿Qué puertos 
hay abiertos?¿A qué servicios corresponden esos puertos? (en /etc/services 
hay un listado detallado de los servicios ordenados por número de puerto). 
 
import java.net.*; 
import java.io.*; 
public class LowPortScanner { 
 public static void main(String[] args) { 
 String host = "localhost"; 
 for (int puerto = 1; puerto < 1024; puerto ++) { 
 try { 
 // COMPLETAR CÓDIGO 
 } 
 catch (UnknownHostException e) { 
 // COMPLETAR CÓDIGO 
 } 
 catch (IOException e) { 
 // COMPLETAR CÓDIGO 
 } 
 } // end for 
 } // end main 
} // end PortScanner 
 
Ejercicio 9: 
Elimina de tu ordenador todos los ficheros que has creado durante el desarrollo 
de esta práctica.

Continuar navegando

Otros materiales