Manual Sockets
Manual Sockets
Redes de Ordenadores
Ingeniero de Telecomunicación
1
1 Introducción
2 El interfaz de sockets
2
• Gestionar condiciones de error y comunicaciones abortadas.
• Liberar recursos locales cuando termina la comunicación.
Una aplicación llama a open para iniciar una entrada/salida, dicha op-
eración devuelve un entero llamado descriptor que utilizará la aplicación para
las demás operaciones. A continuación el programa puede utilizar las fun-
ciones de read o write para leer o escribir datos o lseek para posicionarse
en un determinado lugar. Una vez la aplicación ha terminado sus lecturas
y escrituras, llama a la función close. Por último la función ioctl per-
mite enviar comandos de bajo nivel al dispositivo en uso. Por ejemplo, fijar
parámetros en un dispositivo de red tales como si el interfaz debe escuchar
en modo promiscuo o multicast, etc.
Los descriptores que obtienen las aplicaciones de una llamada a open depen-
den del tipo de recurso que estén abriendo. Una operación de apertura de
un fichero devolverá un descriptor de fichero, mientras que una operación de
apertura de un socket devolverá un descriptor del socket abierto. La llamada
de apertura de un socket se denomina socket.
3
los descriptores de sockets y corresponde a cada aplicación saber que tipo
de recurso está asociado a cada descriptor. El sistema utiliza estas tablas
para referenciar las estructuras de datos que controlan el estado del recurso
representado por el descriptor.
#include <sys/types.h>
#include <sys/socket.h>
4
Familias de sockets
AF UNIX protocolos internos UNIX
AF INET protocolos Internet ARPA
AF ISO protocolos ISO
AF NS Protocolos de sistemas Xerox Network
AF IMPLINK IMP
”host a IMP” nivel de enlace
Tipos de sockets
SOCK STREAM stream fiable orientado a conexión
SOCK DGRAM datagramas, no fiable
SOCK RAW root (datagramas) acceso total
Protocolos de sockets
ip internet protocol, pseudo protocol number
icmp internet control message protocol
igmp internet group multicast protocol
ggp gateway-gateway protocol
tcp transmission control protocol
pup PARC universal packet protocol
udp user datagram protocol
raw RAW IP interface
Una vez creado un socket puede ser usado por un servidor para esperar
conexiones entrantes (socket pasivo), o bien por un cliente para iniciar
conexiones (socket activo). En cualquier caso, para realizar una comuni-
cación, hay que conocer la dirección IP y el puerto local en los que se sitúa
la otra parte de la comunicación. Para ello, el interfaz de sockets permite
especificar las direcciones y los puertos (locales y remotos) que representan
unı́vocamente la comunicación.
5
este manual nos referiremos siempre a TCP/IP cuya familia de direcciones se
denota con la constante AF INET. Las aplicaciones que usan TCP/IP exclusi-
vamente puede usar la estructura de direcciones sockaddr in que detallamos
(en lenguaje C) en la figura 2 de la página 6:
struct sockaddr_in {
u_char sin_len; /* longitud total */
u_short sin_family; /* familia de direcciones */
u_short sin_port; /* numero de puerto */
struct in_addr sin_addr;/* direccion IP(u_long) */
char sin_zero; /* no usado (0) */
}
Una vez creado el socket, un cliente llama a connect para establecer una
conexión remota con un servidor. El cliente debe especificar el punto de
acceso remoto. Una vez conectado el socket, el cliente puede leer y escribir
datos en él.
int connect(
int sockfd, /* descriptor del socket */
struct sockaddr *s_addr, /* direccion del otro extremo */
int addrlen ); /* longitud de la direccion (en bytes) */
6
2.3 La llamada a write
Tanto los clientes como los servidores usan write para enviar datos a través
del socket. Para escribir datos en un socket hay que especificar el buffer que
contiene los datos a escribir ası́ como la longitud del buffer. write copia los
datos en buffers del kernel y devuelve el control a la aplicación. Si todos
los buffers del kernel están llenos, la aplicación queda bloqueada hasta que
se libera el espacio suficiente. La llamada es la misma que se utiliza para
escribir count datos de un buffer buf en un descriptor de fichero fd:
Una vez conectado, el cliente escribe su petición y lee la respuesta que manda
el servidor. Para leer datos de un socket hay que especificar un buffer en el
que deben situarse los datos recibidos, ası́ como la longitud del mismo. La
operación de lectura copia en el buffer del usuario los datos recibidos. Si no
ha llegado ningún dato, la aplicación queda bloqueada hasta que se reciba
alguno. Si llegan más datos que la longitud del buffer del usuario, read solo
extrae del socket el numero suficiente para llenar el buffer del usuario. Si
llegan menos datos de los que caben en el buffer, read copia todos los datos
y devuelve el número de bytes recibidos.
También se puede usar read con sockets que usen UDP. read copia el
datagrama recibido en el buffer del usuario. En caso de que el datagrama
sea más grande que el buffer de usuario, se copian todos los posibles y se
descarta el resto.
7
2.5 La llamada a close
8
2.7 La llamada a listen
9
Servidor
socket
Cliente ?
open bind
? ?
connect listen
read accept $
? ?
read
? ?
write
? ?
close write
?
close %
10
dar servicio a cada petición. El servidor, tras la llamada a listen, entrará
en un bucle en el cual:
1. llama a accept
2. crea un proceso hijo con los parámetros necesarios (al menos el identi-
ficador del socket devuelto por accept).
Para que los programas se puedan migrar de una arquitectura a otra sin
problemas, el sistema operativo ofrece las funciones de conversión de la repre-
sentación usada en la red (a partir de ahora nos referiremos a ella como orden
de red) a la de la máquina en la que corre el sistema operativo. Las funciones
se describen en la tabla 4 de la página 12. Si el programador utiliza estas
funciones para convertir enteros del formato utilizado en la red al
utilizado en la máquina, no tendrá problemas de portabilidad.
11
Conversión Tipo de
Función De A dato
ntohs red host short
ntohl red host long
htons host red short
htonl host red long
struct in_addr {
unsigned long int s_addr;
}
12
La estructura hostent se define de la siguiente forma:
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}
#define h_addr h_addr_list[0]
h addr list Array de direcciones de red para el host en orden de red (un
host puede tener más de una tarjeta de red, etc.)
13
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
}
struct protoent {
char *p_name;
char **p_aliases;
int p_proto;
}
14
p proto número del protocolo.
15
void TCPdaytime(const char *host)
{
char buf[LINELEN]; /* buffer para una linea de texto */
int s, n; /* socket, contador de lectura */
s = connectTCP(host, "daytime");
while((n = read(s, buf, LINELEN)) > 0) {
buf[n] = ’\0’; (void) fputs(buf, stdout);
}
}
16
public String TCPdaytime(String host)
{
Socket s; /* socket */
InputStream sin;
try{
s = new Socket(host, "daytime");
sin =s.getInputStream();
byte buf[LINELEN];
int rec;
while((rec= sin.read(buf)) != -1) {
String texto= new String(buf,0,rec);
System.out.println(texto);
}
}
catch (UnknownHostException uh)
{ System.err.println("Host desconocido"); }
catch (IOException io)
{ System.err.println("Error de I/O"); }
}
LineNumberReader lnrin=
new LineNumberReader(
new InputStreamReader(sin));
system.out.println(lnrin.readLine());
En general resulta más cómodo crear este tipo de filtros con Streams cuando
se necesita leer muchos datos del mismo.
17
4.2 Implementación de un cliente UDP de DAYTIME
18
socket la respuesta, la convierte al formato del host y la escribe (utilizando
facilidades propias de UNIX como ctime).
try{
s = new DatagramSocket(13);
/* 13 es el puerto de daytime */
d = new DatagramPacket(buf,buf.length,
getByName(host),
"que hora es?");
s.send(d);
s.receive(d);
String texto=new String(d.getData(),0,d.getLength());
System.out.println(texto);
}
catch (UnknownHostException uh)
{ System.err.println("Host desconocido"); }
catch (IOException io)
{ System.err.println("Error de I/O"); }
}
19
5 Clientes y servidores de eco
20
void UDPecho(const char *host)
{
char buf[LINELEN+1]; /* buffer para una linea de texto */
int s, n; /* socket, contador de lectura */
s = connectUDP(host, "echo");
while(fgets(buf, sizeof(buf),stdin)){
buf[LINELEN] = ’\0’;
n = strlen(buf);
(void) write(s, buf, n);
21
• Acción a tomar en caso de fallo del cliente.
22
retardos y rutas varı́an dinámicamente.
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void reaper(int);
int TCPechod(int fd);
int errexit(const char *format, ...);
int passivesock(const char *service,
const char *transport, int qlen);
int
main(int argc, char *argv[])
{
char *service = "echo"; /* nombre servicio o num puerto */
23
struct sockaddr_in fsin; /* direccion de un cliente */
int alen; /* longitud de la direccion del cliente */
int msock; /* socket maestro del servidor */
int ssock; /* socket esclavo del servidor */
switch (argc) {
case 1:
break;
case 2:
service = argv[1];
break;
default:
errexit("uso: TCPechod [port]\n");
}
while (1) {
alen = sizeof(fsin);
ssock = accept(msock, (struct sockaddr *)&fsin, &alen);
if (ssock < 0) {
if (errno == EINTR)
continue;
errexit("accept: %s\n", strerror(errno));
}
switch (fork()) {
case 0: /* hijo */
(void) close(msock);
exit(TCPechod(ssock));
default: /* padre */
(void) close(ssock);
break;
case -1:
errexit("fork: %s\n", strerror(errno));
}
}
24
}
int
TCPechod(int fd)
{
char buf[BUFSIZ];
int cc;
void
reaper(int sig)
{
int status;
25
TCPechod() termina. Cuando el proceso hijo termina, UNIX manda al pro-
ceso padre la señal SIGCHLD indicando la terminación del proceso hijo. El
servidor mediante la llamada a signal(SIGCHLD,reaper) informa al sistema
operativo de que debe ejecutar la función reaper() cuando reciba la señal
SIGCHLD. La función reaper() lo que hace es llamar a wait3() que se en-
carga de completar la terminación de un proceso que llamó a exit(). El
argumento WNOHANG hace que la llamada a wait3 no sea bloqueante.
class echoserver{
ServerSocket maestro;
Socket esclavo;
maestro=new ServerSocket(puerto,5);
while (true){
esclavo=maestro.accept();
Hijo hijo=new conexion(esclavo, this);
hijo.start();
}
}
}
26
la implementación Java, el servidor crea el socket maestro y espera a recibir
llamadas entrantes. Por cada llamada crea un nuevo Thread hijo pasando
como parámetro el descriptor del socket esclavo en el cual espera el cliente
del servicio. Del Thread hijo se muestra el constructor y el esqueleto del
método run() que se deja como ejercicio al alumno.
References
[1] W. Richard Stevens. TCP/IP Illustrated, Vol I The Protocols. Addison
Wesley Professional Computing Series, 1994.
[4] J. Postel. Echo Protocol. IETF RFC número 862, 05/01/1983 Experi-
mental
27
public string TCPecho(String host)
{
Socket s; /* socket */
InputStream sin;
OutputStream sout;
try{
s = new Socket(host, "echo");
sin =s.getInputStream();
sout =s.getOutputStream();
byte buf[LINELEN+1];
int outb, inpb;
while(system.in.read(buf,sizeof(buf))){
sout.write(buf);
sin.read(buf);
}
}
catch (UnknownHostException uh)
{ System.err.println("Host desconocido"); }
catch (IOException io)
{ System.err.println("Error de I/O"); }
}
28
public string UDPecho(String host)
{
DatagramSocket s; /* socket UDP */
DatagramPacket din; /* datagrama a recibir*/
DatagramPacket dout; /* datagrama a enviar*/
byte buf[LINELEN+1];
try{
s = new DatagramSocket("echo");
system.in.read(buf,buf.length);
dout = new DatagramPacket(buf,buf.length
getByName(host),"echo");
do{
dout.setData(buf);
s.send(dout);
s.receive(din);
system.out.println(din.getData());
}while (system.in.read(buf,buf.length))
}
}
catch (UnknownHostException uh)
{ System.err.println("Host desconocido"); }
catch (IOException io)
{ System.err.println("Error de I/O"); }
}
29