У меня есть серверное приложение, которое использует boost ASIO для связи с несколькими клиентами. Серверное приложение работает на сервере Linux, а клиенты-на рабочих столах Windows.
Текущая конструкция многопоточна, хотя есть только один boost ASIO thead (который работает boost::asio::io_context
). Поток boost ASIO отвечает только за чтение, запись и некоторые нечастые отправки. Чтение выполняется с помощью boost::asio::async_read
но копирует полученное сообщение, чтобы другой поток мог выполнить работу по обработке. Написание осуществляется с помощью boost::asio::write
но сообщение уже скопировано и передано в поток boost ASIO
В большинстве случаев, когда клиент отключается, boost ASIO выдает ошибку, я отключаю соответствующий сокет, а другие сокеты продолжают работать. Однако, если на рабочем столе Windows клиента произошел сбой питания во время boost::asio::write
пишет им, то boost не обнаруживает проблему и зависает в boost::asio::write
. Иногда он зависает почти на 20 минут, и в течение этого времени сервер не может взаимодействовать с другими клиентами
Из того, что я прочитал в Интернете, авторы boost ASIO не намерены вводить параметр тайм-аута. Я попытался установить значение SO_SNDTIMEO равным 5 секундам, но это никак не повлияло на зависание записи. На данный момент я лучше всего предполагаю, что для решения этой проблемы нужно предоставить каждому сокету отдельный поток, чтобы один клиент не мог отключить других клиентов. Есть ли какие-то лучшие варианты, чем этот? Если я дам каждому сокету свой собственный поток, означает ли это, что мне понадобится boost::asio::io_context
для каждого потока, чтобы избежать зависания записи?
Изменить: После просмотра комментариев я попытался повторить функцию, которая вызывает boost::asio::write
с boost::asio::async_write
. Ниже у меня есть код, который был упрощен для SO, но все еще показывает, каково было общее изменение:
Первоначально с 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();
});
}
Переделано с 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();
}
);
});
}
Блокировка была введена во втором коде, чтобы гарантировать только один вызов boost::asio::async_write
был активен в то время (я знаю, что есть более эффективные способы сделать это, но это проще для тестирования). Оба этих кода имеют одну и ту же проблему зависания boost ASIO при сбое питания клиента. Однако они зависают по-разному, асинхронный код позволяет boost ASIO выполнять другие действия, просто не записывает дальше, пока зависание не приведет к ошибке
Во время отдельного эксперимента я попытался установить SO_KEEPALIVE
но это также не решило проблему зависания