¿Cómo señalar select () para regresar de inmediato?

6 minutos de lectura

Tengo un subproceso de trabajo que escucha un socket TCP para el tráfico entrante y almacena en búfer los datos recibidos para que acceda el subproceso principal (llamemos a este socket A). Sin embargo, el subproceso de trabajo también tiene que realizar algunas operaciones regulares (digamos, una vez por segundo), incluso si no ingresan datos. Por lo tanto, uso select() con un tiempo de espera, para que no tenga que seguir sondeando. (Tenga en cuenta que llamar receive() en un socket sin bloqueo y luego dormir por un segundo no es bueno: los datos entrantes deben estar disponibles de inmediato para el subproceso principal, aunque es posible que el subproceso principal no siempre pueda procesarlos de inmediato, de ahí la necesidad de almacenamiento en búfer. )

Ahora, también necesito poder indicarle al subproceso de trabajo que haga otras cosas de inmediato; del subproceso principal, necesito hacer que el subproceso de trabajo select() volver enseguida. Por ahora, he resuelto esto de la siguiente manera (enfoque básicamente adoptado de aquí y aquí):

Al iniciar el programa, el subproceso de trabajo crea para este propósito un socket adicional del tipo de datagrama (UDP) y lo vincula a algún puerto aleatorio (llamemos a este socket B). Asimismo, el hilo principal crea un socket de datagrama para enviar. En su llamado a select()el subproceso de trabajo ahora enumera ambos A y B en el fd_set. Cuando el subproceso principal necesita enviar una señal, sendto()‘un par de bytes al puerto correspondiente en localhost. De vuelta en el subproceso de trabajo, si B permanece en el fd_set después select() regresa, entonces recvfrom() se llama y los bytes recibidos simplemente se ignoran.

Esto parece funcionar muy bien, pero no puedo decir que me guste la solución, principalmente porque requiere vincular un puerto adicional para By también porque agrega varias llamadas API de socket adicionales que pueden fallar, supongo, y realmente no tengo ganas de averiguar la acción adecuada para cada uno de los casos.

Creo que idealmente, me gustaría llamar a alguna función que tome A como entrada, y no hace nada excepto hace select() volver enseguida. Sin embargo, no conozco esa función. (Supongo que podría por ejemplo shutdown() el zócalo, pero los efectos secundarios no son realmente aceptables 🙂

Si esto no es posible, la segunda mejor opción sería crear un B que es mucho más tonto que un socket UDP real, y realmente no requiere asignar recursos limitados (más allá de una cantidad razonable de memoria). supongo Conectores de dominio Unix haría exactamente esto, pero: la solución no debería ser mucho menos multiplataforma que la que tengo actualmente, aunque una cantidad moderada de #ifdef las cosas estan bien (Me estoy dirigiendo principalmente a Windows y Linux, y por cierto, escribo C++).

No sugiera refactorizar para deshacerse de los dos hilos separados. Este diseño es necesario porque el subproceso principal puede estar bloqueado durante períodos prolongados (por ejemplo, al hacer algunos cálculos intensivos, y no puedo comenzar a llamar periódicamente receive() desde el ciclo de cálculo más interno), y mientras tanto, alguien necesita almacenar en búfer los datos entrantes (y debido a razones que escapan a mi control, no puede ser el remitente).

Ahora que estaba escribiendo esto, me di cuenta de que alguien definitivamente va a responder simplemente “Boost.asio“, así que acabo de verlo por primera vez… Sin embargo, no pude encontrar una solución obvia. Tenga en cuenta que tampoco puedo (fácilmente) afectar cómo el socket A se crea, pero debería poder dejar que otros objetos lo envuelvan, si es necesario.

avatar de usuario
alex b

Ya casi has llegado. Utilizar una truco de “auto-tubería”. Abra una tubería, agréguela a su select() Lee y escribe fd_set, escríbalo desde el subproceso principal para desbloquear un subproceso de trabajo. Es portátil a través de los sistemas POSIX.

He visto una variante de una técnica similar para Windows en un sistema (de hecho, se usa junto con el método anterior, separados por #ifdef WIN32). El desbloqueo se puede lograr agregando un socket de datagrama ficticio (no vinculado) a fd_set y luego cerrándolo. La desventaja es que, por supuesto, tienes que volver a abrirlo cada vez.

Sin embargo, en el sistema mencionado anteriormente, ambos métodos se usan con bastante moderación y para eventos inesperados (por ejemplo, señales, solicitudes de finalización). El método preferido sigue siendo un tiempo de espera variable para select()dependiendo de qué tan pronto se programe algo para un subproceso de trabajo.

  • Gracias, pero como se especifica en la pregunta, la solución final también debería funcionar en Windows.

    – Reunión

    21 de diciembre de 2008 a las 12:31

  • Ok, gracias por la sugerencia de Windows. ¿Alguna idea de lo costosa que es la reapertura en la práctica?

    – Reunión

    21 de diciembre de 2008 a las 12:48

  • Gracias Checkers, crear y cerrar el socket (no vinculado) parece funcionar muy bien.

    – Reunión

    21 de diciembre de 2008 a las 15:21

  • No tengo números concretos, pero dado que está escribiendo para Windows, casi siempre significa escritorio, lo que significa que el costo debería ser insignificante.

    – Alex B.

    22 de diciembre de 2008 a las 7:30

  • En Windows, también puede mantener abierto un socket UDP y enviarle un paquete para hacer que select() regrese listo para leer en ese socket, o puede crear un par de sockets TCP que están conectados entre sí y enviar un byte en uno de ellos mientras select() está monitoreando al otro.

    –Jeremy Friesner

    29 de diciembre de 2018 a las 0:54

Usar una tubería en lugar de un zócalo es un poco más limpio, ya que no hay posibilidad de que otro proceso se apodere de él y arruine las cosas.

El uso de un socket UDP definitivamente crea la posibilidad de que entren e interfieran paquetes extraviados.

Una tubería anónima nunca estará disponible para ningún otro proceso (a menos que se la dé).

También puede usar señales, pero en un programa de subprocesos múltiples querrá asegurarse de que todos los subprocesos, excepto el que desea, tengan esa señal enmascarada.

En Unix será sencillo con el uso de una tubería. Si está en Windows y desea seguir usando la declaración de selección para mantener su código compatible con Unix, el truco para crear un socket UDP no vinculado y cerrarlo funciona bien y es fácil. Pero tienes que hacerlo multi-threadsafe.

La única forma que encontré para hacer que esto sea seguro para subprocesos múltiples es cerrar y volver a crear el socket en el mismo subproceso en el que se está ejecutando la declaración de selección. Por supuesto, esto es difícil si el hilo se bloquea en la selección. Y luego viene en la llamada de Windows QueueUserAPC. Cuando Windows se bloquea en la declaración de selección, el subproceso puede manejar llamadas de procedimiento asíncrono. Puede programar esto desde un hilo diferente usando QueueUserAPC. Windows interrumpe la selección, ejecuta su función en el mismo hilo y continúa con la declaración de selección. Ahora puede, en su método APC, cerrar el zócalo y volver a crearlo. Seguro para subprocesos garantizado y nunca perderá una señal.

avatar de usuario
bruce lu

Para ser simple: una var global guarda el identificador del socket, luego cierra el socket global, el select() regresará inmediatamente: closesocket(g_socket);

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad