- Introducción a la programación de red
1.1 Conceptos básicos de redes Una red informática consiste en múltiples computadoras con funciones independientes, ubicadas en diferentes ubicaciones geográficas, conectadas mediante líneas de comunicación. Estas computadoras operan bajo sistemas operativos de red, software de gestión y protocolos de comunicación que permiten compartir recursos e intercambiar información.
La programación de red implica la transferencia de datos entre programas ejecutándose en diferentes máquinas, bajo un protocolo de comunicación específico.
1.2 Elementos clave en la programación de red
- Dirección IP: Identifica de forma única cada dispositivo en una red. Es el número asignado para localizar y comunicarse con una máquina específica.
- Puerto: Identifica un proceso o servicio específico dentro de un dispositivo. Aunque la IP localiza la máquina, el puerto determina cuál aplicación recibe los datos.
- Protocolo: Define las reglas que deben seguirse durante la comunicación. Los más comunes son TCP y UDP, que establecen cómo se estructuran, transmiten y verifican los datos.
1.3 Direcciones IP Las direcciones IP son identificadores únicos para dispositivos en una red.
- IPv4: Utiliza 32 bits (4 bytes), representados comúnmente en formato decimal con puntos (por ejemplo, 192.168.1.66).
- IPv6: Usa 128 bits, divididos en 8 grupos hexadecimales, ampliando significativamente el espacio de direcciones disponibles.
Comandos útiles en línea de comandos:
ipconfig: muestra la dirección IP del equipo local.ping <dirección_ip>: verifica si hay conectividad con otro dispositivo.
Direcciones especiales:
127.0.0.1: dirección de bucle (loopback), representa al propio equipo y se usa para pruebas.
1.4 Clase InetAddress Representa una dirección IP en Internet.
| Método | Descripción |
|---|---|
| static InetAddress getByName(String host) | Obtiene la dirección IP a partir de un nombre de host o una IP directa. |
| String getHostName() | Devuelve el nombre del host asociado a la IP. |
| String getHostAddress() | Retorna la dirección IP como cadena de texto. |
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getByName("192.168.1.66");
System.out.println("Nombre del host: " + address.getHostName());
System.out.println("Dirección IP: " + address.getHostAddress());
}
}
1.5 Puertos y protocolos
- Puerto: Entero de 16 bits (0–65535). Los puertos 0–1023 están reservados para servicios conocidos. Se recomienda usar puertos superiores a 1024 para aplicaciones personalizadas.
- UDP: Protocolo sin conexión. No garantiza entrega ni orden de paquetes. Ideal para transmisiones en tiempo real como audio y video.
- TCP: Protocolo orientado a conexión. Requiere tres handshakes para establecer una sesión segura. Garantiza integridad y orden de datos. Usado en transferencias de archivos, navegación web, etc.
- Comunicación UDP
2.1 Enviar datos por UDP En Java, se utiliza DatagramSocket para crear un socket UDP y DatagramPacket para encapsular los datos.
| Método | Descripción |
|---|---|
| DatagramSocket() | Crea un socket vinculado a cualquier puerto disponible en el equipo local. |
| DatagramPacket(byte[] buf, int len, InetAddress addr, int port) | Construye un paquete con datos de longitud len dirigido al addr en el port. |
| send(DatagramPacket p) | Envía el paquete. |
| close() | Libera el socket. |
Pasos:
- Crear
DatagramSocket. - Crear datos y empaquetarlos.
- Enviar el paquete.
- Cerrar el socket.
public class SendDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] data = "Hola, UDP, aquí llego".getBytes();
DatagramPacket dp = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 10086);
ds.send(dp);
ds.close();
}
}
2.2 Recibir datos por UDP Pasos:
- Crear
DatagramSocketen un puerto específico. - Crear un buffer para recibir datos.
- Esperar datos mediante
receive(). - Extraer y mostrar el contenido.
- Cerrar el socket.
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(12345);
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp);
System.out.println("Datos recibidos: " + new String(dp.getData(), 0, dp.getLength()));
ds.close();
}
}
2.3 Ejercicio: Envío y recepción continua Enviar desde teclado hasta que se ingrese "886". El receptor debe estar en un bucle infinito.
// Cliente
public class SendDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while(true) {
String input = sc.nextLine();
if ("886".equals(input)) break;
byte[] data = input.getBytes();
DatagramPacket dp = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.1.66"), 12345);
ds.send(dp);
}
ds.close();
}
}
// Servidor
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(12345);
while(true) {
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp);
System.out.println("Recibido: " + new String(dp.getData(), 0, dp.getLength()));
}
}
}
2.4 Tipos de comunicación UDP
- Unicast: Comunicación punto a punto entre dos dispositivos.
- Multicast: Envío a un grupo específico de dispositivos.
- Broadcast: Envío a todos los dispositivos en la red local.
2.5 Multicast con UDP
// Emisor
public class ClientDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] data = "Hola multicast".getBytes();
InetAddress group = InetAddress.getByName("224.0.1.0");
DatagramPacket dp = new DatagramPacket(data, data.length, group, 10000);
ds.send(dp);
ds.close();
}
}
// Receptor
public class ServerDemo {
public static void main(String[] args) throws IOException {
MulticastSocket ms = new MulticastSocket(10000);
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ms.receive(dp);
System.out.println(new String(dp.getData(), 0, dp.getLength()));
ms.close();
}
}
2.6 Broadcast con UDP
// Emisor
public class ClientDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] data = "Broadcast hello".getBytes();
InetAddress broadcast = InetAddress.getByName("255.255.255.255");
DatagramPacket dp = new DatagramPacket(data, data.length, broadcast, 10000);
ds.send(dp);
ds.close();
}
}
// Receptor
public class ServerDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10000);
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp);
System.out.println(new String(dp.getData(), 0, dp.getLength()));
ds.close();
}
}
- Comuniccaión TCP
3.1 Enviar datos por TCP Java proporciona Socket para clientes y ServerSocket para servidores.
| Método | Descripción |
|---|---|
| Socket(String host, int port) | Crea un socket y se conecta al servidor. |
| getOutputStream() | Obtiene el flujo de salida para enviar datos. |
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
OutputStream os = socket.getOutputStream();
os.write("Hola, TCP, aquí vengo".getBytes());
socket.close();
}
}
3.2 Recibir datos por TCP El servidor espera conexiones con accept(), que es bloqueante.
| Método | Descripción |
|---|---|
| ServerSocket(int port) | Asigna un puerto al servidor. |
| accept() | Espera y acepta una conexión entrante. |
| getInputStream() | Obtiene el flujo de entrada para leer datos. |
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
InputStream is = s.getInputStream();
byte[] buffer = new byte[1024];
int len = is.read(buffer);
System.out.println("Datos: " + new String(buffer, 0, len));
s.close();
ss.close();
}
}
3.3 Ejercicio: Cliente-servidor con retroalimentación Cliente envía "hello", servidor responde "¿Quién eres?".
// Cliente
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
socket.shutdownOutput(); // Marca fin de escritura
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
socket.close();
}
}
// Servidor
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
InputStream is = s.getInputStream();
int b;
while ((b = is.read()) != -1) {
System.out.print((char) b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write("¿Quién eres?");
bw.newLine();
bw.flush();
bw.close();
s.close();
ss.close();
}
}
3.4 Transferencia de archivos con TCP Cliente envía un archivo, servidor lo guarda y responde.
// Cliente
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("ClientDir/1.jpg"));
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bos.flush();
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
bis.close();
socket.close();
}
}
// Servidor
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket s = ss.accept();
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("ServerDir/copy.jpg"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw.write("Transferencia exitosa");
bw.newLine();
bw.flush();
bos.close();
s.close();
ss.close();
}
}
3.5 Opitmización del servidor TCP
- Iteración: Permitir múltiples conexiones usando un bucle
while(true). - Nombres de archivo únicos: Usar
UUID.randomUUID()para evitar sobrescrituras. - Hilos: Procesar cada cliente en un hilo separado.
- Pool de hilos: Usar
ThreadPoolExecutorpara gestionar eficientemente recursos.
// Pool de hilos
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
while (true) {
Socket accept = ss.accept();
ThreadSocket task = new ThreadSocket(accept);
pool.submit(task);
}
}
}
- Programación no bloqueante (NIO)
4.1 Conceptos gneerales
- BIO: Entrada/salida bloqueante. El hilo espera hasta que se complete la operación.
- NIO: Entrada/salida no bloqueante. Permite realizar otras tareas mientras se espera.
4.2 Diferencias entre BIO y NIO
- BIO: Bloqueante, orientado a flujos.
- NIO: No bloqueante, orientado a búferes (buffers) bidireccionales.
4.3 Componentes principales de NIO
- Búferes: Almacenan datos temporalmente.
- Canal: Establece conexión y transfiere datos.
- Selector: Monitorea múltiples canales simultáneamente.
4.4 Crear búferes
ByteBuffer buffer = ByteBuffer.allocate(10); // Búfer vacío
ByteBuffer wrap = ByteBuffer.wrap("abc".getBytes()); // Búfer con datos
4.5 Agregar datos al búfer Métodos: put(byte), put(byte[]). Modifican el índice position.
4.6 Leer datos del búfer
| Método | Descripción |
|---|---|
| flip() | Cambia el modo de escritura a lectura. |
| get() | Lee un byte. |
| get(byte[] dst) | Lee varios bytes. |
| rewind() | Reinicia el índice a 0. |
| clear() | Prepara el búfer para nueva escritura. |
| array() | Convierte el búfer a array. |
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("abc".getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
4.7 Resumen
- Para escribir datos, el búfer está en modo de lectura (después de
flip()). - Para leer, se usa
flip()antes de comenzar. clear()prepara el búfer para escribir nuevamente.capacity: tamaño total del búfer.limit: máximo índice accesible.position: índice actual de lectura/escritura.