Comunicación Entre Procesos Usando Sockets
Veremos: Generalidades, Funciones usadas por el lado Servidor y Funciones usadas por el lado Cliente.

Generalidades
Host Name (nombre de la máquina)

Nombre del host v/s dirección (mateo.elo.utfsm.cl v/s 200.1.17.4)
Obtención del nombre del host
int gethostname(char *name, int len);
Esta función pone el nombre del host local en el arreglo name, el cual es de largo len. Retorna 0 en caso de exito y -1 en falla.
Dirección del Host (dirección de la máquina)
Dirección única en la Internet y de 32 bits. Otros nombres: dirección de red, dirección Internet.
Usamos notación punto para referirnos a ellas. i.e. 123.45.67.89 a cambio de 0x7B2D4359
Cada dirección de red tiene dos partes: un número de red y uno de host.
 
El número de host puede ser dividido en número de subred y número de host. La dirección de red es usada por los routers para el enrutamiento de los paquetes en la red.
Clase B y C no resultaron adecuadas para los tamaños típicos de las organizaciones. Clase C (254 máquinas) no logra satisfacer las necesidades de muchas empresas medianas, y Clase B resulta muy grande para éstas (65.534 máquinas).
Las normas aprobadas para modificar la definición del espacio de direcciones se conocen bajo el nombre de Classless Inter-Domain Routing (CIDR) (Enruteamiento entre dominios sin usar clases), y las normas que lo describen son: RFC 1467, RFC 1518, y RFC 1519.
Para hacer un mejor uso de las direcciones dentro de cada clase  es posible definir subredes. Es decir, una clase se divide en varias subclases. Por ejmeplo la red ELO tiene:
Clase C: 200.1.17.0 y la divide en dos subredes de 128 direcciones cada una.
Clase C: 200.1.28.0 de esta clase sólo tiene disponibles las subredes 200.1.28.0/26 y 200.1.28.128/26
Por otra parte la posibilidad de crear subredes aumenta en forma considerable la cantidad de entradas en las tablas de ruteo de los routers. Por ello se defines las super-redes.  La idea es compactar a sólo una entrada en la tabla de ruta a todas las redes o subredes adyacentes que deban ser ruteadas hacia un mismo destino.
Caso red Electrónica
Transformación entre nombre y dirección de máquina
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet./in.h>
struct hostent * gethostent (void)
struct hostent * gethostbyname(const char *name);
struct hostent * gethostbyaddr(const char * addr, int len, int type);
int sethostent(int stayopen);
int endhostent(void);
Estas funciones buscan los nombres de máquinas y sus direcciones en bases de datos que dependen de la configuración del sistema.
sethostent() y endhostent() permiten abrir y cerrar la base de datos.

gethostent() lee la próxima entrada en la base de datos.
gethostbyname busca la entrada con el nombre de máquida dado.
gethostbyaddr busca la entrada con la dirección de máquina dada.

Estas funciones retornan un puntero a la estrutura con la información sobre la máquina. Si no está retornan un NULL.
struct hostent {
    char  *h_name;  /* nombre oficial */
    char  **h_aliases;  /* aliases */
    int   h_addrtype;
    int   h_length;
    char  **h_addr_list;
}
Servicios y Números de Puertas
    Los servicios ofrecidos (ftp, telnet, email, etc) por una máquina son diferenciados por un número de puerta. El número de puerta es un entero sin signo de 16 bits.
    Los servicios de una máquina están listados en el archivo /etc/services  (en mi computador con Debian tengo este /etc/services).  Este archivo puede ser leido con llamados al sistema que simplifican su acceso. Ej:
struct servent * get servbyport(int port, char *proto);

Orden de los Bytes en la Red
    Intel (80x86, Pentium y otros) y Digital Equipment Corporation (DEC) son little-endian. Motorola y Sun SPARC son big-endian (byte más significativo en la dirección menor de memoria). Solución: se definió un orden de bytes de red (= big-endian). Así se asegura que el tráfico puede ser leido en ambas arquitecturas.

Funciones para hacer las conversiones:

#include <sys/types.h>
#include <netinet/in.h>
u_long htonl (u_long hostlong);
u_short htons(u_short hostshort);
u_long ntohl(u_long netlong);
u_long ntohs(u_short netshort);
gethostby* retornan enteros con orden de red.
¿Cómo tratar string?
¿Cómo tratar números en punto flotante?

Algunos Protocolos Relevantes de la Internet
(Las imágenes de esta sección fueron tomadas de: TCP/IP Illustrated, Volume 1. W. Richard Stevens)

 


 


 

