Resumen

  • Windows Sockets 1.1 dio a los programadores de Windows una interfaz TCP/IP basada en Berkeley, pero no pudo copiar sin cambios el comportamiento bloqueante de Berkeley en Windows de 16 bits.
  • Una llamada Winsock bloqueada debía seguir distribuyendo mensajes de Windows. Así el escritorio cooperativo permanecía activo, aunque con límites estrictos para las llamadas anidadas, la reentrada y la vida útil de los búferes.

Se suponía que el socket ocultaba la red

Un socket ofrece al programa una manera estable de enviar y recibir datos sin atarlo al proveedor de la pila de red subyacente. Los desarrolladores de Windows querían ese trato. También querían partir de un modelo conocido: los sockets de Berkeley ya eran el modelo estándar de programación TCP/IP en Unix.

El proyecto Windows Sockets comenzó en una sesión Birds of a Feather de Interop ’91, en San José, el 10 de octubre de 1991. La especificación 1.1 registra lo que siguió: un comité, debates por correo electrónico, un primer borrador basado en buena medida en propuestas de JSB y NetManage, una reunión organizada por Microsoft en diciembre y un grupo de trabajo que continuó desarrollando el documento. Fue una interfaz construida por un grupo, no un protocolo TCP/IP impuesto por un único proveedor.

La versión 1.0 se publicó para que los proveedores de software de red y los desarrolladores de aplicaciones pudieran empezar a crear implementaciones y programas conforme a un contrato común. La versión 1.1, fechada el 20 de enero de 1993, introdujo aclaraciones y correcciones menores a partir de la experiencia de las empresas que habían implementado la 1.0. El orden importa: antes de cerrar el texto siguiente, el grupo ya lo había contrastado con código en funcionamiento.

La especificación adoptó el modelo de sockets de Berkeley para resultar familiar a los programadores de Unix y facilitar el traslado del código fuente. Definió la suite de protocolos de Internet, con sockets de flujo TCP y sockets de datagramas UDP. No fingió que la versión 1.1 normalizaba todas las redes posibles. Ese límite era útil: los desarrolladores obtenían una interfaz conocida, sin la promesa de que todos los sistemas situados debajo se comportarían igual.

Entonces recv se encontró con un escritorio de 16 bits

La parte incómoda aparece cuando un programa espera. Un recv bloqueante no retorna hasta que llegan datos o la operación se resuelve de otro modo. En un sistema que puede interrumpir una tarea y ejecutar otra, el sistema operativo puede planificar otros trabajos mientras ese proceso espera. Windows Sockets 1.1 no podía suponer que Windows de 16 bits permitiera ese modelo.

La documentación de Microsoft describe una solución poco común. Si una operación bloqueante no podía terminar de inmediato, el proveedor de servicio la iniciaba y entraba en un ciclo: distribuía mensajes de Windows, comprobaba si había terminado la llamada al socket y repetía. Así la aplicación basada en mensajes podía seguir atendiendo los eventos de sus ventanas mientras la lectura de red seguía pendiente.

Ese diseño no convirtió un programa de 16 bits en un sistema donde Windows pudiera ejecutar otras tareas mientras la aplicación esperaba. Insertó la distribución de mensajes de Windows dentro de una llamada que un programador podía haber tratado como una simple espera. Los mensajes que llegaran durante esa espera podían hacer que la aplicación intentara otra llamada Winsock. La versión 1.1 prohibía anidarlas: una tarea solo podía tener una llamada Winsock pendiente. Una segunda llamada anidada fallaba con WSAEINPROGRESS.

La API ofrecía dos pequeñas salidas. WSAIsBlocking permitía comprobar si había una llamada en curso. WSACancelBlockingCall permitía cancelarla. Las aplicaciones más complejas, incluidas las que manejaban varias ventanas de documentos, podían instalar WSASetBlockingHook y sustituir la rutina predeterminada de distribución de mensajes. La aplicación ganaba control y asumía también la responsabilidad de atender bien esos mensajes.

La interfaz trasladó trabajo; no lo borró

Las llamadas no bloqueantes y asíncronas evitaban el problema de reentrada del bloqueo. Traían otra obligación. Si una operación conservaba un puntero a un búfer u otro objeto de memoria, la aplicación debía mantenerlo válido hasta que terminara. La biblioteca de red no podía decidir por ella cuánto duraba esa vida útil.

Esa es la brecha, pequeña pero decisiva, entre una API familiar y un programa portable. Unos nombres de función coincidentes pueden reducir el coste inicial de trasladar código. No eliminan el modelo de planificación del sistema anfitrión, su cola de mensajes ni la vida útil de la memoria. Microsoft documentó después los archivos de cabecera que facilitaban portar código BSD, y mantuvo las declaraciones antiguas de Winsock.h para dar compatibilidad a aplicaciones anteriores cuando Winsock 2 introdujo Winsock2.h. La propia compatibilidad se convirtió en software que había que conservar.

La versión 1.1 fijó su frontera en la interfaz y en los tipos de socket TCP/IP que admitía. Los proveedores aportaban las implementaciones; los desarrolladores de aplicaciones elegían cómo esperar, cancelar, distribuir mensajes y proteger la memoria. La especificación común facilitaba conectar esas piezas. No volvía intercambiables sus responsabilidades.

La decisión de ingeniería llamativa no fue que Windows copiara a Berkeley. El grupo conservó suficiente vocabulario de programación de Berkeley para hacer atractivo el port, y documentó dónde Windows no podía hacerse pasar por Unix. La cola de mensajes fue el recibo: una interfaz abierta redujo un coste y mostró dónde cayó el siguiente.

Fuentes