El Lado del Servidor de un Socket
Esta sección muestra cómo escribir el lado del servidor de una conexión socket, con un ejemplo
completo cliente-servidor. El servidor en el pareja cliente/servidor sirve bromas "Knock Knock".
Las bromas Knock Knock son las favoritas por los niños pequeños y que normalmente
son vehículos para malos juegos de palabras. Son de esta forma.
Servidor: "Knock knock!"
Cliente: "¿Quién es?"
Servidor: "Dexter."
Cliente: "¿Qué Dexter?"
Servidor: "La entrada de Dexter con ramas de acebo."
Cliente: "Gemido."
El ejemplo consiste en dos programas Java independientes ejecutandose: el programa cliente y el
servidor. El programa cliente está implementado por una sóla clase KnockKnockClient, y está
basado en el ejemplo EchoTest de la
página anterior. El programa
servidor está implementado por dos clases: KnockKnockServer y KKState. KnockKnockServer
contiene el método main() para el program servidor y realiza todo el
trabajo duro, de escuchar el puerto, establecer conexiones, y leer y escribir a través del
socket. KKState sirve la bromas: sigue la pista de la broma actual, el estado actual (enviar
konck knock, enviar pistas, etc...) y servir varias piezas de texto de la broma dependiendo del
estado actual. Esta página explica los detalles de cada clase en estos programas y finalmente le
muestra cómo ejecutarlas.
El servidor Knock Knock
Esta sección pasa a través del código que implemente el programa servidor Knock Knock, Aquí
tienes el código fuente completo de la clase
KnockKnockServer.class. El
programa servidor empieza creando un nuevo objeto ServerSocket para escuchar en un puerto
específico. Cuando escriba un servidor, debería elegir un puerto que no estuviera ya dedicado a
otro servicio, KnockKnockServer escucha en el puerto 4444 porque sucede que el 4 es mi número
favorito y el puerto 4444 no está siendo utilizado por ninguna otra cosa en mi entorno.
try {
serverSocket = new ServerSocket(4444);
} catch (IOException e) {
System.out.println("Could not listen on port: " + 4444 + ", " + e);
System.exit(1);
}
ServerSocket es una clase java.net que proporciona una implementación independientes del sistema
del lado del servidor de una conexión cliente/servidor. El constructor de ServerSocket lanza una
excepción por alguna razón (cómo que el puerto ya está siendo utilizado) no puede escuchar en el
puerto especificado. En este caso, el KnockKnockServer no tiene elección pero sale.
Si el servidor se conecta con éxito con su puerto, el objeto ServerSocket se crea y el servidor
continua con el siguiente paso, que es aceptar una conexión desde el cliente.
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
} catch (IOException e) {
System.out.println("Accept failed: " + 4444 + ", " + e);
System.exit(1);
}
El método accept() se bloquea (espera) hasta que un cliente empiece y pida
una conexión el puerto (en este caso 4444) que el servidor está escuchando. Cuando el método
accept() establece la conexión con éxito con el cliente, devuelve un
objeto Socket que apunta a un puerto local nuevo. El servidor puede continuar con el cliente
sobre este nuevo Socket en un puerto diferente del que estaba escuchando originalmente para las
conexiones. Por eso el servidor puede continuar escuchando nuevas peticiones de clientes a
través del puerto original del ServerSocket. Esta versión del programa de ejemplo no escucha más
peticiones de clientes. Sin embargo, una versión modificada de este programa, porpocionada más
adelante, si lo hace.
El código que hay dentro del siguiente bloque try implememte el lado del
servidor de una comunicación con el cliente. Esta sección del servidor es muy similar al lado
del cliente (que vió en el ejemplo de la página anterior y que verá más adelante en el ejemplo
de la clase KnockKnockClient).
- Abre un stream de entrada y otro de salida sobre un socket.
- Lee y escribe a través del socket.
Empecemos con las primeras 6 líneas.
DataInputStream is = new DataInputStream(
new BufferedInputStream(clientSocket.getInputStream()));
PrintStream os = new PrintStream(
new BufferedOutputStream(clientSocket.getOutputStream(), 1024), false);
String inputLine, outputLine;
KKState kks = new KKState();
Las primeras dos líneas del código abren un stream de entrada sobre el socket devuelto por el
método accept(). Las siguiente dos líneas abren un stream de salida sobre
el mismo socket. La siguiente línea declara y crea un par de strings locales utilizadas para
leer y escribir sobre el socket. Y finalmente, la última línea crea un objeto KKState. Este es
el objeto que sigue la pista de la broma actual, el estado actual dentro de una broma, etc..
Este objeto implementa el protocolo -- el lenguaje que el cliente y el
servidor deben utilizar para comunicarse.
El servidor es el primero en hablar, con estas líneas de código.
outputLine = kks.processInput(null);
os.println(outputLine);
os.flush();
La primera línea de código obtiene del objeto KKState la primera línea que el servidor le dice
al cliente. Por ejemplo lo primero que el servidor dice es "Knock! Knock!".
Las siguientes dos líneas escriben en el stream de salida conectado al socket del cliente y
vacía el stream de salida. Esta secuencia de código inicia la conversación entre el cliente y el
servidor.
La siguiente sección de código es un bucle que lee y escribe a través del socket enviando y
recibiendo mensajess entre el cliente y el servidor mientras que tengan que decirse algo el uno
al otro. Como el servidor inicia la conversación con un "Knock! Knock!", el servidor debe
esperar la respuesta del cliente. Así el bucle while itera y lee del stream
de entrada. El método readLine() espera hasta que el cliente respondan algo
escribiendo algo en el stream de salida (el stream de entrada del servidor). Cuando el cliente
responde, el servidor pasa la respuesta al objeto KKState y le pide a éste una respuesta
adecuada. El servidor inmediatamente envía la respuesta al cliente mediante el stream de salida
conectado al socket, utilizando las llamadas a println() y
flush(). Si la respuesta del servidor generada por el objeto KKState es
"Bye.", indica que el cliente dijo que no quería más bromas y el bucle termina.
while ((inputLine = is.readLine()) != null) {
outputLine = kks.processInput(inputLine);
os.println(outputLine);
os.flush();
if (outputLine.equals("Bye."))
break;
}
La clase KnockKnockServer es un servidor de buen comportamiento, ya que las últimas líneas de
esta sección realizan la limpieza cerrando todas los streams de entrada y salida, el socket del
cliente, y el socket del servidor.
os.close();
is.close();
clientSocket.close();
serverSocket.close();
El Protocolo Knock Knock
La clase KKState implementa el protocolo
que deben utilizar el cliente y el servidor para comunicarse. Esta clase sigue la pista de dónde
están el cliente y el servidor en su comunicación y sirve las respuestas del servidor a las
setencias del cliente. El objeto KKState contiene el texto de todos las bromas y se asegura de
que el servidor ofrece la respuesta adecuada a las frases del cliente. No debería decir el
cliente "¿Qué Dexter?" cuando el servidor dice "Knock! Knock!".
Todos las parejas cliente-servidor deden tener algún protocolo en el que hablar uno con otro, o
el significado de los datos que se pasan unos a otro. El protocolo que utilicen sus clientes y
servidores dependen enteramente de la comunicación requerida por ellos para realizar su tarea.
El Cliente Knock Knock
La clase KnockKnockClient
implementa el programa cliente que habla con KnockKnockServer. KnockKnockClient está basado en
el programa EchoTest de la página
anterior y debería serte familiar.
Pero echemos un vistazo de todas formas para ver lo que sucede en el cliente, mientras tenemos
en mente lo que sucedía en el servidor.
Cuando arranca el program cliente, el servidor debería estar ya ejecutándose y escuchando el
puerto esperando un petición de conexión por parte del cliente.
kkSocket = new Socket("taranis", 4444);
os = new PrintStream(kkSocket.getOutputStream());
is = new DataInputStream(kkSocket.getInputStream());
Así, lo primero que hace el programa cliente es abrir un socket sobre el puerto en el que está
escuchando el servidor en la máquina en la que se está ejecutando el servidor. El programa
ejemplo KnockKnockClient abre un socket sobre el puerto 4444 que el mismo por el que está
escuchando el servidor. KnockKnockClient utiliza el nombre de host taranis,
que es el nombre de una máquina (hipotética) en tu red local. Cuando teclees y ejecutes este
programa en tu máquina, deberías cambiar este nombre por el de una máquina de tu red. Esta es la
máquina en la ejecutará KnockKnockServer.
Luego el cliente abre un stream de entrada y otro de salida sobre el socket.
Luego comienza el bucle que implementa la comunicación entre el cliente y el servidor. El
servidor habla primero, por lo que el cliente debe escuchar, lo que hace leyendo desde el
stream de entrada adosado al socket. Cuando el servidor habla, si dice "Bye,", el cliente sale
del bucle. De otra forma muestra el texto en la salida estandard, y luego lee la respuesta del
usuario, que la teclea en al entrada estandard. Después de que el usuario teclee el retorno de
carro, el cliente envía el texto al servidor a través del stream de salida adosado al socket.
while ((fromServer = is.readLine()) != null) {
System.out.println("Server: " + fromServer);
if (fromServer.equals("Bye."))
break;
while ((c = System.in.read()) != '\n') {
buf.append((char)c);
}
System.out.println("Client: " + buf);
os.println(buf.toString());
os.flush();
buf.setLength(0);
}
La comunicación termina cuando el servidor pregunta si el cliente quiere escuchar otra broma, si
el usuario dice no, el servidor dice "Bye.".
En el interés de una buena limpieza, el cliente cierra sus streams de entrada y salida y el
socket.
os.close();
is.close();
kkSocket.close();
Ejecutar los Programas
Primero se debe arrancar el programa servidor. Haz esto ejecutando el programa servidor
utilizando el intérprete de Java, como lo haría con cualquier otro programa. Recuerda que debes
ejecutarlo en la máquina que el programa cliente especifica cuando crea el socket.
Luego ejecutas el programa cliente. Observa que puedes ejecuarlo en cualquier máquina de tu
red, no tiene porque ejecutarse en la misma máquina que el servidor.
Si es demasiado rápido, podría arrancar el cliente antes de que el servidor tuviera la
oportunidad de incializarse y empezar a escuchar el puerto. Si esto sucede verás el siguiente
mensaje de error cuando intentes arrancar el programa cliente.
Exception: java.net.SocketException: Connection refused
Si esto sucede, intenta ejecutar el programa cliente de nuevo.
Verás el siguiente mensaje de error si se te olvidó cambiar el nombre del host en el código
fuente del programa KnockKnockClient.
Trying to connect to unknown host: java.net.UnknownHostException: taranis
Modifica el programa KnockKnockClient y proporciona un nombre de host válido en tu red.
Recompila el programa cliente e intentalo de nuevo.
Si intentas arrancar un segundo cliente mientras el primero está conectado al servidor, el
segundo colgará. La siguiente sección le cuenta como
soportar múltiples clientes.
Cuando obtengas una conexión entre el cliente y el servidor verás esto en tu pantalla.
Server: Knock! Knock!
Ahora, deberás responder con .
Who's there?
El cliente repite lo que has tecleado y envía el texto al servidor. El servidor responde con la
primera línea de uno de sus varias bromas Knock Knock de su repertorio. Ahora tu pantalla
debería contener esto (el texto que escribiste; está en negrita).
Server: Knock! Knock!
Who's there?
Client: Who's there?
Server: Turnip
Ahora deberías responderle con.
Turnip who?
De nuevo, el cliente repite lo que has tecleado y envía el texto al servidor. El servidor
responde con la línea graciosa. Ahora tu pantalla debería contener esto (el texto que
escribiste; está en negrita).
Server: Knock! Knock!
Who's there?
Client: Who's there?
Server: Turnip
Turnip who?
Client: Turnip who?
Server: Turnip the heat, it's cold in here! Want another? (y/n)
Si quieres oir otra borma teclea "y", si no, teclee "n". Si tecleas "y", el servidor empieza de
nuevo con "Knock! Knock!". Si tecleas "n" el servidor dice "Bye.", haciendo que tanto el cliente
como el servidor terminen.
Si en cualquier momento cometes un error al teclear, el objeto KKState lo captura, el servidor
responde con un mensaje similar a este, y empieza la broma otra vez.
Server: You're supposed to say "Who's there?"! Try again. Knock! Knock!
El objeto KKState es particular sobre la ortografía y la puntuación, pero no sobre las letras
mayúsculas y minúsculas.
Soportar Mútiples Clientes
El ejemplo KnockKnockServer fue diseñado para escuchar y manejar una sola petición de
conexión. Sin embargo, pueden recibirse varias peticiones sobre el mismo puerto y
consecuentemente sobre el mismo ServeSocket. Las peticiones de conexiones de clientes se
almecenan en el puerto, para que el servidor pueda aceptarlas de forma secuencial. Sin embargo,
puede servirlas simultaneamente a través del uso de threads -- un thread para procesar cada
conexión de cliente.
El flujo lógico básico en este servidor sería como este.
while (true) {
aceptar un a conexión;
crear un thread para tratar a cada cliente;
end while
El thread lee y escribe en la conexión del cliente cuando sea necesario.
Intenta esto: Modifica el KnockKnockServer para que pueda servir a varios
clientes al mismo tiempo. Aquí tienes nuestra solución, que está compuesta en dos clases.
KKMultiServer y
KKMultiServerThread.
KKMultiServer hace un bucle continúo escuchando peticiones de conexión desde los clientes en un
ServerSocket. Cuando llega una petición KKMultiServer la accepta, crea un objeto
KKMultiServerThread para procesarlo, manejando el socket devuelto por
accept(), y arranca el thread. Luego el servidor vuelve a escuchar en el
puerto las peticiones de conexión. El objeto KKMultiServerThread comunica con el cliente con el
que está leyendo y escribiendo a través del socket. Ejecute el nuevo servidor Knock Knock y
luego ejecuite varios clientes sucesivamente.