En este tutorial vamos a conectar un servidor hecho en java con un cliente en java y ver cómo pasar datos de uno a otro. Para una explicación de lo que es un socket, un servidor, un cliente, puedes ver la primera parte del ejemplo de sockets en C. Aunque sea en C, los coceptos son los mismos. Utilizo la versión de J2SE 1.4.1_01.
Al final hay unos fuentes de ejemplo como aplicaciones independientes. No intentes todo esto desde un applet porque tendrás problemas de permisos.
Los pasos que debemos dar en el servidor son los siguientes:
Para hacer el servidor en java tenemos la clase ServerSocket. Al instanciarla usaremos el constructor al que se le pasa un número de servicio (de puerto). Como se vio en C, este número de puerto puede ser cualquier entero entre 1 y 65535. Los números de 1 a 1023 están reservados para servicios del sistema (como ftp, mail, www, telnet, etc, etc). Del 1024 en adelante podemos usarlos a nuestro gusto. Lo único es que no puede haber dos servidores atendiendo al mismo puerto/servicio.
ServerSocket socket = new ServerSocket (35557);
Una vez creado el servidor, le decimos que empiece a atender conexiones de clientes. Para ello llamamos al método accept(). Este método se queda bloqueado hasta que algún cliente se conecta. Nos devuelve un Socket, que es la conexión con dicho cliente.
Socket cliente = socket.accept();
Podemos aceptar simultáneamente varios clientes, pero para atenderlos necesitaremos programación multitarea o algo similar.
Ahora en cliente tenemos la conexión con el cliente (valga la redundancia). Lo único que tenemos que hacer es obtener de él el OuputStream o InputStream con los métodos getOutputStream() o getInputStream(). La clase OutpuStream nos sirve para enviarle datos al cliente. La clase InputStream nos sirve para leer datos del cliente.
InputStream entrada =
cliente.getIntputStream();
OutputStream salida = cliente.getOutputStream();
Construirnos un InputStream y/o un OutputStream más adecuados a nuestras necesidades
Los métodos de estas dos clases para leer o escribr datos son un poco feos, ya que únicamente envían bytes. Suele ser habitual construir alguna otra clase de entrada/salida de datos que tenga métodos más adecuados:
DataInputStream entradaDatos =
new DataInputStream
(entrada);
DataOuputStream salidaDatos = new DataOutputStream (salida);
ObjectInputStream entradaObjetos
= new ObjectInputStream
(entrada);
ObjectOutputStream salidaObjetos = new ObjectOutputStream (salida);
Estas nuevas clases tienen métodos más bonitos de usar (writeInt(), writeChar(), etc)
El envío/lectura de datos normales se hace con las clases DataInputStream y DataOutputStream. No tienen ningún truco especial, basta usar el metodo adecuado (writeInt(), writeFloat(), readInt(), etc). Para strings usaremos los métodos writeUTF() y readUTF(), que envían/leen las cadenas en formato UTF.
Para el envio/lectura de clases normales usaremos los métodos readObject() y writeObject() de ObjectInputStream y ObjectOutputStream. Para poder usar estos métodos las clases que enviemos deben implementar la interface Serializable.
Las clases de tipos de java (Integer, Float, String, etc) implementan esa interface, así que se pueden enviar/leer a través de estos métodos.
Si una clase nuestra contiene atributos que sean primitivos de java (int, float, etc) o clases primitivas (Float, Integer, String, etc), basta con hacer que implemente la interface Serializable para poder enviarlas. No hace falta que escribamos ningún método, simplemente poner que se implementa.
// Esta clase se puede enviar/leer
por un socket.
class Atributo implements Serializable
{
int a;
String b;
}
Si alguno de los atributos no es primitivo de java, basta con que implemente la misma interface Serializable. Si es así, no tendremos ningún problema.
// Esta clase se puede enviar/leer
por un socket
class DatoSocket implements Serializable
{
int c;
String d;
Atributo e; // Esta clase
es serializable.
}
Si alguna de las clases no es Serializable, tendremos que implementar los métodos privados
private void
writeObject(java.io.ObjectOutputStream out)
throws IOException {
// Enviamos los
atributos a y b en un orden cualquiera.
out.writeInt (a);
out.writeUtf (b);
}
private void readObject(java.io.ObjectInputStream in) throws
IOException, ClassNotFoundException {
// Leemos los atributos
a y b en el mismo orden que los enviamos en writeObject()
a = in.readInt();
b = in.readUtf();
}
En el método writeObject() debermos enviar por el ObjectOutputStream todos los atributos de nuestra clase, en el orden que consideremos adecuado. En el método readObject() deberemos ir leyendo del ObjectInputStream todos los atributos de nuestra clase e ir rellenando nuestros atributos. El orden en que leemos debe ser el mismo que en el que escribimos y el formato leido el mismo que el escrito.
En los dos cuadros de ejemplo de arriba hemos puesto el código de ambos métodos para la clase Atributo, pero realmente no hace falta escribir este código. En los fuentes de ejemplo aparece el código, pero está comentado. Si lo descomentas funcionará igual.
Para enviar una de estas clases el código es sencillo
DatoSocket dato = new DatoSocket();
cliente.writeObject(dato);
Para leerlo es igual de simple, sólo que tenemos que saber qué tipo de clase estamos recibiendo para hacer el "cast" adecuado.
DatoSocket dato;
Object aux;
aux = socket.readObject();// Se lee el objeto
if (aux instanceof DatoSocket) // Se comprueba
si es de tipo DatoSocket
dato = (DatoSocket)aux; //
Se hace el cast.
Debemos ir haciendo varios if con las posibles clases que nos envíen desde el otro lado.
Para cerrar un socket hay que llamar a la función close().
cliente.close(); // Con
esto se cierra la conexión con el cliente.
socket.close(); // Con esto se cierra el
socket servidor, ya no atendemos más conexiones.
Para el cliente tenemos la clase Socket. Basta instanciarla indicandole contra que máquina conectarse y el puerto con el que debe conectarse. Debe ser el mismo que el puerto que está atendiendo el servidor.
Socket socket = new Socket ("localhost", 35557);
El resto es igual que en el servidor.
Tienes los fuentes de un ejemplo entre un servidor y un cliente. SocketServidor.java es el servidor, SocketCliente.java es el cliente, Atributo.java es un atributo de una clase a enviar y DatoSocket.java es la clase a enviar, que contiene un Atributo. Puedes bajarte los fuentes, quitarles la extensión .txt y compilarlos y ejecutarlos. Si todo está en el mismo directorio, puedes compilarlos desde una shell de linux o una ventana de ms-dos con
$ javac SocketServidor.java SocketCliente.java DatoSocket.java Atributo.java
Para ejecutarlo, abre dos shell o ventanas de ms-dos distintas, situadas en el directorio donde esté todo esto y escribe