Tengo una aplicación de servidor que utiliza impulso ASIO para comunicarse con varios clientes. La aplicación de servidor se ejecuta en un servidor Linux y los clientes que se ejecutan en los escritorios de Windows.
El diseño actual es multi-hilo aunque sólo hay un impulso ASIO thead (que se ejecuta boost::asio::io_context
). El impulso ASIO hilo sólo es responsable de la lectura, la escritura, y algunos poco frecuentes de envío. La lectura se realiza mediante boost::asio::async_read
pero copia el mensaje resultante, de modo que otro subproceso puede hacer el trabajo de procesamiento. La escritura se realiza mediante boost::asio::write
pero el mensaje ya ha sido copiado y pasa a las manos del impulso ASIO hilo
Bajo la mayoría de circunstancias cuando un cliente se desconecta del impulso ASIO lanza un error, puedo cerrar el socket asociado, y los otros zócalos de seguir trabajando. Sin embargo, si un cliente de escritorio de Windows tiene un fallo de alimentación mientras boost::asio::write
está escrito para ellos, a continuación, impulsar no detecta un problema y se cuelga en boost::asio::write
. Se cuelga durante casi 20 minutos y a veces el servidor no puede comunicarse con otros clientes durante este tiempo
Por lo que he leído en línea que los autores de impulso ASIO no tienen la intención de introducir un parámetro de tiempo de espera. Traté de configuración SO_SNDTIMEO a 5 segundos, pero que no tienen ningún efecto en la escritura de colgar. A partir de ahora mi mejor estimación para resolver el problema es dar a cada socket en un subproceso diferente para que un cliente no puede acabar con los otros clientes. Hay mejores opciones que este? Si me dan cada socket su propio hilo significa eso que voy a necesitar una boost::asio::io_context
por el hilo para evitar que la escritura se bloquea?
Edit: Después de ver los comentarios que he tratado de rehacer la función que llama a boost::asio::write
con boost::asio::async_write
. A continuación tienes algún código que se ha simplificado para tanto, pero todavía muestra lo que el cambio general fue:
Originalmente con boost::asio::write
:
inline void MessagingServer::writeMessage(
GuiSession* const a_guiSession,
const PB::Message& a_msg
) {
boost::asio::dispatch(m_guiIoIoContext, [this, a_guiSession, a_msg]() {
// I removed code that writes a_msg's bytes into m_guiIoWriteBuf
// and sets totalSize to simplify for SO
boost::system::error_code error;
boost::asio::write(a_guiSession->m_guiIoGsSocket, boost::asio::buffer(m_guiIoWriteBuf, totalSize), error);
if (UNLIKELY(error))
ERRLOG << a_guiSession->m_gsSessionId << " write failed: " << error.message();
});
}
Remodelada con boost::asio::async_write
:
inline void MessagingServer::writeMessage(
GuiSession* const a_guiSession,
const PB::Message& a_msg
) {
a_guiSession->m_tempMutex.lock();
boost::asio::dispatch(m_guiIoIoContext, [this, a_guiSession, a_msg]() {
// I removed code that writes a_msg's bytes into m_guiIoWriteBuf
// and sets totalSize to simplify for SO
boost::asio::async_write(
a_guiSession->m_guiIoGsSocket,
boost::asio::buffer(m_guiIoWriteBuf, totalSize),
[this, a_guiSession](const boost::system::error_code& a_error, std::size_t) {
if (UNLIKELY(a_error))
ERRLOG << a_guiSession->m_gsSessionId << " write failed: " << a_error.message();
a_guiSession->m_tempMutex.unlock();
}
);
});
}
El bloqueo fue introducido en el segundo código para garantizar a sólo una llamada a boost::asio::async_write
se activa en un momento (soy consciente de que hay más eficientes maneras de hacer esto, pero este es más sencillo para las pruebas). Ambos de estos códigos tienen el mismo número de colgantes impulso ASIO cuando el cliente tiene una falla de energía. Sin embargo, se cuelgan en diferentes formas, el código asíncrono permite aumentar ASIO para realizar otras acciones, simplemente no escribe hasta que la cortina se produce un error
Durante un experimento separado, me hizo intentar establecer SO_KEEPALIVE
pero que también no se soluciona el problema de bloqueo