Creación de Sockets
    El socket es la abstracción usada para definir un punto de comunicación.

#include <sys/types.h>
#include <sys/socket.h>
int socket (int domain, int type, int protocol);
domain especifica la familia de direcciones en que se debe interpretar la dirección del socket. Puede ser: AF_UNIX , en cuyo caso la dirección es un pathname; o AF_INET , en caso de uns dirección Internet.

type puede ser SOCK_STREAM, lo cual especifica una conexión de circuito virtual. Es bidireccional y contínuo. En la Internet éste implica TCP. Otra opción es SOCK_DGRAM, lo cual especifica envío de paquetes discretos. En la Internet éste corresponde a UDP.

protocolo es el número del protocolo. Corresponde a la familia de protocolo (PF_UNIX o PF_INET). Lo mejor es dejarlo en cero y el sistema resuelve el valor que corresponde.

La función retorna un descriptor de socket. Éste es similar a un descriptor de archivo.

Funciones del lado del Servidor
El servidor debe asociar el socket con una puerta y dirección para que los clientes puedan acceder a él. Esto se logra con la función bind.

#include <sys/types.h>
#include <sys/socket.h>
int bind (int s, const sockaddr * name, int addrlen);
s es el socket ya creado,
addrlen es el largo de la estructura name, y
name es del tipo sockaddr_in
struct socketaddr_in {
    short                   sin_family; /* AF_INET */
    u_short               sin_port;    /* puerta de servicio */
    struct in_addr     sin_addr;    /* dirección asociada a la puerta */
}
Las máquinas pueden tener más de una tarjeta de red; i.e. más de una dirección Internet, por ello se debe especificar la dirección sin_addr. Si queremos atender requerimientos entrando por cualquier interfaz, se puede usar INADDR_ANY como dirección asociada a la puerta.

Esperando por Conexiones
Sólo es requerido cuando el servicio es orientado al stream (TCP).

#include <sys/types.h>
#include <sys/socket.h>
int listen (int s, int backlog);
Esta función comunica al sistema operativo que el socket está listo para recibir conexiones. backlog especifica el máximo número de requerimientos de conexión que pueden estar pendientes en cada momento (<= 5). Si hay muchos tratando de conectarse, el cliente recibe un mensaje "connection refused".

Aceptación de Conexiones

#include <sys/types.h>
#include <sys/socket.h>
int accept (int s, struct sockaddr *name, int *addrlen);
 Esta función retorna un nuevo descriptor de socket que el servidor usa para comunicarse con el cliente que fue aceptado. El OS almacena la información del cliente en name y el largo de la estructura en addrlen; es decir son valores de salida.

Funciones Usadas por el lado Cliente
Conexión con el servidor

#include <sys/types.h>
#include <sys/socket.h>
int connect (int s, struct socketaddr *name, int addrlen);
Esta función conecta el socket s con el servidor corriendo en la máquina y puerta especificada en name.

Esta fucnión puede ser usada en conexiones datagramas o de circuito virtual. En el primer caso esta función deja claro que los próximos datagramas están dirigidos al destido donde se hace la conexión. De otra forma la dirección destino debe ser especificada en cada llamado de envío de datagrama.

Funciones para Transferencia de Datos

#include <sys/types.h>
#include <sys/socket.h>
int recv (int s, char * buf, int len, int flags);
int send (int s, const char * buf, int len, int flags);
Son idénticas a read and write, sólo que ellas tienen un cuarto argumento para especificar como enviar o recibir los datos.

Cuando se usa conexión datagrama, el servidor no llama a las funciones listen y accept, y el cliente generalemnet no llama a la función connect. En estos casos se usan las funciones:

int recvfrom(int s, char * buf, int len, int flags, struct sockaddr *from, int fromlen);
int sendto(int s, const char *buf, int len, int flags, struct sockaddr *to, int tolen);
Destrucción  o Cierre del Canal de Comunicación
Es posible usar la función close(). Ésta se bloquea hasta que todos los datos sean enviados. Alternativamente se puede usar:
#include <sys/types.h>
#include <sys/socket.h>
int shutdown(int s, int how);
how = 0 se cierra para lectura, se puede seguir enviando.
how = 1 se cierra para escritura, se puede seguir recibiendo. Datos siendo enviados por el sistema operativo pueden ser descartados.
how = 2  equivale a shutdown(0); shutdown(1);

Ejemplos:
    TCP: Servidor    Cliente
    UDP: Servidor    Cliente