Title: Tema II: Programaci
1Tema IIProgramación con sockets
2Tema II Contenidos
2.1 Introducción 2.2 Streams en Java 2.3
Sockets TCP 2.4 Sockets UDP 2.5 Sockets con
entrada/salida asíncrona en Java
3Lección 2.1 Introducción
2.1 Introducción 2.2 Streams en Java 2.3
Sockets TCP 2.4 Sockets UDP 2.5 Sockets con
entrada/salida asíncrona en Java
4La API de sockets
- La API de sockets es un mecanismo que permite
acceder a los servicios IPC proporcionados por
los protocolos TCP y UDP - Los sockets están en el nivel de abstracción del
modelo de paso de mensajes - Casi nadie desarrolla directamente sobre sockets
hoy en día - pero todas las APIs de mayor abstracción se
basan en sockets - Es importante comprender los detalles de la API
de sockets - La API de sockets está disponible en la inmensa
mayoría de los sistemas - En ocasiones forma parte del núcleo del sistema
operativo (i.e. Unix) - En ocasiones es una librería externa (i.e. API
Winsock de Windows) - Traducción de socket
- enchufe, toma eléctrica
- La metáfora es que el socket es un punto en el
proceso (toma) al que otros procesos pueden
enchufar un cable - El cable representa una conexión o un flujo de
datos
5La metáfora del socket
Proceso A
Proceso B
6La metáfora del socket
Proceso A
Proceso B
CREATE SOCKET
CREATE SOCKET
7La metáfora del socket
Proceso A
Proceso B
PLUG SOCKET
PLUG SOCKET
8La metáfora del socket
Proceso A
Proceso B
SEND DATA
RECEIVE DATA
9La metáfora del socket
Proceso A
Proceso B
UNPLUG
UNPLUG
10Lección 2.2 Streams en Java
2.1 Introducción 2.2 Streams en Java 2.3
Sockets TCP 2.4 Sockets UDP 2.5 Sockets con
entrada/salida asíncrona en Java
11Streams
- Traducción de Stream
- Corriente, chorro, flujo
- El stream es una secuencia ordenada de unidades
de información (bytes, caracteres, etc) que puede
fluir en dos direcciones hacia fuera de un
proceso (de salida) o hacia dentro de un proceso
(de entrada) - Los streams están diseñados para acceder a los
datos de manera secuencial - Los streams no suelen estar preparados para
realizar operaciones no secuenciales (volver
hacia atrás, saltarse datos, realizar acceso
aleatorio a datos, etc.) - Los streams, en principio, van recorriendo una
secuencia de datos (de entrada o de salida) y al
mismo tiempo, la van consumiendo. Es decir, un
dato leido/escrito no puede ser
desleido/desecrito (nota existen excepciones a
esta regla) - Pueden existir streams especializados en la
lectura/escritura de determinados tipos de datos
(bytes, enteros, flotantes, caracteres, objetos,
etc.)
12Funcionamiento básico de un stream
Proceso
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
13Funcionamiento básico de un stream
OPEN STREAM
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
14Funcionamiento básico de un stream
READ BYTE
E
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
15Funcionamiento básico de un stream
READ BYTE
El
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
16Funcionamiento básico de un stream
READ BYTE
El_
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
17Funcionamiento básico de un stream
READ BYTE
El_g
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
18Funcionamiento básico de un stream
READ BYTE
El_ga
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
19Funcionamiento básico de un stream
CLOSE STREAM
El_ga
E l g a t o e s p e r ó u n r a t o , p e
Secuencia de bytes almacenados en un fichero
20Streams, Readers y Writers en Java
- En informática, existen dos mecanismos
fundamentales para almacenar datos - En formato texto
- Utiliza el carácter como unidad de almacenamiento
- El carácter es la representación de un símbolo
legible por un humano - En formato binario
- Utiliza el byte como unidad de almacenamiento
- Este formato no es, en general, legible por
humanos - En Java, existen clases especialistas para
leer/escribir datos en estos formatos - Lectura de streams de texto Reader
- Escritura en steams de texto Writer
- Lectura de streams binarios InputStream
- Escritura en streams binarios OutputStream
- Las cuatro clases mencionadas son abstractas (no
se pueden instanciar directamente objetos de esa
clase) - Cada una de esas cuatro clases actúa como padre
de una jerarquía de herencia - En la citada jerarquía se han definido clases
concretas especialistas en problemas concretos
de entrada/salida de datos - Todas las clases de la jerarquía forman parte del
paquete java.io de la API estándar
21Jerarquía de herencia asociada a InputStream
22Jerarquía de herencia asociada a OutputStream
23Jerarquía de herencia asociada a Reader
24Jerarquía de herencia asociada a Writer
25Leyendo y escribiendo datos binarios con Streams
package es.urjc import java.io. public class
BasicDataStreams //Los datos que vamos a
escribir en un fichero private static byte
primes 1, 2, 3, 5, 7, 11, 13 public
static void readDataFile(String filename) throws
IOException FileInputStream fis new
FileInputStream(filename) int prime while(
(prime fis.read()) ! -1) System.out.println(
"He leido " (byte)prime) fis.close() p
ublic static void writeDataFile(String filename)
throws IOException File file new
File(filename) FileOutputStream fos new
FileOutputStream(file) for(int i 0 i lt
primes.length i) fos.write(primesi) f
os.flush() fos.close() ...
26Leyendo y escribiendo datos binarios con Streams
package es.urjc import java.io. public class
BasicDataStreams ... public static void
main(String args) String filename
"primes.dat" if(args.length 1) filename
args0 try writeDataFile(filename)
catch(IOException ioe) System.err.println("
No se ha podido escribir en el fichero "
filename) try readDataFile(filename)
catch(IOException ioe) System.err.println(
"No se ha podido escribir en el fichero "
filename)
27Conectando streams para realizar entrada/salida
- Cada tipo de stream es especialista en un tipo de
entrada/salida - Por ejemplo
- FileInputSteam especialista en leer bytes de un
fichero - DataInputStream especialista en leer datos
complejos (enteros, flotantes, booleanos, etc.)
de un stream de bytes - Los streams se pueden conectar para combinar
sus capacidades
Especialista en leer datos complejos de streams
de bytes
Fichero en disco
Especialista en leer bytes de ficheros
Datos leídos
0101 1001 1101
1,234 0,2345 2,23E-12
doubles
bytes
Hw
... FileInputStream fis new FileInputStream("C\
\nombre\\fichero.dat") DataInputStream dis new
DataInputStream(fis) dis.readInt() dis.readFloat
() dis.readDouble() dis.close() ...
28Serialización y streams de objetos
- Los DataStreams son especialistas en algunos
datos complejos (int, double, etc.) - Los ObjectStreams son especialistas en
leer/escribir objetos en/desde streams - Los ObjectStreams se basan en el mecanismo de la
serialización - En Java, la serialización es el proceso por el
que un objeto se transforma en una secuencia de
bytes que lo representa - La deserialización es proceso por el que se
reconstruye un objeto previamente serializado a
partir de una secuencia de bytes - En Java, para que un objeto sea serializable,
debe ser instancia de una clase que implemente la
interfaz Serializable, esta interfaz no define
ningún método
class MiClase implements Serializable
//atributos y métodos ... MiClase miObjeto
new MiClase()
Objeto en memoria
Objeto recuperado en memoria
Serialización
Deserialización
value 14 name Hello p0.23
value 14 name Hello p0.23
Objeto serializado
001010100010101
29ObjectIntputStream y ObjectOutputStream
- ObjectOutputStream
- Especialista en transformar objetos en una
secuencia de bytes (en serializar objetos) - Debe estar conectado a un OutputStream que se
especifica en construcción - Ej ObjectOutputStream oos new
ObjectOutputStream(outputStream) - La serialización se realiza invocando el método
void writeObject (Object obj) - ObjectInputStream
- Especialista en recuperar objetos desde una
secuencia de bytes (en recuperar objetos
previamente serializados) - Debe estar conectado a un InputStream que se
especifica en construcción - Ej ObjectInputStream ois new
ObjectInputStream(inputStream) - La deserialización se logra invocando el método
Object readObject()
Ejemplo de clase serializable
import java.util.ArrayList public class Persona
implements Serializable public String
nombre public int age public
ArrayListltPersonagt friends
30Ejemplo de uso de Object/Input/Output/Streams
import java.io. public class BasicObjectStreams
private static void writePersona(String
filename) Persona p new Persona() p.name
"Luis" p.age 29 p.friends
null try ObjectOutputStream oos new
ObjectOutputStream(new FileOutputStream(file
name)) oos.writeObject(p) oos.close()
catch(IOException ioe ) System.exit(-1)
private static Persona readPersona(String
filename) try ObjectInputStream ois new
ObjectInputStream(new FileInputStream(filena
me)) return (Persona)ois.readObject()
//Habría que cerrar con close()
catch(IOException ioe) System.err.println(ioe
.getMessage()) catch (ClassNotFoundException
cnfe) cnfe.printStackTrace() return
null ...
31Ejemplo de uso de Object/Input/Output/Streams
import java.io. public class
BasicObjectStreams ... public static void
main(String args) String filename
"persona.dat" if(args.length 1) filename
args0 writePersona(filename) Persona p
readPersona(filename) System.out.println("p.n
ame" p.name " -- p.age" p.age)
javac BasicObjectStreams.java java
BasicObjectStream p.nameLuis -- p.age29 ls
... persona.dat
32Qué se serializa?
- Los objetos que sean instancias de clases
Serializable se pueden serializar - Para que un atributo no se serialice, debe ser
marcado como transient - Multitud de atributos no pueden ser serializados
y deben ser marcados como transient (por ejemplo,
un FileInputStream) - Se serializan todos los atributos que no sean
transient ni static - Si un atributo es una referencia a un objeto, el
objeto se serializa - Por tanto, la serialización de un objeto con
referencias crea un árbol de serialización - Al deserializar el objeto, se recupera el árbol
completo de objetos - Si hay varias referencias a un objeto, los datos
del mismo se serializan solo una vez
Objeto A
Objeto B
Objeto A
Objeto B
Objeto D
Objeto D
transient
Serialización Objeto A
Deserialización Objeto A
001010110
Objeto C
Objeto E
Objeto E
33Readers, writers y charsets
- La entrada/salida de caracteres de texto es
compleja debido a la gran cantidad de formatos de
codificación que existen (ASCII, UTF-8, Unicode,
etc.) - Hacer entrada/salida de texto directamente con
streams es complejo - Los Readers y los Writers simplifican enormemente
la tarea - Como en el caso de los streams, existen clases
especialistas en tareas concretas - Para entenderlas hay que comprender cómo Java
procesa y representa caracteres - En Java, los caracteres se representan
internamente en formato Unicode de 16 bits - Cuando se lee un carácter, este se convierte de
su formato nativo a Unicode - Cuando se escribe un carácter, este se convierte
de Unicode a su formato objetivo - En Java, al formato nativo/objetivo de una cadena
se le denomina su charset - Desde Java 1.5 existe la clase Charset (en el
paquete java.nio.charset) que permite operar
sobre la representación de caracteres - En Java, se soportan múltiples charsets de
entrata/salida, entre los que se incluyen
US-ASCII, ISO-8859-1, UTF-8, UTF-16BE, UTF-816LE,
etc. - Cada instancia de la máquina virtual tiene un
charset por defecto que depende del formato
utilizado por el sistema operativo subyacente - El charset por defecto se puede averiguar
mediante el método estático defaultCharset() de
la clase Charset
34Algunos Readers y Writers
- Vamos a ver algunos Readers y Writers básicos y
en qué se especializan - InputStreamReader
- Hace de puente entre streams de bytes y de
caracteres - Debe estar conectado con un InputStream que se
especifica en construcción - Tiene constructores que permiten especificar un
Charset de preferencia - Los constructores que no toman un Charset, usan
el charset por defecto - Puede leer caracteres de uno en uno utilizando el
método int read() - OutputStreamWriter
- Equivalente al InputStreamReader pero para salida
de caractéres - Escribe caractéres usando el método write(int c)
Hijos de InputStream
chars
bytes
Hijos de OutputStream
chars
bytes
35Algunos Readers y Writers
- BufferedReader
- Permite leer texto de forma eficiente a través de
un buffer intermedio - Debe estar conectado con un Reader que se
especifica en construcción - Se utiliza el Charset que posea ese Reader
- Puede leer líneas completas utilizando el método
String readLine() - PrintWriter
- Imprime texto formateado
- Puede estar conectado a un Writer
- Puede estar conectado a un OutputStream
- Puede volcar directamente sobre un fichero
- Existen constructores para cada uno de los casos
- Se pueden imprimir líneas completas usando el
método println(String line)
InputStreamReader isr new InputStreamReader(new
FileOutputStream(C\\file.txt) BufferedReader
br new BufferedReader(isr) String line
br.readLine() ... PrintWriter pw new
PrintWriter(C\\file.txt) pw.println(Hola
mundo) ...
36Lección 2.3 Sockets TCP
2.1 Introducción 2.2 Streams en Java 2.3
Sockets TCP 2.4 Sockets UDP 2.5 Sockets con
entrada/salida asíncrona en Java
37La API de sockets en modo stream (TCP sockets)
- La API de sockets en modo stream proporciona
acceso al servicio de transporte de datos
proporcionado por el protocolo TCP - El servicio TCP es orientado a conexión
proporciona un transporte fiable - La conexión se establece
- Un flujo de bytes sale del emisor
- El mismo flujo de bytes es recibido en el
receptor - El servicio proporcionado por el socket es
full-duplex (el mismo socket puede ser utilizado
para enviar y para recibir datos) - Para que la conexión se pueda establecer,
- Uno de los sockets debe estar a la espera de
recibir conexiones - El otro socket debe iniciar una conexión
- Para que una conexión se libere (cierre)
- Basta con que uno de los sockets desee cerrarla
- Un socket recién creado no está conectado
- Vamos a estudiar en detalle el funcionamiento de
los sockets TCP utilizando la funcionalidad
proporcionada por la API estándar de Java
38Tipos de stream socket en Java
- En Java hay dos tipos de stream sockets que
tienen asociadas sendas clases - ServerSocket
- Proporciona un socket dispuesto a aceptar
conexiones TCP de forma pasiva - Para simplificar, lo denominaremos socket de
conexión - Al proceso que posee un ServerSocket se le
denomina también servidor - Socket
- Proporciona un socket por el que se pueden
intercambiar datos con otro socket - Para simplificar, lo denominaremos socket de
datos - Al proceso que inicia una conexión desde un
Socket se le denomina cliente - El proceso a seguir para lograr el intercambio de
datos es - El servidor crea el ServerSocket y lo asocia a un
puerto - El cliente crea un Socket
- El cliente ordena a su Socket que se conecte con
el ServerSocket remoto - Al recibir la petición, el servidor crea un nuevo
Socket y lo connecta con el remoto - Se intercambian los datos
- La conexión se cierra cuando alguno de los dos
sockets lo indica
39La clase ServerSocket
- La clase ServerSocket forma parte del paquete
java.net de la API estándar - Constructores
- ServerSocket() Crea un nuevo socket de conexión
no atado a ningún puerto - ServerSocket(int port) Crea un nuevo socket de
conexión y lo ata al puerto especificado. En
número de puerto debe estar comprendido entre 0 y
65535 inclusive. Si port es 0, se toma un puerto
libre al azar - ServerSocket(int port, int backlog) Permite
además especificar el tamaño de la cola de
solicitudes de conexión no atendidas. Si el
número de solicitudes no atendidas supera el
tamaño especificado se rechazán solicitudes de
conexión - ServerSocket(int port, int backlog, InetAddress
bindAddr) Permite además especificar la
dirección IP en la que se atenderán las
solicitudes de conexión (para ordenadores que
dispongan de varias). Si bindAddr es null, el
ServerSocket atenderá solicitudes de conexión en
todas las interfaces de la máquina
40La clase ServerSocket Cont.
Algunos Métodos Socket accept() Escucha una
solicitud de conexión y la acepta cuando se
recibe. En ese momento, se crea nuevo Socket que
es el que realmente se conecta con el cliente.
Tras esto, el ServerSocket sigue disponible para
realizar nuevos accept() void bind(SocketAddress
endpoint) Ata al ServerSocket a la dirección
especificada (dirección IP y puerto). El
ServerSocket no debe estar atado previamente int
getLocalPort() Devuelve el puerto al que está
atado el ServerSocket void close() Cierra el
ServerSocket
... ServerSocket serverSocket new
ServerSocket(0) System.out.println(El puerto es
serverSocket.getLocalPort()) Socket
connection serverSocket.accept() //hacemos
cosas con connection Socket otherConnection
serverSocket.accept() //hacemos cosas con
otherConnection serverSocket.close() ...
41La clase Socket
- La clase Socket forma parte del paquete java.net
de la API estándar - Algunos Constructores
- Socket() Crea un nuevo socket sin conectar
- Socket(InetAddress address, int port) Crea un
nuevo socket y lo conecta automáticamente con un
socket remoto en la dirección IP y puertos
especificados - Socket(InetAddress address, int port, InetAddress
localAddr, int localPort) Permite además
especificar la dirección IP local y el puerto
local a los que se asociará el socket - Socket(String host, int port) El host remoto se
puede especificar a través de su nombre de
dominio. Se utilizarán los mecanismos de
resolución del sistema operativo subyacente - Socket(Proxy proxy) Se crea un socket no
conectado especificando el tipo de proxy que se
desea usar
42La clase Socket Cont.
Algunos Métodos void connect(SocketAddress
endpoint) Conecta este socket al servidor
especificado (dirección IP y puerto) InetAddress
getInetAddress() Devuelve la dirección (IP y
puerto) a la que este socket se encuentra
conectado InputStream getInputStream() Devuelve
un InputStream que permite leer bytes del socket
(recibir datos enviados desde el socket remoto)
utilizando los mecanismos habituales de los
streams. El socket debe estar conectado OutputStre
am getOutputStream() Devuelve un OutputStream
que permite escribir bytes sobre el socket
(enviar bytes al socket remoto) utilizando los
mecanismos habituales de los streams. El socket
debe estar conectado void setKeepAlive() Al
activarlo, se chequea que la conexión entre dos
sockets sigue activa enviando paquetes basura
periódicamente (RFC 1122) void setSoLinger(boolean
on, int linger) Al activarlo, si hay datos
pendientes de enviar cuando se invoca close(), el
llamante se bloquea (durante un máximo de linger
segundos) hasta que los datos pendientes han sido
enviados void setTcpNoDelay() Permite
activar/desactivar el algoritmo de Nagle para el
envío en TCP (RFC 896) void setTrafficClass(int
tc) Permite seleccionar el byte TOS del paquete
IP (no todas las redes lo tienen en cuenta) Más
información en RFC 1349
43Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
Mensaje
socket.close()
socket.close()
serverSocket.close()
44Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
ServerSocket server new ServerSocket() La
creación del ServerSocket no requiere
ninguna sincronización entre servidor y cliente,
la operación es no -bloqueante
Mensaje
socket.close()
socket.close()
serverSocket.close()
45Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
Socket socket serverSocket.accept() La llamada
a accept() es síncrona y bloquea al hilo
llamante hasta que se recibe una solicitud de
conexión destinada al proceso servidor (dirección
IP y puerto). Cuando se recibe la citada
solicitud, se crea un nuevo socket que sirve
como extremo de la conexión junto con el socket
remoto del cliente
Mensaje
socket.close()
socket.close()
serverSocket.close()
46Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
Socket client new Socket(host, port) La
creación del socket cliente con este constructor
requiere el establecimiento de la conexión,
motivo por el cual esta llamada es bloqueante,
haciendo que el hilo cliente se bloquee hasta que
el establecimiento de la conexión haya
finalizado.
Mensaje
socket.close()
socket.close()
serverSocket.close()
47Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
socket.getInputStream()?read() La llamada a
read() sobre el InputStream del socket
es bloqueante, el hilo se detiene mientras no se
reciban los datos enviados por el proceso remoto.
Si se especifica un tamaño de datos esperado, la
llamada se bloquea hasta que se
reciben suficiente información desde el socket
remoto (i.e. readLine())
Mensaje
socket.close()
socket.close()
serverSocket.close()
48Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
socket.getOutputStream()?write() La llamada a
write() sobre el OutputStream del socket
es bloqueante, el hilo se detiene mientras TCP
haya recibido el asentimiento de los datos
enviados. Puede que una llamada a write() se
desbloquee sin que se haya producido un read()
en el proceso remoto, pero esto sólo sucede
cuando los datos enviados caben en la ventana
TCP del receptor
Mensaje
socket.close()
socket.close()
serverSocket.close()
49Sincronización de cliente y servidor
Cliente
Servidor
server new ServerSocket()
server.accept()
socket.close() Las llamadas a cloase() son
siempre no-bloqueantes, excepto cuando SO_LINGER
se ha activado sobre el socket, En cuyo caso la
llamada es bloqueante hasta que se terminan de
enviar los datos pendientes
client new Socket(host, port)
Conexión
Nuevo Socket conectado
read() sobre el Socket
write() sobre el socket
Mensaje
read() sobre el socket
write() sobre el Socket
Mensaje
socket.close()
socket.close()
serverSocket.close()
50Java stream sockets ejemplo de aplicación
import java.io. import java.net. public
class BasicServer public static void
main(String args) throws IOException int
port 2345 if(args.length 1) port
Integer.parseInt(args0) BasicServer server
new BasicServer(port) server.launch()
private ServerSocket serverSocket private
BasicServer(int port) throws IOException
serverSocket new ServerSocket(port) pri
vate void launch() throws IOException
while(true) Socket connection
serverSocket.accept() BufferedReader br new
BufferedReader( new InputStreamReader(connect
ion.getInputStream())) String inputMessage
br.readLine() PrintWriter pw new
PrintWriter(connection.getOutputStream()) pw.p
rintln(inputMessage.toUpperCase()) pw.close()
//si no se cierra primero, hay que hacer
flush br.close() connection.close()
51Java stream sockets ejemplo de aplicación
import java.io. import java.net. public class
BasicClient public static void main(String
args) throws Exception String remoteHost
"localhost" int remotePort
2345 BasicClient client new
BasicClient(remoteHost, remotePort) client.laun
ch() private String host private int
port private static final String message
"mensaje básico en iteración " private
BasicClient(String host, int port) throws
IOException this.host host this.port
port private void launch() throws Exception
for(int iteration 0 iteration lt 10
iteration) Socket connection new
Socket(host, port) PrintWriter pw new
PrintWriter(connection.getOutputStream()) pw.p
rintln(message iteration) pw.flush() Sys
tem.out.println("gtgtgtgtgt" message
iteration) BufferedReader br new
BufferedReader(new InputStreamReader(connecti
on.getInputStream())) System.out.println("ltltltlt
lt" br.readLine()) pw.close() br.close()
connection.close() Thread.sleep(1000)
52Lección 2.4 Sockets UDP
2.1 Introducción 2.2 Streams en Java 2.3
Sockets TCP 2.4 Sockets UDP 2.5 Sockets con
entrada/salida asíncrona en Java
53La API de sockets en modo datagama (UDP sockets)
- La API de sockets en modo datagrama proporciona
acceso al servicio de transporte de datos que
implementa el protocolo UDP - El servicio UDP no es orientado a conexión ni
proporciona entrega fiable de datos - El emisor envía un datagrama a un proceso remoto
(dirección IP y puerto) - Si el datagrama no se pierde, eventualmente será
recibido en el receptor - La API estándar de Java proporciona sockets
orientados a datagrama que permiten utilizar
directamente el servicio UPD que acabamos de
describir - Un socket en modo datagrama proporciona un
servicio full-duplex - Java también proporciona una abstracción
adicional que provee de un servicio de transporte
no fiable orientado a conexión basado en el
protocolo UPD. Esta abstracción forma parte del
runtime de Java y no supone modificación alguna
sobre el funcionamiento del protocolo UDP - Todos los paquetes que pertenecen a una de estas
conexiones lógicas comparten 4 parámetros (el
runtime de Java asigna paquetes a conexiones
usándolos) Puerto de origen, Dirección IP de
origen, Puerto de destino, Dirección IP de
destino - Vamos a estudiar en detalle el funcionamiento de
los sockets UDP utilizando la funcionalidad
proporcionada por la API estándar de Java
54La clase DatagramSocket
- La clase DatagramSocket forma parte del paquete
java.net de la API estándar - Constructores
- DatagramSocket() Crea un socket de datagrama y
lo ata a un puerto libre en la máquina local.
Este constructor suele crearse para desarrollar
el extremo cliente de una aplicación. - DatagramSocket(int port) Crea un nuevo socket de
datagrama y lo ata al puerto especificado en
todas las interfaces de la máquina local. Este
constructor suele utilizarse para desarrollar el
extremos servidor de una aplicación - DatagramSocket(int port, InetAddress laddr) Crea
un nuevo socket de datagrama y lo ata al puerto
especificado en la dirección IP dada sobre la
máquina local (para máquinas con varias
interfaces). Este constructor suele utilizarse
para crear el extremo servidor de una aplicación - DatagramSocket(SocketAddress bindaddr) Crea un
nuevo socket de datagrama y lo ata a la dirección
especificada (puerto y dirección IP). Este
constructor suele utilizarse para crear el
extremos servidor de una aplicación
55La clase DatagramSocket
Algunos métodos void bind(SocketAddress addr)
Ata el socket de datagrama a la dirección
especificada (puerto y dirección IP) int
getLocalPort() Devuelve el puerto al que el
socket está atado void receive(DatagramPacket p)
Recibe un datagrama en el socket. Esta llamada es
bloqueante. Cuando este método retorna, los datos
se devuelven en el buffer del DatagramPacket. El
DatagramPacket también contiene la dirección IP y
el puerto del emisor. void send(DatagramPacket
p) Envía un datagrama desde el socket. El
DatagramPacket contiene los datos, su longitud,
la dirección IP del receptor y su número de
puerto void setTraffciClass(int tc) Permite
establecer es campo TOS (type-of-service) de los
paquetes IP que encapsulan los datagramas (RFC
1349) void close() Cierra el socket, no se
podrán volver a enviar ni a recibir datragamas
desde el socket
56La clase DatagramPacket
- La clase DatagramPacket forma parte del paquete
java.net de la API estándar - Constructores
- DatagramPacket(byte buf, int length) Construye
un DatagamPacket para recibir datagramas con
hasta length bytes de datos - DatagramPacket(byte buf, int length,
InetAddress addr, int port) Crea un nuevo
DatagramPacket para el envío de los datos
especificados a la dirección y puerto remotos
dados - DatagramPacket(byte buf, int offset, int
length) Construye un DatagramPacket para la
recepción de datos, que se depositarán con el
offset especificado en el buffer dado - DatagramPacket(byte buf, int offset, int
length, InetAddress addr, int port) Crea un
DatagramPacket para enviar un datagrama UPD que
contenga length bytes de datos a partir de
bufoffset
57La clase DatagramPacket
Algunos métodos InetAddress getAddress() Cuando
el DatagramPacket es para envío, indica la
dirección IP del receptor, cuando el
DatagramPacket es de recepción, indica la
dirección IP del emisor (siempre la dirección IP
del lado remoto) byte getData() Devuelve el
buffer de datos asociado a este DatagramPacket.
Si el DatagramPacket es de recepción, el buffer
contendrá los datos recibidos int getLength()
Devuelve en número de bytes útiles en el
datagrama. Este método permite determinar los
bytes de datos útiles que se pueden extraer del
buffer devuelto por getData() int getPort()
Cuando el DatagramPacket es para envío, indica el
puerto receptor, cuando el DatagramPacket es de
recepción, indica el puerto del emisor (siempre
el puerto del lado remoto) void setData(byte
buf) Permite asociar un nuevo buffer de datos
con el DatagramPacket. Si el DatagramPacket es de
recepción, los datos del datagrama recibido no
podrán exceder buf.length, en caso contrario, los
datos se truncarán void setLength(int length)
Indica el número de bytes útiles en el buffer
asociado al DatagramPacket. Este método es usado
cuando el DatagramPacket es de emisión para
indicar cuantos bytes del buffer especificado se
enviarán en el datagrama correspondiente void
setPort(int port) Indica el puerto asociado a
un DatagramPacket. Este método se usa para
indicar el puerto del receptor el datagramas de
envío
58Sincronización de los DatagramSocket
Cliente
Servidor
server new DatagramSocket(2345)
client new DatagramSocket()
server.receive(datagramPacket)
client.send(datagramPacket)
Mensaje
client.receive(datagramPacket)
server.send(datagramPacket)
Mensaje
server.close()
client.close()
59Sincronización de los DatagramSocket
Cliente
Servidor
server new DatagramSocket(2345)
client new DatagramSocket()
server.receive(datagramPacket)
client.send(datagramPacket)
Mensaje
client.receive(datagramPacket)
server.send(datagramPacket)
DatagramSocket server new DatagramSocket(2345)
La creación de un DatagramSocket atado a un
puerto es no -bloqueante
Mensaje
server.close()
client.close()
60Sincronización de los DatagramSocket
Cliente
Servidor
server new DatagramSocket(2345)
client new DatagramSocket()
server.receive(datagramPacket)
client.send(datagramPacket)
Mensaje
client.receive(datagramPacket)
server.send(datagramPacket)
server.receive(datagramPacket) La llamada a
receive en un DatagramSocket es una
operación síncrona y, por tanto, bloqueante
Mensaje
server.close()
client.close()
61Sincronización de los DatagramSocket
Cliente
Servidor
server new DatagramSocket(2345)
client new DatagramSocket()
server.receive(datagramPacket)
client.send(datagramPacket)
Mensaje
client.receive(datagramPacket)
server.send(datagramPacket)
Mensaje
client.send(datagramPacket) La llamada a send en
un DatagramSocket es una operación no-bloqueante,
el paquete se envía pero so se espera
ningún asentimiento
server.close()
client.close()
62Sincronización de los DatagramSocket
Cliente
Servidor
server new DatagramSocket(2345)
client new DatagramSocket()
server.receive(datagramPacket)
client.send(datagramPacket)
Mensaje
client.receive(datagramPacket)
server.send(datagramPacket)
Mensaje
Cuando el mensaje se recibe, se desbloquea la
llamada al método receive de la clase
DatagramSocket
server.close()
client.close()
63Sincronización de los DatagramSocket
Cliente
Servidor
server new DatagramSocket(2345)
client new DatagramSocket()
datagramSocket.close() Las llamadas al método
close son siempre no -bloqueante
server.receive(datagramPacket)
client.send(datagramPacket)
Mensaje
client.receive(datagramPacket)
server.send(datagramPacket)
Mensaje
server.close()
client.close()
64Java Datagram Sockets ejemplo de aplicación
import java.io. import java.net. public
class BasicUDPServer public static void
main(String args) throws IOException
DatagramSocket udpSocket new
DatagramSocket(2345) DatagramPacket
datagramReceived new DatagramPacket(new
byte512, 512) while(true) udpSocket.recei
ve(datagramReceived) String messageReceived
new String(datagramReceived.getData(), 0
, datagramReceived.getLength()) byt
e answer messageReceived.toUpperCase().getByte
s() DatagramPacket datagramSent new
DatagramPacket( answer, answer.lengt
h, datagramReceived.getAddress(), data
gramReceived.getPort()) udpSocket.send(d
atagramSent)
65Java Datagram Sockets ejemplo de aplicación
import java.net. public class BasicUDPClient
private final static String message "el
cliente envia el mensaje " public static void
main(String args) throws Exception
DatagramSocket udpSocket new
DatagramSocket() for(int i 0 ilt 10 i
) String messageSent message i
byte datos messageSent.getBytes() Dat
agramPacket datagram new DatagramPacket( d
atos, datos.length, InetAddress.getByN
ame("localhost"), 2345) udpSocket.send(d
atagram) System.out.println("gtgtgtgtgt"
messageSent) datagram new
DatagramPacket(new byte512, 512) udpSocket.r
eceive(datagram) String messageReceived new
String(datagram.getData(), 0, datagram.getLe
ngth()) System.out.println("ltltltltlt"
messageReceived) Thread.sleep(1000)
66Java DatagramSockets orientados a conexión
- La clase DatagramPacket proporciona una
abstracción de envío de datagramas no fiable con
orientación a conexión - Métodos implicados
- void connect(InetAddress address, int port)
Conecta el DatagramSocket a la dirección remota
especificada. Una vez conectado, el socket sólo
podrá enviar o recibir datagramas de la dirección
remota especificada. Si se envía o recibe un
datagrama con dirección IP o puerto remoto
diferentes, se elevará una excepción
IllegalArgumentException. - En algunos sistemas, si el socket remoto no
existe en la dirección especificada, o si éste no
se puede alcanzar, el DatagramSocket local
recibirá un paquete ICMP (destination
unreachable), lo que se notificará con una
excepción PortUnreachableException en cualquier
llamada a send() or receive() - void disconnect() Desconecta el DatagramSocket
- boolean isConnected() Devuelve true si el socket
está conectado, false en caso contrario
67Lección 2.5 Sockets con entrada/salida asíncrona
en Java
2.1 Introducción 2.2 Streams en Java 2.3
Sockets TCP 2.4 Sockets UDP 2.5 Sockets con
entrada/salida asíncrona en Java
68Operaciones bloqueantes en los sockets de Java
Socket UDP
Socket TCP
server new ServerSocket()
client new DatagramSocket()
server.accept()
client.send(datagramPacket)
Nuevo Socket conectado
read() sobre el Socket
client.receive(datagramPacket)
write() sobre el Socket
socket.close()
client.close()
serverSocket.close()
69Problemas de las llamadas bloqueantes
- Las llamadas bloqueantes facilitan la
sincronización entre los procesos que comunican,
pero complican algunos aspectos del desarrollo - En ciertas condiciones producen el bloqueo
indefinido de un proceso - datagramSocket.receive(datagramPacket) pérdida
del mensaje emitido - datagramSocket.receive(datagramPacket) crash
del proceso emisor - serverSocket.accept() crash del proceso remoto
- Pueden producir interbloqueo entre los procesos
que comunican - Para procesos que deben mantener varias
comunicaciones simultáneas, requieren el uso
intensivo de hilos de ejecución (threads) - Los hilos limitan la escalabilidad del sistema
- La gestión de muchos hilos hace menos eficiente
el programa - Las llamadas bloqueantes dificultan la
destrucción y reutilización de hilos
Proceso A
Proceso B
Proceso origen en espera de proceso destino
Proceso C
70Uso de bloqueos con temporizador
- Para evitar los bloqueos indefinidos y permitir
que los procesos puedan reaccionar ante
situaciones especiales, Java proporciona la
posibilidad de establecer temporizadores
asociados a las llamadas bloqueantes - Estos temporizadores funcionan del modo
siguiente - El usuario elige un tiempo máximo (a través de
una invocación) - Cuando se produce la invocación al método
bloqueante, se inicia un temporizador - Si el método bloqueante retorna (es decir, se
desbloquea) antes de que se alcance el tiempo
máximo, el programa continúa con normalidad - Si se alcanza el tiempo máximo sin que la
llamada retorne, se eleva una excepción desde
dentro del método bloqueante - En todos los casos, el agotamiento del
temporizador y el lanzamiento de la excepción
correspondiente no afecta al estado del socket
correspondiente - El desarrollador tiene la opción de manejar la
excepción y dejar al programa continuar, o bien
de tomar acciones adicionales que pueden incluir
el cierre y destrucción del socket.
71Estableciendo el tiempo de espera máximo
- Estableciendo el tiempo máximo de espera en la
clase ServerSocket - serverSocket.setSoTimeout(int tiemout)
- tiemout especifica el tiempo máximo de espera en
milisegundos. Un valor de 0 indica un tiempo de
espera infinito - Afecta a las llamadas a serverSocket.accept(). Si
el timeout se agota, se elevará una excepción
java.net.SocketTimeoutException - Estableciendo el tiempo máximo de espera en la
clase Socket - serverSocket.setSoTimeout(int tiemout)
- tiemout especifica el tiempo máximo de espera en
milisegundos. Un valor de 0 indica un tiempo de
espera infinito - Afecta a las llamadas a read() en el InputStream
asociado al socket. Si el timeout se agota, se
elevará una java.net.SocketTimeoutException - Estableciendo el tiempo máximo de espera en la
clase DatagramSocket - serverSocket.setSoTimeout(int tiemout)
- tiemout especifica el tiempo máximo de espera en
milisegundos. Un valor de 0 indica un tiempo de
espera infinito - Afecta a las llamadas a datagramSocket.receive(p).
Si el timeout se agota, se elevará una
java.net.SocketTimeoutException
72Ejemplo de desarrollo con temporizadores
- Queremos que un proceso (o hilo) no se bloquee de
manera indefinida - Usamos temporizadores para desbloquearlo de
manera periódica - Cada cierto tiempo chequeamos si el necesario
continuar con otra cosa
import java.net. import java.io. public
class TimeoutServerSocket public static void
main(String args) throws IOException
ServerSocket server new ServerSocket(2345)
server.setSoTimeout(250) //El valor del
timeout depende de la aplicación boolean
continuar true while(continuar) try
Socket connection server.accept() //Ha
cer cosas con la conexión //Podemos hacer
continuar false catch(SocketTimeoutExc
eption ste) //Chequear si debemos
continuar //Podemos elevar una
excepción //Podemos cerrar el socket y hacer
continuar false
73Desarrollo con entrada/salida asíncrona
- El modelo basado en temporizadores es una
solución a medias - No evita el uso de múltiples hilos de ejecución
- Si se quieren tiempos de respuesta bajos
(timeouts pequeños) se degradan las prestaciones - Si se quieren prestaciones elevadas (timeouts
grandes) se degradan los tiempos de respuesta - Muchos lenguajes de programación/entornos de
desarrollo ofrecen operaciones de entrada salida
asíncronas - Las operaciones de entrada/salida asíncronas
ofrecen servicios de lectura y escritura no
bloqueantes - La estrategia habitual para desarrollar programas
con entrada/salida asíncrona se basa en el uso de
modelos de programación orientados a eventos - El programa es un bucle a la espera de eventos
- Cuando se producen eventos, el bucle realiza una
iteración para procesarlos - Puede usarse un único hilo de ejecución o varios
para procesar los eventos - Java define un conjunto de facilidades de
entrada/salida asíncronas en el paquete java.nio
(Java New Input Output) que existe desde el JDK
1.4 - Vamos a ver los fundamentos de estas facilidades,
pero no profundizaremos
74Java New Input Output
- El paquete java.nio proporciona facilidades para
el desarrollo de aplicaciones que requieran
entrada/salida de alta velocidad y/o altas
prestaciones - Delega algunas de las partes más tediosas
(gestión de bufferes) al sistema nativo - El modelo de streams (definido en java.io) se
basa en corrientes de bytes, el modelo de
java.nio, se basa en bloques que se almacenan en
bufferes - En java.nio existen dos conceptos (objetos)
esenciales - Channel Es un objeto análogo a un stream, pero
bidireccional (se puede leer y escribir sobre el
mismo objeto) y que funciona sobre bloques y no
sobre bytes - Buffer Es el objeto que se utiliza como
contenedor de bloques para los channels - En java.nio todos los datos que se leen o
escriben se gestionan a través de un objeto
Buffer - Además de contener los datos, el objeto Buffer
proporciona facilidades al programador que
facilitan el desarrollo de aplicaciones - Existen diferentes tipos de buffers
especialistas en diferentes tipos de datos
75Bufferes en java.nio
- La clase java.nio.Buffer es abstracta
- Para cada tipo de datos primitivos existe una
subclase especialista ByteBuffer, CharBuffer,
ShortBuffer, IntBuffer, LongBuffer, FloatBuffer y
DoubleBuffer - El Buffer contiene un bloque (array) de datos de
entrada/salida que puede ser manipulado a través
de métodos específicos - El funcionamiento de estos métodos se basa en
tres variables de estado - La variable position
- Especifica la siguiente posición del array que
será escrita/leída - En cada operación de lectura/escritura, position
se incrementa en una unidad - La variable limit
- Especifica cuantas posiciones del array pueden
ser leidas/escritas en total - Es decir, indica cuanto espacio tiene el array
para lectura/escritura - Siempre se cumple que position lt limit
- La variable capacity
- Especifica la máxima cantidad de elementos que
caben en el array - Es decir, indica el tamaño en memoria del array
subyacente - Siempre se cumple que limit lt capacity
76Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
77Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
a
78Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
a
b
79Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
a
b
c
80Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
a
b
c
81Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
c a
a
b
c
82Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
c b
a
b
c
83Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
c c
a
b
c
84Bufferes en java.nio Ejemplo de uso
CharBuffer charBuffer CharBuffer.allocate(5) ch
arBuffer.put('a') charBuffer.put('b') charBuffer
.put('c') charBuffer.flip() char c
charBuffer.get() c charBuffer.get() c
charBuffer.get() c charBuffer.get()
throws java.nio.BufferUnderflowException
a
b
c
85Channels en java.nio
- Channel es una interfaz definida en el paquete
java.nio.channels - El Channel representa una conexión abierta hacia
una entidad sobre la que se puede realizar
entrada/salida de datos - Un Channel recién creado está abierto, una vez
que se cierra no es posible realizar operaciones
posteriores de entrada/salida. Se puede saber si
un Channel está abierto invocando el método (de
la interfaz) isOpen() - La interfaz Channel cuenta con un conjunto de
subinterfaces y de clases que la implementan - Algunas Subinterfaces ByteChannel,
InterruptibleChannel, ReadableByteChannel,
WritableByteChannel, etc. - Algunas clases que lo implementan
- FileChannel Especialista en asociarse a
FileStreams para entrada/salida - SocketChannel Especialista en asociarse a
instancias de la clase Socket - ServerSocketChannel Especialista en asociarse a
instancias de ServerSocket - DatagramChannel Especialista en asociarse a
instancias de DatagramSocket - SelectableChannel Channel que puede ser
multiplexado a través de un Selector. Es
superclase de todos los Channels que tienen que
ver con Sockets
86Ejemplo de uso de la clase FileChannel
import java.io. import java.nio. import
java.nio.channels. public class
FileChannelExample private static final String
FILENAME "channelFile.dat" private static
final String MESSAGE "hola nena" public
static void main(String args) throws
IOException writeFileWithChannel() readFile
WithChannel() public static void
writeFileWithChannel() throws IOException
FileOutputStream fos new FileOutputStream(FI
LENAME) ByteBuffer buffer ByteBuffer.allocate
Direct(1024) buffer.put(MESSAGE.getBytes("ISO-8
859-1")) buffer.flip() FileChannel ch
fos.getChannel() ch.write(buffer) ch.close()
public static void readFileWithChannel()
throws IOException FileInputStream fis new
FileInputStream(FILENAME) FileChannel ch
fis.getChannel() ByteBuffer buffer
ByteBuffer.allocateDirect(1024) ch.read(buffer)
buffer.flip() byte data new
bytebuffer.remaining() buffer.get(data) St
ring message new String(data,
"ISO-8859-1") ch.close()
87Entrada/Salida asíncrona con Sockets en Java
- Este mecanismo permite leer/escribir sobre un
socket de red sin bloqueos - El modelo de programación se basa en eventos
- Hay que registrar interés por un evento de
entrada/salida determinado - Cuando el evento sucede, el sistema informa del
mismo - Se procesa el evento ocurrido
- Se vuelve a esperar por eventos
- La clase Selector se utiliza para la gestión de
eventos en subclases de SelectableChannels - Selector es una clase abstracta que proporciona
un multiplexor de channels - Un SelectableChannel que se registra en un
Selector, queda representado mediante un objeto
de la clase SelectionKey - Las instancias de SelectionKey tienen múltiples
métodos de interés - SelectableChannel channel() recupera el channel
asociado a la key - Selector selector() recupera el Selector en que
se creó la key - Al registrar el SelectableChannel hay que
especificar los eventos de interés - Cuando todos los channels y sus eventos se han
registrado, el Selector está listo
88Uso de selectores
- Partimos de instancias de subclases de
SelectableChannel - Abrimos un Selector
- Registramos los channels declarando los eventos
de nuestro interés - Los eventos de interés son enteros potencias de
dos - Se pueden activar varios eventos usando el
operador (bitwise) - SelectionKey.OP_ACCEPT Cuando se ha recibido una