6.
INTERFACE DE SOCKETS :La Interface de sockets es una API para redes TCP/IP que se compone de funciones o rutinas. Originalmente se construyó a principios de los 80 para el sistema operativo UNIX , aunque hoy en dia también la utilizan otros sistemas operativos como Microsoft Windows, Mac, OS2, etc...
Las llamadas al sistema de E/S en UNIX se basan en el proceso de open-read-write-close (abrir-leer-escribir-cerrar), y esto se utiliza tanto con archivos como con dispositivos hardware. En este tipo de comunicación se basó el diseño de la interface de sockets, con lo que para comunicarse con una red TCP/IP, se abre primero una conexión con la red , se leen y escriben datos a traves de ella y una vez terminados los procesos se cierra la conexión.
Diseño basado en el modelo cliente-servidor
Ejemplo de esquema de conexión entre un servidor WWW y un cliente WWW a través de sockets.
6.1. SOCKETS : UN INTERFAZ GENÉRICO :
Un socket es una representación abstracta del extremo (endpoint) en un proceso de comunicación. Es un punto de acceso (SAP) que una aplicación puede crear para acceder a los servicios de comunicación que ofrecen las pilas de protocolos.
Para que se dé la comunicación en una red , el programa requiere un socket en cada extremo del proceso de comunicación.
Esquema de interacción de la Interface de Sockets con las aplicaciones y los protocolos de red.
Entre sus características principales del Interfaz de Sockets destacamos :
Para crear un socket , se utiliza la función socket , que devuelve un identificador de socket. Se deben especificar tres parametros :
socket = socket(protocol_familiy, socket_type, protocol)
1. Familia de Protocolos (Protocol Family) : Identifica a una familia de protocolos relacionados , como TCP/IP. Se utilizan constantes para definir a las familias de protocolos como : PF_INET (familia de protocolos Internet).
2. Tipo de Socket (Socket_type) : Si el programa utilizará el socket para transmitir flujo de bytes o datagramas. SOCK_DGRAM para datagramas y SOCK_STREAM para flujo de bytes. La interface de sockets también define un tercer tipo de comunicación llamada socket básico (raw socket) SOCK_RAW , que permite a una aplicación utilizar los mismos protocolos de nivel inferior que la red utiliza comunmente.
3. Protocolo a utilizar : Permite especificar que protocolo utilizará el socket. IPPROTO_TCP , IPPROTO_UDP
Ej : socket_handle = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
Cuando se llama a la función socket, la implementación del socket lo crea y devuelve un identificador de socket que identifica a un registro en la tabla de descripción. El registro muestra la estructura de datos del socket.
Estructura de Datos del Socket |
Familia de Protocolos |
Tipo de Servicio |
Dirección IP Local |
Dirección IP Remota |
Puerto de Protocolo Local |
Puerto de Protocolo Remoto |
Cada vez que la aplicación llama a la función socket, la implementación de este reserva memoria para una nueva estructura de datos y almacena la dirección de la familia, el tipo de socket y el protocolo.
Entonces una conexión de red entre dos procesos se compone de cinco elementos :
1. Un puerto de protocolo local que especifica donde recibe mensajes un proceso.
2. La dirección del host local, la cual identifica al anfitrión que recibirá los paquetes de datos.
3. Un puerto de protocolo remoto que identifica al proceso destino.
4. La dirección del anfitrión remoto, que identifica al anfitrión destino.
5. Un protocolo, que define como los procesos transfieren la información a través de la red.
Cuando un proceso desea establecer una comunicación con otro, el proceso emisor transmite la información al socket y la API de sockets maneja la interface con la pila de protocolos TCP/IP.
6.1.1 CONFIGURACION DEL SOCKET :
Una vez creado el socket , utilizando la función socket, se pueden utilizar las funciones de configuración dependiendo del uso que se le vaya a dar al socket :
Maquina Local : bind Maquina remota : send
Maquina Local : bind Maquina Remota : listen y accept
Maquina Local : bind Maquina Remota : recvfrom
6.1.2 CONEXIÓN DE UN SOCKET :
Un programa cliente orientado a conexión utiliza la función connect para configurar un socket, y requiere como parámetros , el identificador de socket, que es el valor del descriptor del socket que devolvió la función socket. Como segundo parametro, utiliza la dirección del socket remoto, es decir la dirección IP del host remoto y el puerto de protocolo. El tercer parámetro se refiere a la longitud de la dirección, el tamaño en bytes de la dirección del socket remoto.
result = connect(socket_handle, remote_socket_address, address_length);
En la mayoria de los casos un programa cliente orientado a conexión no especifica puerto de protocolo local, ya que puede recibir datos en cualquier puerto de protocolo. Es decir, no es necesario almacenar las direcciones IP locales, la implementación del socket lo hace automaticamente y selecciona por si misma un puerto de protocolo local.
En el caso de clientes no orientados a conexión o servidores en general tienen que atender a un puerto de protocolo las solicitudes que les pueden llegar.
La función de asignación de nombres, bind, en la API de sockets permite a un programa asociar una dirección local con un socket :
Result = bind(socket_handle, local_socket_address, address_lenght);
De esta manera se le comunica a la implementación del socket , que puerto de protocolo utilizar para la entrega de datos.
6.1.3 TRANSMISION DE DATOS :
Se proporcionan cinco funciones para transmitir datos a través de un socket y se dividen en dos grupos. Existen tres funciones que requieren una dirección de destino como parámetro, las restantes que son las utilizadas en los procesos orientados a conexión, no lo precisan.
FUNCION |
DESCRIPCIÓN |
send |
Transmite datos a través de un socket de conexión. |
write |
Transmite datos a través de un socket de conexión utilizando un buffer de datos simple. |
writev |
Transmite datos a través de un socket de conexión utilizando bloques de memoria no contiguos. |
sendto |
Transmite datos a través de un socket sin conexión utilizando un buffer de mensajes simple. |
sendmsg |
Transmite datos a través de un socket sin conexión, utilizando una estructura de mensajes flexible como buffer de mensajes |
Las funciones del API de sockets que hacen transmisiones de datos orientadas a conexión no requieren que el programa especifique una dirección destino como parametro. Las funciones send, write y writev sólo trabajan con sockets conectados, y tiene la siguiente estructura :
Result= write(socket_handle, message_buffer, buffer_length);
El primer parametro es el identificador de socket, el segundo es el búfer de mensajes, que apunta al búfer de datos que contiene la información a transmitir. El tercer parametro es el tamaño del búfer de datos.
Por otro lado la función writev no requiere que los datos ocupen bloques de memoria contiguos como si lo hace la función write. Así writev permite que se especifique una tabla de direcciones que contenga los datos.
Result= writev(socket_handle, io_vector, vector_length);
También requiere como primer parametro , un identificador de socket , el segundo parametro especifica la dirección de una tabla que contiene una secuencia de apuntadores. Cuando la función writev transmita los datos, enviará la información contenida en cada localidad de memoria especificada por el array de apuntadores, transmitiéndolos en el mismo orden en que aparecen en el array.
La función send es otra función del interface de sockets para utilizar con sockets orientados a conexión :
Result=send(socket(socket_handle, message_buffer,buffer_length,special_flags);
Con send se pueden especificar banderas opcionales para controlar la transmisión, como la gestión de datos urgentes (fuera de banda).
Las tres funciones write, writev y send , devuelven un valor entero, que es el numero de bytes transmitidos por el socket. En caso de que exista un error devuelven : -1 .
Para enviar datos a través de un socket sin conexión se dispone de las funciones , sendto y sendmsg :
Result=sendto(socket_handle,message_buffer,buffer_length,special_flags, socket_address_structure, address_structure_length);
Requiere 6 parametros, entre los nuevos que aparecen ahora, la dirección de destino (socket_address_structure, y la longitud de la misma en bytes (address_structure_length).
La función sendmsg nos permite utilizar para la transmisión una estructura de mensaje, en lugar de un simple buffer de datos. Requiere como parametros un identificador de socket, un puntero a la estructura del mensaje (message_structure) y banderas especiales (special_flags).
Result= sendmsg(socket_handle, message_structure, special_flags);
6.1.4 RECEPCIÓN DE DATOS POR UN SOCKET :
La Interface de Sockets , incluye cinco funciones para la recepción de datos, que se corresponden con las anteriores vistas en la transmisión de datos :
Funciones de Transmisión |
Funciones de Recepción |
send |
recv |
write |
read |
writev |
readv |
sendto |
recvfrom |
sendmsg |
recvmsg |
A parte de estas correspondencias , hay que tener en cuenta que no es indispensable utilizarlas unas con otras , ya que una vez se han enviado datos , para recibirlos se puede realizar con cualquiera de las funciones correspondientes al tipo de servicio , orientado o no a conexión.
6.1.5 DESCRIPCIÓN DEL PROCESO :
La parte izquierda muestra las llamadas a funciones del servidor , mientras que la derecha
muestra las llamadas a funciones del cliente.
El programa servidor solicita a la implementación del socket que le asigne una estructura de datos para el socket y que le devuelva un descriptor de sockets para utilizarlo en las siguientes llamadas a funciones de la interfaz de sockets.
Después el servidor une el socket a un puerto de protocolo local. La función listen indica al socket que atienda las conexiones entrantes y que confirme las solicitudes de conexión y se encarga de poner al socket en modo de atención pasiva.
Result = listen(socket_handle, queue_length);
Donde el segundo parametro queue_length , nos permite especificar el número máximo de solicitudes que pueden acumularse en la cola. Después de configurar una cola de datos entrantes, el programa servidor llamará a la función accept , cesa su actividad y espera una solicitud de conexión de un programa cliente.
Result = accept (socket_handle, socket_address, address_length);
El programa cliente también crea un socket , pero no necesita ocuparse de que dirección local usará el protocolo ya que utiliza un protocolo orientado a conexión, por lo tanto no llama a la función bind. Lo que hace es iniciar la conversación en red llamando a la función connect.
Después de que el cliente y el servidor establecen la conexión , pueden ocurrir comunicaciones adicionales a través de las funciones write y read.
6.2 LA API WINDOWS SOCKETS :
El software llamado Windows Sockets o Winsock es una API (Application Program Interface) , Interface de programas de aplicación para redes TCP/IP, y especifica la familia de sistemas operativos de Microsoft Windows en todas sus versiones.
Windows Sockets implementa la interface de sockets como una biblioteca de enlace dinamico, una DLL (Dynamic Lynk Library), que no es más que un módulo ejecutable que el sistema puede cargar en cualquier momento.
La API Winsock proporciona una biblioteca de funciones que se divide en tres grupos :
1. Funciones de los Berkeley Sockets.
2. Funciones de Bases de Datos que permiten obtener información relacionada con el DNS, servicios de comunicaciones y protocolos.
3. Las extensiones específicas de Windows a las rutinas de los Sockets de Berkeley.
Distinguimos entre las funciones de bloqueo , que son aquellas que evitan que se llame a cualquier otra función hasta que esta termine sus propias operaciones de red. Y las de no bloqueo que terminan de inmediato o emiten un mensaje de error.
6.2.1 FUNCIONES SOCKET :
Función |
Descripción |
accept |
Confirma una conexión de entrada. Crea un socket nuevo y lo conecta al host remoto que pidió la conexión. Devuelve el socket original a su estado de atender. |
closesocket |
Cierra un extremo de una conexión por sockets. |
connect |
Inicia una conexión en el socket especificado. |
recv |
Recibe información de un socket conectado. |
recvfrom |
Recibe información de un socket conectado o de uno no conectado. |
select |
Ejecuta multiplexaje sincrono de E/S al monitorear el estado de múltiples sockets. |
send |
Envia información a un socket conectado. |
sendto |
Envia información a un socket conectado o a uno no conectado. |
Las funciones vistas en la tabla anterior , se pueden clasificar como de bloqueo porque requieren comunicación con el anfitrión remoto. Mientras que las que aparecen en la tabla siguiente sólo utilizan información almacenada de manera local, o bien sólo trabajan con el extremo de una conexión del socket de su computadora.
Función |
Descripción |
bind |
Asigna un nombre local a un socket sin nombre. |
getpeername |
Obtiene el nombre del par conectado al socket especificado. |
getsockname |
Obtiene el nombre local para el socket especificado. |
getsockopt |
Obtiene opciones asociadas con el socket especificado. |
htonl |
Convierte un número de 32 bits de ordenamiento de byte de anfitrión a ordenamiento de byte de red. |
htons |
Convierte un número de 16 bits de ordenamiento de byte de anfitrión a ordenamiento de byte de red. |
inet_addr |
Convierte una cadena de caracateres que representa una dirección IP en notación decimal al valor binario de 32 bits. |
inet_ntoa |
Convierte una dirección IP a notación decimal. |
ioctlsocket |
Controla varios parametros relacionados con la forma de operar del socket y el manejo de la E/S de red. |
listen |
Indica a un socket específico que atienda las conexiones entrantes. |
ntohl |
Convierte un número de 32 bits de ordenamiento de byte de red a ordenamiento de byte de anfitrión. |
ntohs |
Convierte un número de 16 bits de ordenamiento de byte de red a ordenamiento de byte de anfitrión. |
setsockopt |
Almacena opciones asociadas con el socket especificado. |
shutdown |
Cierra parte de una conexión full duplex. |
socket |
Crea un extremo para la comunicación y devuelve un identificador de socket. |
6.2.2 FUNCIONES DE BASE DE DATOS :
Las funciones de base de datos de Winsock , permiten obtener información sobre nombres de dominio, servicios de comunicación y protocolos .
Función |
Descripción |
gethostbyaddr |
Obtiene nombre de dominio y dirección IP correspondiente a una dirección de red. |
gethostbyname |
Obtiene nombre de dominio y dirección IP correspondiente a un nombre de anfitrión. |
gethostname |
Obtiene el nombre de dominio del anfitrión local. |
getprotobyname |
Obtiene un protocolo por nombre y devuelve el nombre oficial y el número definido para representar al protocolo. |
getprotobynumber |
Obtiene el nombre y número de protocolo representado por un número específico. |
getservbyname |
Obtiene un nombre de servicio y el puerto de protocolo correspondiente al nombre del servicio. |
getservbyport |
Obtiene el nombre del servicio y el puerto correspondiente a un puerto de protocolo especifico. |
Algunas de las funciones de bases de datos de Winsock devuelven estructuras de datos volátiles, ya que sólo guardan los resultados hasta la próxima llamada a otra función Winsock, por lo tanto estos resultados debe guardarse en un lugar diferente de la memoria. Para ello tenemos las versiones asincronas de estas funciones que nos permiten aprovechar el despliegue de mensajes dentro de Windows.
Función |
Descripción |
WSAAsyncSelect |
Ofrece una versión asincrona de la función Select. |
WSACancelAsyncRequest |
Cancela una instancia de la función WSAAsyncGetXByY |
WSACancelBlockingCall |
Cancela una llamada de bloqueo de la API |
WSACleanup |
Termina sesión desde los DLL subyacentes de Winsock |
WSAGetLastError |
Obtiene detalles sobre el último error de la API Winsock. |
WSAIsBlocking |
Determina si el DLL de Winsock subyacente está bloqueado. |
WSASetLastError |
Establece el regreso de error para WSAGetLastError subsecuente. |
WSAStartup |
Inicia el DLL de Winsock subyacente. |
WSAUnhookBlockingHook |
Restaura la función original de bloqueo. |
6.2.3 PROGRAMACIÓN CON WINDOWS SOCKETS :
Los programas de red basados en la API Windows Sockets deben incluir un encabezado de archivo llamado winsock.h :
#include<winsock.h>
La API de Windows Sockets requiere dos funciones especificas de windows, que son:
.WSAStartup : Se debe llamar a esta función antes de llamar a cualquier otra de Winsock. Nos permite especificar que versión de la API Windows Sockets va a necesitar nuestro programa. Se establece una negociación entre la aplicación y Winsock.dll .
.WSACleanup : Se debe colocar por cada llamada que se haga a WSAStartup. Cuando se llama a la funcion WSACleanup por última vez, winsock desconecta cualquiera de los sockets de flujo de bytes existentes. Aunque respeta la información pendiente.
El descriptor de Socket : Winsock define SOCKET como un tipo de datos sin signo y emplea la constante INVALID_SOCKET para identificar a los no válidos.
Un socket válido está en el rango desde 0......INVALID SOCKET-1 .
Para el caso de que ocurra un error de socket , winsock nos proporciona la constante SOCKET_ERROR , que define como –1, y para identificar la condición concreta del error nuestra aplicación deberá llamar a la función WSAGetLastError, que se encarga de obtener el último error que ocurrió en la red.
La función Select : Esta función deja que un solo proceso monitoree o determine el estado de multiples sockets. Un conjunto es una lista específica de sockets que Winsock monitorea para revisar los cambios de estado. Para manipular estos conjuntos Winsock se vale de las siguientes macros :
MACRO |
FUNCION |
FD_CLR |
Borra un identificador de socket de la lista. |
FD_ISSET |
Devuelve un valor diferente de cero (TRUE) si el identificador de socket está establecido y cero (FALSE) si no lo está. |
FD_SET |
Agrega un identificador de socket a una lista. |
FD_ZERO |
Inicializa un conjunto de identificadores. |
Winsock proporciona una versión asíncrona de SELECT , WSAAsyncSelect para las operaciones de socket de no bloqueo en Winsock.
Nuestra aplicación puede llamar a la función WSAAsyncSelect para cambiar la operación del socket de bloqueo a no bloqueo. Teniendo en cuenta que WSAAsyncSelect sólo acepta un identificador de socket a la vez como parámetro.
WSAAsyncSelect (SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
El parametro lEvent es una mascara de bits que especifica que combinación de eventos de la red se desean revisar. Sus valores pueden ser :
Constante |
Significado |
FD_READ |
Solicita notificación de que está listo para leer. |
FD_WRITE |
Solicita notificación de que está listo para escribir. |
FD_OOB |
Solicita un aviso de la llegada de información fuera de banda. |
FD_ACCEPT |
Solicita un aviso de las conexiones entrantes. |
FD_CONNECT |
Solicita un aviso de las conexiones terminadas. |
FD_CLOSE |
Solicita un aviso de la terminación de socket. |
El segundo parametro hWnd especifica el identificador de la ventana que recibirá el mensaje. (parametro wMsg)
El tercer parametro wMsg , define el mensaje que se quiere que envie Windows cuando el evento especificado ocurra en la red.
Cuando se llama a la función WSAAsyncSelect, Winsock designa al socket que se especifica como de no bloqueo. Por ejemplo si se quiere realizar una operación usando la función recv , evitando operaciones de bloqueo. Se llamará a la función WSAAsyncSelect, que se encargará a su vez de decirle a Windows que notifique a la aplicación en cuestión cuando el socket está listo para leer. Cuando el socket recibe la información, Windows enviará el identificador de mensaje correspondiente a la ventana que especifique la función WSAAsyncSelect . Cuando el procedimiento de manejo del mensaje para esa ventana reciba el mensaje, la aplicación podrá ejecutar un código que llame a la función recv.
. Hook de Bloqueo : Winsock nunca permite que una operación de bloqueo ocurra dentro de Windows. Cuando un programa llama a una función que causaría una operación de este tipo, Winsock entra en un ciclo y llama de forma repetitiva a un identificador de bloqueo o rutina hook, cuyo proposito es interceptar las llamadas a funciones que causan una operación de bloqueo.
El identificador de bloqueo estandar de Winsock incluye un código análogo a las siguientes instrucciones :
If (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
La función PeekMessage revisa la cola de mensajes de la aplicación, en caso de que existan mensajes, lo coloca en una estructura MSG y devuelve un valor que no sea cero.
De todas formas el hook de bloqueo no resuelve el problema en su totalidad , ya que cuando se ejecuta es posible que aparezca un mensaje de Windows para la tarea en curso, que puede causar que la aplicación llame a otra función de Winsock. Con lo cual se tiene que siempre que una operación de bloqueo de Winsock esté en progreso, no se podrá llamar desde la aplicación a otras funciones de Winsock.
Winsock proporciona dos funciones para detectar y manejar operaciones de bloqueo, ninguna de las funciones requiere parámetros :
. WSAIsBlocking : Se llama a esta función para determinar si está en progreso una operación de bloqueo. Devolverá TRUE si es así y FALSE , en caso contrario.
. WSACancelBlockingCall : Se llamará a esta función para cancelar la operación de bloqueo que esté en curso. La llamada a la función que inició la operación de bloqueo coge el valor de error WSAEINTR.
La API de Winsock incluye dos funciones avanzadas para manejar este tipo de operaciones :
. WSASetBlockingHook : Nos permite definir nuestra propia rutina de bloqueo.
. WSAUnhookBlockingHook : Se encarga de restaurar el hook de bloqueo predeterminado.
Para los sistemas operativos Windows95 y WindowsNT , el bloqueo ocurre en base a cada tarea. Winsock no incluye un hook de bloqueo para las versiones multitarea de Windows, cuando sucede una operación de bloqueo todas las demás actividades en la tarea se detienen hasta que esta concluye. De todas formas Winsock permite utilizar en tales versiones de Windows la función WSASetBlockingHook para implementar nuestro propio hook de bloqueo.