Basado en cuatro capas
Cada capa representa un nivel de abstracción
Cada capa se comunica con su homóloga en otro nodo distinto, utilizando un protocolo como lenguaje.
Sin embargo, la comunicación entre capas homólogas no es directa.
Cada capa solicita a su capa inferior que se envíe el mensaje al destino.
Cada capa extiende el mensaje a enviar con información propia que le permite realizar su cometido
Finalmente, la capa de enlace es la que realiza el envío físico
El nodo receptor recibe el mensaje a través de la capa de enlace.
Cada capa interpreta la información adicional introducida por su capa homóloga durante el envío, y acaba remitiendo el mensaje original (sin esta información adicional) a la capa superior.
Finalmente, la capa de aplicación recibe el mensaje tal y como lo envió la capa de aplicación en el origen.
La capa de enlace de datos proporciona una vía de transmisión de paquetes de datos entre dos nodos enlazados directamente
La capa de red gestiona el envío de mensajes a través de múltiples nodos intermedios.
Cada nodo está identificado mediante una dirección IP
Las capas de red de los nodos intervinientes enrutan los paquetes desde el origen hasta el destino
La capa de transporte se encarga de la transmisión sin errores desde origen a destino.
Segmentación en paquetes, recepción en orden correcto...
Protocolos:
La capa de transporte permite varios canales de comunicación entre nodos.
Cada canal está identificado por un número de puerto.
Existen puertos TCP y puertos UDP.
Supongamos que dos nodos quieren conectarse para intercambiar información
¿Cómo y cuándo se establece esta conexión?
En el modelo cliente/servidor cada nodo adquiere un rol:
El sistema operativo proporciona un mecanismo de acceso a la capa de transporte.
Este mecanismo se basa en el uso de sockets.
Supongamos que un servidor está escuchando en un determinado puerto y que un cliente se conecta al mismo:
Esto crea un canal de comunicación bidireccional entre cliente y servidor.
Cada socket tiene asociados dos flujos:
Implementamos en Java un sencillo servidor que realiza la suma de dos números.
El cliente envía al servidor una cadena de texto con dos números enteros.
El servidor responde con la suma de dichos números.
public class ArithServer {
// Número de puerto
private static final int PORT_NUMBER = 5432;
public static void main(String... args) {
try {
ServerSocket server = new ServerSocket(PORT_NUMBER);
Socket s = server.accept();
servePetition(s); // Implementado más adelante
} catch (IOException e) {
...
}
}
private static void servePetition(Socket s) { ... }
}
ServerSocket server = new ServerSocket(PORT_NUMBER);
Crea un objeto ServerSocket
, que permite recibir conexiones a través del puerto dado.
Socket s = server.accept();
Queda a la espera de que se conecte un cliente.
El método accept()
detiene la ejecución del programa hasta que reciba la conexión de un cliente.
El objeto de la clase Socket
es el que permite acceder a los flujos de entrada y salida del socket:
public InputStream getInputStream(); public OutputStream getOutputStream();
servePetition
recibe la petición y envía la respuesta a través de los flujos del socket.
private static void servePetition(Socket socket) {
try (
Scanner sc = new Scanner(socket.getInputStream());
PrintWriter out =
new PrintWriter(
new OutputStreamWriter(socket.getOutputStream())
)) {
// Leer sumandos a partir del flujo de entrada
int sum1 = sc.nextInt();
int sum2 = sc.nextInt();
// Escribir en el flujo de salida
out.println(sum1 + sum2);
} catch (IOException e) { ... }
}
Más información: Java try-with-resources
public static void main(String... args) {
try {
Socket s = new Socket("localhost", PORT_NUMBER);
// Enviar información al servidor y recibir respuesta
int res = add(s, 21, 45);
System.out.println("21 + 45 = " + res);
} catch (IOException e) {
System.err.println("Error de conexión: " + e.getMessage());
}
}
La conexión a un servidor se realiza creando directamente una instancia de la clase Socket
, indicando el servidor al que conectarse
(localhost = propia máquina) y el puerto.
add()
es la que envía la petición al servidor:
private static int add(Socket s, int n1, int n2) {
try (PrintWriter out = new PrintWriter(
new OutputStreamWriter(s.getOutputStream()));
Scanner sc = new Scanner(s.getInputStream())) {
// Enviar sumandos al servidor
out.print(n1);
out.print(" ");
out.println(n2);
out.flush();
// Recibir resultado
return sc.nextInt();
} catch (IOException e) {
System.err.println("Error de E/S: " + e.getMessage());
return -1;
}
}
Para ejecutar este ejemplo:
main()
del servidor.main()
del cliente, obteniendo como salida:
21 + 45 = 66
ServerSocket server = new ServerSocket(PORT_NUMBER);
while (true) {
Socket s = server.accept();
servePetition(s);
}
Modificamos el servidor para que pueda realizar otro tipo de operaciones aritméticas.
El servidor recibirá mensajes de la forma:
ADD n1 n2
SUB n1 n2
MUL n1 n2
DIV n1 n2
SQRT n1
private static void servePetition(Socket socket) {
try (Scanner sc = new Scanner(socket.getInputStream());
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream())
)) {
String op = sc.next();
switch (op) {
case "ADD" : serveAddition(sc, out); break;
case "SUB" : serveSubstraction(sc, out); break;
case "MUL" : serveMultiplication(sc, out); break;
case "DIV" : serveDivision(sc, out); break;
case "SQRT" : serveSquareRoot(sc, out); break;
default : out.println("ERROR");
}
} catch (IOException e) {
System.err.println("Error al obtener el flujo de entrada: " + e.getMessage());
}
}
private static void serveDivision(Scanner sc, PrintWriter out) {
int n1 = sc.nextInt();
int n2 = sc.nextInt();
if (n2 != 0) {
System.out.println("Dividiendo " + n1 + " entre " + n2);
out.println((double)n1 / n2);
} else {
out.println("ERROR");
}
}
public static void main(String... args) throws IOException {
Socket s = new Socket("localhost", 5432);
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(s.getOutputStream()));
bw.write("MUL 4 12\n");
bw.flush();
BufferedReader br = new BufferedReader(
new InputStreamReader(s.getInputStream()));
System.out.println(br.readLine());
bw.close();
br.close();
}
Resultado:
48
Petición | Respuesta |
---|---|
ADD n1 n2 |
n1 + n2 |
SUB n1 n2 |
n1 - n2 |
MUL n1 n2 |
n1 * n2 |
DIV n1 n2 |
n1 / n2 (si n1 ≠ 0)
ERROR (e.o.c.)
|
SQRT n |
√n (si n ≥ 0)
ERROR (e.o.c.)
|
Hemos implementado la capa de aplicación de la calculadora.
public static void main(String... args) throws IOException {
Socket s = new Socket("informatica.ucm.es", 80);
PrintWriter pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
// Petición
pw.println("GET / HTTP/1.0");
pw.println("Host: informatica.ucm.es\n");
pw.flush();
// Respuesta
Scanner sc = new Scanner(s.getInputStream());
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
}
HTTP/1.1 200 OK Date: Wed, 10 Aug 2016 16:40:14 GMT Server: Apache Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: no-cache Content-Type: text/html; charset=UTF-8 Set-Cookie: ucmweb=7rbkr7h03pld7rkugaod7u28a6; path=/; domain=ucm.es Connection: close <!DOCTYPE html> <html lang="es"> <head> <title>Facultad de Informática. Universidad Complutense de ...
Las aplicaciones web también utilizan el modelo cliente-servidor:
Tiene el siguiente formato:
[método] [recurso] [versión_HTTP]
[método]
: Acción a realizar (GET, POST, HEAD, PUT, ...)[recurso]
: Nombre del recurso (URI) sobre el que se accederá.[versión_HTTP]
: HTTP/1.0 o HTTP/1.1.Ejemplo:
GET /index.html HTTP/1.1
GET
: Obtener un recurso del servidor.HEAD
: Igual que GET, pero el servidor no devolverá el recurso; sólo las cabeceras de la respuesta.POST
: Enviar información al servidor.PUT
: Guardar información en el servidor.DELETE
: Eliminar información del servidor.OPTIONS
: Obtener la lista de métodos soportados por el servidor.[método] [recurso] [versión_HTTP]
El nombre de recurso tiene la aparencia de un nombre de fichero, pero no tiene por qué corresponder con un fichero físico almacenado en el servidor
Un servidor web puede hace corresponder las URIs con llamadas a programas (por ejemplo, servlets), accesos a recursos físicos almacenados con un nombre distinto, etc.
Especifican información adicional en una petición
Host: www.ucm.es
Accept: text/html, image/gif, */*
Accept-Language: en-US
Accept-Charset: UTF-8
Accept-Encoding: x-gzip
User-Agent: Mozilla/5.0 ...
Content-Type: text/html
Authorization: Basic bWFudWVsOm1hbnVlbA==
If-Modified-Since: Fri, 26 Feb 2016 03:09:05 GMT
Indica la versión del protocolo HTTP utilizada, un código de estado y un texto con la descripción dicho código.
200 OK
: Petición recibida y servida con éxito.3xx
: Redirección: el recurso ha cambiado de dirección.
301 Move Permanently
302 Move Temporarily
304 Not modified
En los códigos 301 y 302 se indica en la cabecera de la respuesta la nueva dirección del recurso. El código 304 se devuelve en el caso en el que el cliente haya incluido
If-Modified-Since
y el recurso no haya cambiado desde la fecha indicada.
4xx
: Errores atribuibles al cliente.400 Bad Request
: Petición incorrecta.401 Authentication Required
: Se requiere identificación (nombre y contraseña).403 Forbidden
: Identificación incorrecta.404 Not Found
: Recurso no encontrado en el servidor.405 Method not Allowed
: Acción (GET, POST, etc.) no permitida.5xx
: Errores atribuibles al servidor.500 Internal Server Error
503 Service Unavailable
Tal y como indica la cabecera Content-Type: text/html
, el cuerpo de la respuesta contiene un texto en formato HTML que define la estructura de la página web recuperada.