Russian Qt Forum

Qt => Работа с сетью => Тема начата: Ground от Июль 08, 2012, 07:22



Название: [Решено] Проблема с сервером, зависшие клиенты
Отправлено: Ground от Июль 08, 2012, 07:22
Доброго времени суток!
Появилось у меня два вопроса, на которые без посторонней помощи не могу найти ответа. Итак:
1. Первый вопрос - общий. У меня есть класс на базе QTcpServer. В нем массив клиентских сокетов (QTcpSocket). Так вот, при отключении клиента, я выполняю следующий код:
Код
C++ (Qt)
       if (!mClients[i].isNull())
       {
           // Слот - "Отключение клиента"
           disconnect(mClients[i], SIGNAL(disconnected()),
                      this, SLOT(slotRejectClient()));
           // Слот - "Получение новых данных"
           disconnect(mClients[i], SIGNAL(readyRead()),
                      this, SLOT(slotReceiveData()));
           // Слот - "Ошибка сокета"
           disconnect(mClients[i], SIGNAL(error(QAbstractSocket::SocketError)),
                      this, SLOT(slotClientSocketError(QAbstractSocket::SocketError)));
 
           mClients[i]->close();
           mClients[i]->deleteLater();
           mClients[i] = 0;
Таким образом, я сокет закрываю, и вызываю метод отложенного удаления. Вопрос - можно ли сразу после deleteLater занулять указатель? Утечки не будет? Просто при вызове deleteLater не срабатывает QPointer, и автоматически не зануляет указатель.

2. Второй вопрос - он же основной. При подключении клиента у меня выполняется следующий код:
Код
C++ (Qt)
// Подключение клиента к серверному сокету
int Server::slotAcceptClient()
{
   // Превышение максимального количества активных клиентов
   if (mActiveClients == mMaxClients)
   {
       QPointer<QTcpSocket> client = QPointer<QTcpSocket>(mServer->nextPendingConnection());
       client->write(createCommandPackage(CommandType::ConnectionLimit));
 
       emit signalSendTextToLog(QString("Перегрузка сервера! Подключение клиента с адреса <b>%1</b> отклонено!")
                                .arg(client->peerAddress().toString()));
       client->close();
       client->deleteLater();
 
       return 0;
   }
 
   bool result = false;
   int clientNumber = 0;
   // Поиск свободного места в массиве для сохранения информации о клиенте
   for (int i = 0; i < mMaxClients; i++)
   {
       if (!mClients[i].isNull())
           continue;
       clientNumber = i;
       result = true;
       break;
   }
 
   if (!result)
       return -1;
 
   mClients[clientNumber] = QPointer<QTcpSocket>(mServer->nextPendingConnection());
 
   // Слот - "Отключение клиента"
   connect(mClients[clientNumber], SIGNAL(disconnected()),
           this, SLOT(slotRejectClient()));
 
   // Слот - "Получение новых данных"
   connect(mClients[clientNumber], SIGNAL(readyRead()),
           this, SLOT(slotReceiveData()));
 
   // Слот - "Ошибка сокета"
   connect(mClients[clientNumber], SIGNAL(error(QAbstractSocket::SocketError)),
           this, SLOT(slotClientSocketError(QAbstractSocket::SocketError)));
 
   mActiveClients++;
 
   emit signalSendTextToLog(QString("Подключен клиент №%1 (<b>%2</b>)")
                            .arg(clientNumber)
                            .arg(mClients[clientNumber]->peerAddress().toString()));
 
   // Инициализирующие данные
   if (!mClients[clientNumber].isNull())
       mClients[clientNumber]->write(createCommandPackage(CommandType::IsReady));
 
   return 0;
}
Косяк этого кода в том, что клиент может отключиться после вызова nextPendingConnection() и до подключения сигнала disconnected(). В итогу появляется висящий сокет.
У меня, конечно, есть функция с таймером, которая прибивает все сокеты в состоянии QAbstractSocket::UnconnectedState, но меня напрягает такая ситуация - из-за нее вылазят некоторые другие баги. Как можно решить такую проблему?


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: alexis031182 от Июль 08, 2012, 11:03
1. Можно. Утечки не будет.
2. А где у Вас конектится disconnected()?

Update: не увидел скролл для второго листинга


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: mutineer от Июль 08, 2012, 12:12
1. Скорее всего QPointer не реагирует потому, что deleteLater() удаляет объект не сразу при вызове


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: Ground от Июль 08, 2012, 12:25
1. Можно. Утечки не будет.
2. А где у Вас конектится disconnected()?
Да вот сразу, после nextPendingConnection(). Баг редкий, проявляется только под стресс-тестированием
Код
C++ (Qt)
   mClients[clientNumber] = QPointer<QTcpSocket>(mServer->nextPendingConnection());
 
   // Слот - "Отключение клиента"
   connect(mClients[clientNumber], SIGNAL(disconnected()),
           this, SLOT(slotRejectClient()));

1. Скорее всего QPointer не реагирует потому, что deleteLater() удаляет объект не сразу при вызове
Ну я вообще рассуждал так, что раз QPointer требует QObject в качестве объекта наблюдения, то они связываются сигналами (иначе зачем QObject нужен?). Ну и соответственно, логично было бы, если бы QPointer висел на сигнале destroyed() от объекта наблюдения.


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: alexis031182 от Июль 08, 2012, 12:31
Да вот сразу, после nextPendingConnection(). Баг редкий, проявляется только под стресс-тестированием
...
Тогда может между этими строками просто проверять состояние сокета, и если оно неудовлетворительное, то переходить к удалению сокета. Ещё можно чуть-чуть снизить латентность реакции на появление нового соединения путём перегрузки
Код
C++ (Qt)
void QTcpServer::incomingConnection(int socketDescriptor)


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: mutineer от Июль 08, 2012, 12:32
1. Скорее всего QPointer не реагирует потому, что deleteLater() удаляет объект не сразу при вызове
Ну я вообще рассуждал так, что раз QPointer требует QObject в качестве объекта наблюдения, то они связываются сигналами (иначе зачем QObject нужен?). Ну и соответственно, логично было бы, если бы QPointer висел на сигнале destroyed() от объекта наблюдения.
Логично, да. Только destroyed не испускается непосредственно при вызове deleteLater(), он испускает уже при непосредственном удалении объекта


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: Ground от Июль 08, 2012, 12:58
Логично, да. Только destroyed не испускается непосредственно при вызове deleteLater(), он испускает уже при непосредственном удалении объекта
Ну это понятно. Но ведь и от QPointer только и требуется - в ответ на этот сигнал занулить указатель. А у меня получается так, что даже через пол минуты (!) висит неудаленный сокет в состоянии QAbstractSocket::UnconnectedState.
Ну главное, что его можно занулить вручную, тогда добрая половина проблем снимается.

И еще один момент хотел спросить. Я правильно понимаю, при использовании стандартного QTcpServer, асинхронно идет только отправка данных клиентам, ожидание данных от клиента? Подключение новых клиентов, обработка входящих данных и пр. обрабатываются непосредственно в потоке с QTcpServer?


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: alexis031182 от Июль 08, 2012, 13:03
...
И еще один момент хотел спросить. Я правильно понимаю, при использовании стандартного QTcpServer, асинхронно идет только отправка данных клиентам, ожидание данных от клиента? Подключение новых клиентов, обработка входящих данных и пр. обрабатываются непосредственно в потоке с QTcpServer?
Всё, и отправка, и приём данных, и подключение с отключением клиентов выполняется в одном потоке. Асинхронность достигается лишь формированием соответствующих событий.


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: LisandreL от Июль 08, 2012, 15:11
А у меня получается так, что даже через пол минуты (!) висит неудаленный сокет в состоянии QAbstractSocket::UnconnectedState.
Может редко в цикл обработки событий вываливаетесь?


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: Igors от Июль 08, 2012, 16:15
Ну я вообще рассуждал так, что раз QPointer требует QObject в качестве объекта наблюдения, то они связываются сигналами (иначе зачем QObject нужен?). Ну и соответственно, логично было бы, если бы QPointer висел на сигнале destroyed() от объекта наблюдения.
Нет, сигналами они не связываются. QObject держит хеш всех QPointer указывающих на него. Когда QPointer меняется хеш корректируется. QPointer должен становиться нулевым если сработал деструктор QObject. Зануление ничем технически не грозит, но затемняет картину. Что мешает удалить явно (explicit)?

Косяк этого кода в том, что клиент может отключиться после вызова nextPendingConnection() и до подключения сигнала disconnected(). В итогу появляется висящий сокет.
Может так: в конце метода Server::slotAcceptClient() проверить состояние и если не гуд, просто удалить сокет, тогда проверка в disconnect сработает


Название: Re: Проблема с сервером, зависшие клиенты
Отправлено: Ground от Июль 09, 2012, 03:15
Проблема решена, всем спасибо за участие в обсуждении. Львиная доля проблем возникала из-за не зануления QPointer. Часть с зависшими клиентами решилась с помощью:
Код
C++ (Qt)
void QTcpServer::incomingConnection(int socketDescriptor)
За что спасибо alexis031182.

Сегодня еще проверил способ, предложенный Igors. Он тоже работает, решает проблему, и даже проще.
Может так: в конце метода Server::slotAcceptClient() проверить состояние и если не гуд, просто удалить сокет, тогда проверка в disconnect сработает

Нет, сигналами они не связываются. QObject держит хеш всех QPointer указывающих на него. Когда QPointer меняется хеш корректируется. QPointer должен становиться нулевым если сработал деструктор QObject. Зануление ничем технически не грозит, но затемняет картину. Что мешает удалить явно (explicit)?
Если удалять явно (несмотря на принудительный disconnect сигналов), Qt бросает предупреждение об отсутствующем получателе.