Comunicación Entre Procesos Usando Sockets
Comunicación Entre Procesos Usando Sockets POSIX.1
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 éxito y -1 en falla.
Dirección del Host (dirección de la máquina)
Dirección única en la Internet y de 32 bits en IPv4 (128 bits en IPv6). 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: una dirección de subred y una de host dentro de la subred.
 
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) (Enrutamiento entre dominios sin usar clases), y los estándares 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 debiendo especificar cuántos bits corresponden a la dirección de la red (prefijo). La diferencia corresponde a la dirección de la máquina dentro de esa subred. Por ejemplo la red ELO podría tener:
Clase C: 200.1.17.0 dividida en dos subredes de 128 direcciones cada una.
Transformación entre nombre y dirección de máquina
#include
#include
#include

int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);

void freeaddrinfo(struct addrinfo *res);

Estas funciones son la versión actual de las funciones gethostbyname() y getservbyname().
getaddrinfo()maneja transparentemente direcciones IPv4 e IPv6. Así el programa se torna independiente de la versión de IP que se ocupe.

struct addrinfo {
             int ai_flags;           /* input flags */
             int ai_family;          /* protocol family for socket */
             int ai_socktype;        /* socket type SOCK_STREAM, SOCK_DGRAM*/
             int ai_protocol;        /* protocol for socket */
             socklen_t ai_addrlen;   /* length of socket-address */
             struct sockaddr *ai_addr; /* socket-address for socket */
             char *ai_canonname;     /* canonical name of host for service location */
             struct addrinfo *ai_next; /* pointer to next in list */
     };



La operación inversa (obtener nombre y servicio a partir de una dirección IP y puerto) se logra con getnameinfo()
Servicios y Números de Puertas
    Los servicios ofrecidos (ftp, telnet, email, etc) por una máquina son diferenciados por un número de puerto. El número de puerto es un entero sin signo de 16 bits.
    Los servicios de una máquina están listados en el archivo /etc/services  (ejemplo de /etc/services en Ubuntu).  Este archivo puede ser leído 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 leído 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);
Ver páginas man en plataforma específica. Por ejemplo en aragorn año 2008, se tiene:
#include <netinet/in.h>
 uint32_t htonl(uint32_t hostlong);
 uint16_t htons(uint16_t hostshort);
 uint32_t ntohl(uint32_t netlong);
 uint16_t ntohs(uint16_t netshort);
gethostby* retornan enteros con orden de red.
¿Cómo tratar string?
¿Cómo tratar números en punto flotante?

Protocolos Relevantes en 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 (Address Family UNIX), en cuyo caso la dirección es un pathname; o AF_INET (Address Family Internet), en caso de una dirección Internet IPv4, Usar AP_INET6 (Address Protocol Internet IPv6) para IPv6. Ver más en man socket.

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. Otras opciones son: SOCK_RAW para acceso a un protocolo "crudo", por ejemplo directo a IP , SOCK_RDM provee servicio garantizado pero sin comprometer orden.

protocolo es el número del protocolo específico a utilizar del tipo señalado. Normalmente sólo un protocolo es soportado de un tipo dado dentro de una familia de protocolos. Una opción es dejarlo en cero y el sistema resuelve el valor que corresponde a ese protocolo según el valor dado en type.

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

Funciones en programación de Servidor
El servidor debe asociar el socket con una puerta y dirección de interfaz de red 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 de red, se puede usar INADDR_ANY como dirección asociada a la interfaz.

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 en 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 puerto 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 (Servicor y Cliente)

#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 (Servicor y Cliente)
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