Bienvenidos sean a este post, hoy hablaremos sobre la posibilidad de poder hacer programas que se puedan ejecutar en varios dispositivos los cuales estan todos conectados por la misma red.
El paquete java.net contiene la coleccion de clases y metodos que proveen detalles de comunicacion a bajo nivel y permitiendo escribir programas que nos facilitan este tipo de tareas. El paquete soporta dos de los protocolos mas utilizados sobre la red:
- TCP: significa Protocolo de Control de Transmision (por sus siglas en ingles), este protocolo es utilizado para conexion estables entre dos aplicaciones
- UDP: significa Protocolo de Datagrama de Usuario (por sus siglas en ingles), una conexion menos protocolar y permite enviar paquetes de informacion a otra aplicacion sin necesidad de recibir la notificacion de que haya sido recibido
Estos dos protocolos corren sobre el Protocolo de Internet (IP) por lo que en general son los mas utilizados ya que son la base de tanto las redes locales de una casa o empresa (LAN) como de la internet (WAN), para continuar con nuestro tema hablemos sobre los sockets.
Los sockets proveen el mecanismo de comunicacion entre dos dispositivos utilizando TCP; el programa cliente crea un Socket que en su otro extremo intenta conectarse al socket de un servidor. Una vez realizada esta conexion, el servidor crea un objeto de tipo socket en su lado de comunicacion. Esto permite que tanto el cliente como el servidor podran leer y escribir en este socket permitiendo comunicarse entre ellos.
La clase java.net.Socket representa al socket y la clase java.net.SocketServer provee un mecanismo para que el programa servidor escuche a los clientes y asi poder establecer una comunicacion entre ambos. Estos son los pasos que ocurren cuando se establece la comunicacion entre dos dispositivos:
- El servidor instancia un objeto ServerSocket, diciendo cual numero de puerto de conexion se utilizara
- El servidor invoca al metodo accept() de la clase ServerSocket, este metodo espera hasta que un cliente se conecte en el puerto indicado anteriormente
- Con el servidor funcionando, el cliente instancia a un objeto Socket, donde especificaremos el servidor y el puerto a conectarse
- El constructor de la clase Socket intenta conectar al cliente con el servidor especificado y si la comunicacion es establecida ahora disponemos de un objeto Socket que permitira la comunicacion entre ambos
- Del lado del servidor accept() devuelve una referencia a un nuevo socket en el servidor que es conectado al socket del cliente
Con la conexion establecida la comunicacion utiliza streams de I/O (Entrada/Salida por sus siglas en ingles). Cada Socket tiene los metodos OutputStream e InputStream, y podemos decir que el cliente de OutputStream se conecta al InputStream del servidor y el OutputStream del servidor al InputStream del cliente. La ventaja de TCP es que permite una comunicacion en ambos sentidos y por ende se puede enviar informacion simultaneamente. A continuacion, veremos los constructores para la clase ServerSocket:
- public ServerSocket(int puerto) throws IOException: intenta crear un objeto socket con el puerto especificado, la excepcion puede ocurrir si el puerto esta siendo utilizado
- public ServerSocket(int puerto, int backlog) throws IOException: similar al anterior pero backlog indicara la cantidad de clientes a almacenar que iran a la cola de espera
- public ServerSocket(int puerto, int backlog, inetAddres direccion) throws IOException: similar al anterior pero el ultimo parametro va a ser la direccion IP donde establecera la conexion
- public ServerSocket() throws IOException: crea un socket de servidor independiente. Cuando use este constructor, use el método bind() cuando esté listo para enlazar el socket del servidor.
Si despues de utilizado algunos de estos constructores no arrojo ninguna excepcion podemos decir que hemos creado de forma exitosa un servidor y esta a la espera de clientes para comunicarse. Nuestra siguiente lista sera sobre algunos de los metodos disponibles para esta clase:
- public int getLocalPort(): devuelve el puerto que esta escuchando el servidor, esto es util si pasamos un numero de puerto en cero y dejamos que el servidor defina uno por usted
- public Socket accept() throws IOException: espera por un proximo cliente, este método se bloquea hasta que un cliente se conecta al servidor en el puerto especificado o el tiempo de espera del socket sino se establece alguno este método se bloquea indefinidamente
- public void setSoTimeout(int timeout): setea el valor de tiempo de espera por cuanto el socket server espera por un cliente durante el accept()
- public void bind(SocketAddress host, int backlog): especifica el servidor y puerto especificado al objeto SocketAddress, este debe usarse cuando se instancia por medio del constructor sin parametros antes visto.
Cuando ServerSocket invoca accept() el metodo no devuelve nada hasta que el cliente se conecta. Una vez establecida la conexion, este crea un nuevo Socket a un puerto no especificado y devuelve la referencia a este nuevo Socket. Con esto la conexion TCP entre cliente y servidor ha sido establecida y podemos decir que la comunicacion puede comenzar.
Nuestro siguiente tema sera la clase Socket, esta pertenece a java.net.Socket y este es el verdadero socket que permite realmente la comunicacion entre el cliente y el servidor. El cliente obtiene un objeto Socket por la instancia de uno. En cambio, el servidor obtiene por el valor devuelto del metodo accept(). A continuacion veremos los constructores de la clase Socket:
- public Socket(String host, int puerto) throws UnknownHostException, IOException: este metodo intenta conectarse al servidor y puerto informados, sino devuelve ninguna de las dos excepciones significa que la conexion fue exitosa
- public Socket(inetAddress host, int puerto) throws IOException: es similar al anterior pero el host es un objeto del tipo inetAddress
- public Socket(String host, int puerto, inetAddress localAdrress, int localPort) throws IOException: conecta al servidor y puerto especificado y a su vez crea un socket con la direccion y puerto local informado
- public Socket(inetAddress host, int puerto, inetAddress localAddress, int localPort) throws IOException: similar al anterior pero la direccion del servidor en lugar de ser String es de tipo inetAddress
- public Socket(): crea un socket sin conexion, para conectarse debe utilizar el metodo connect() para conectarse a un server
Cuando el constructor vuelve no solamente instancia un objeto de tipo Socket sino que a su vez intenta la conexion al servidor. Donde en caso de ser exitosa, nos permitira utilizar alguno de los metodos que describiremos a continuacion. Tengan en cuenta que de ambos extremos tenemos un objeto Socket, lo que implica que los siguientes metodos pueden ser utilizados tanto por el cliente como por el servidor:
- public void connect(SocketAddress host, int timeout) throws IOException: este metodo conecta al servidor especificado pero solamente se usa cuando utilizamos el constructor sin parametros
- public InetAddress getInetAddress(): devuelve la direccion de la otra computadora conectada al socket
- public int getPort(): devuelve el puerto del socket informado en el equipo remoto
- public int getLocalPort(): devuelve el puerto del socket informado en el equipo local
- public SocketAddress getRemoteSocketAddress(): devuelve la direccion del equipo remoto
- public InputStream getInputStream() throws IOException: devuelve el stream de entrada del socket, el stream de entrada esta conectado al stream de salida del equipo remoto
- public OutputStream getOutputStream() throws IOException: devuelve el stream de salida del socket, el stream de salida esta conectado al stream de entrada del equipo remoto
- public void close() throws IOException: cierra el socket y este metodo hace que el objeto socket no pueda conectarse a ningun servidor
Pasemos a hablar sobre la clase InetAddress, y algunos metodos, y tambien haremos un ejemplo de un cliente/servidor para entender el concepto. Comencemos con la clase InetAddress.
La clase InetAddress representa una direccion de protocolo de internet (IP). A continuacion, enlistaremos algunos de los metodos disponibles por esta clase:
- static InetAddress getByAddress(byte[] addr): devuelve un objeto InetAddres dada la direccion IP en crudo
- static InetAddress getByAddress(String host, byte[] addr): crea una InetAddres basado en el nombre del host y la direccion IP informados
- static InetAddress getByName(String host): determina la direccion IP de un host en base al nombre informado
- String getHostAddress(): devuelve la direccion IP en una representacion textual de tipo String
- String getHostName(): consigue el nombre del host para esta direccion IP
- static InetAddress getLocalHost(): devuelve el local host
- String toString(): convierte la direccion IP a tipo String
Nuestro siguiente paso sera sobre un ejemplo de tipo cliente/servidor. Para ello, crearemos dos programas, uno de cliente llamado Cliente.java y otro llamado Server.java comencemos con el cliente:
Cliente.java
import java.net.*;
import java.io.*;
public class Cliente
{
public static void main(String[] args)
{
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try
{
System.out.println("Conectando a " + serverName
+ " en el puerto " + port);
Socket cliente = new Socket(serverName, port);
System.out.println("Recien conectado a "
+ cliente.getRemoteSocketAddress());
OutputStream salidaAserver = cliente.getOutputStream();
DataOutputStream salida = new
DataOutputStream(salidaAserver);
salida.writeUTF("Hola desde " + cliente
.getLocalSocketAddress());
InputStream entDesdeServer = cliente.getInputStream();
DataInputStream entrada = new
DataInputStream(entDesdeServer);
System.out.println("Servidor dice "
+ entrada.readUTF());
cliente.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
Para este ejemplo, utilizaremos dos series de paquetes: java.net.* y java.io.*. Las cuales nos permitiran utilizar el Socket y los Stream respectivamente. Primero, crearemos una variable llamada serverName a la cual asignaremos el primer argumento. Nuestra siguiente variable llamada port le asignaremos el segundo argumento. De esto hablaremos luego, despues utilizaremos try/catch. En el try mostraremos primero un mensaje donde indicamos a quien nos conectamos, luego crearemos un objeto de tipo Socket llamado Cliente donde le pasaremos la informacion de nuestro servidor y puerto. Mostraremos un mensaje donde nos conectamos, en este caso obtendremos la direccion remota por medio de getRemoteSocketAddress(). Crearemos un objeto de tipo OutputStream llamada a salidaAserver para la cual usaremos getOutputStream() para nuestro cliente. Luego crearemos un objeto llamado salida del tipo DataOutputStream a la cual por medio del constructor le enviaremos el OutputStream del server. Despues mostraremos un mensaje para el cual usaremos el getLocalSocketAddress() para obtener la direccion local. Seguido a esto, crearemos un objeto llamado entDesdeServer del tipo InputStream tambien por medio de getInputStream obtendremos la entrada desde el lado del servidor. Al igual que antes, usaremos en este caso DataInputStream para crear un objeto llamado entrada y enviaremos el dato almacenado en entDesdeServer. Para mostrar un mensaje desde el socket de la entrada por medio de readUTF(), despues cerraremos el cliente, en el catch mostraremos un mensaje de la pila si ocurre algun error, pasemos a ver el codigo del servidor:
Server.java
import java.net.*;
import java.io.*;
public class Server extends Thread
{
private ServerSocket serverSocket;
public Server(int port) throws IOException
{
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(30000);
}
public void run()
{
while(true)
{
try
{
System.out.println("Esperando al cliente en el puerto "
+ serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("Recien conectado a " + server.
getRemoteSocketAddress());
DataInputStream entrada = new DataInputStream(
server.getInputStream());
System.out.println(entrada.readUTF());
DataOutputStream salida = new DataOutputStream(
server.getOutputStream());
salida.writeUTF("Gracias por conectarte a "
+ server.getLocalSocketAddress() + "\nAdios!!!");
server.close();
}
catch (SocketTimeoutException s)
{
System.out.println("Tiempo de espera expirado!!!");
break;
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
int port = Integer.parseInt(args[0]);
try
{
Thread t = new Server(port);
t.start();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
En el caso del server vamos a tener los dos paquetes de nuevo, java.net y java.io, despues haremos que esta clase extienda a Threads. Crearemos un objeto llamado serverSocket del tipo ServerSocket. Lo siguiente es un constructor donde recibira un atributo, el cual representara a nuestro puerto y en el cual se lo pasaremos a serverSocket por medio de su constructor y tambien le agregaremos un tiempo de expiracion mediante setSoTimeOut(). Despues tendremos un metodo llamado run, en el cual tendremos un try/catch donde habra un while el cual quedara bucleando siempre que este en true, mientras mostraremos un mensaje de espera. Luego crearemos un objeto llamado server y mediante serverSocket utilizaremos accept(). Si acepta la conexion, mostrara la direccion de la conexion remota a traves de getRemoteSocketAddress(). Despues crearemos un objeto llamado entrada de tipo DataInputStream y utilizaremos a getInputStream() y mostrara el mensaje obtenido mediante readUTF(). Nuestro siguiente paso sera crear un objeto llamado salida de DataOutputStream y mediante este objeto usaremos el metodo writeUTF() donde enviaremos un mensaje a la salida del socket remoto. Por ultimo, cerramos al server. Despues nos queda el primer catch que se activara en caso de un tiempo expirado y nos mostrara un mensaje en pantalla. El siguiente catch es por si ocurre un error de I/O y nos mostrara la descripcion de la pila.
Despues pasamos al main donde tendremos una variable llamada port a la cual le asignaremos el primer argumento, e Integer.parseInt se encargaran de convertir ese dato en tipo entero. Luego crearemos un objeto de tipo Thread llamado t para asignarle mediante nuestro constructor el valor del puerto para el SocketServer y despues con start() lo iniciaremos. Deberemos compilar los dos programas previamente y asumiendo que no ocurrio ningun error primero deberemos ejecutar nuestros programas de la siguiente manera si estan probando desde un linux:
$ java Server 9999 | java Cliente localhost 9999
Nota: Si lo prueban desde un Windows les recomiendo ejecutarlos en dos ventanas de DOS separadas, ejecutando primero el Server y luego el cliente.
Al hacerlo (en Linux) obtendran la siguiente salida:
$ java Server 9999 | java Cliente localhost 9999
Conectando a localhost en el puerto 9999
Recien conectado a localhost/127.0.0.1:9999
Servidor dice Gracias por conectarte a /127.0.0.1:9999
Adios!!!
$
Como pueden ver no nos mostro el mensaje del server pero si creo el socket y nuestro cliente pudo conectarse exitosamente, y observen como nos devolvio la notificacion creada en el servidor.
En resumen, hoy hemos visto el networking para Java, hemos hablado sobre Socket, tanto del Socket como del SocketServer, hemos visto los constructores y los metodos de estas clases, hemos visto a InetAddress, hemos visto un ejemplo de un cliente, un ejemplo de un servidor, como pudimos obtener informacion uno de otro y como pudimos enviar informacion uno de otro. Espero les haya sido de utilidad, les dejo algunas de mis redes sociales para seguirme o recibir una notificacion cada vez que subo un nuevo post:


Donación
Es para mantenimento del sitio, gracias!
$1.50





