Russian Qt Forum
Ноябрь 23, 2024, 17:56 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: [Решено] Проблема с сервером, зависшие клиенты  (Прочитано 6587 раз)
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, но меня напрягает такая ситуация - из-за нее вылазят некоторые другие баги. Как можно решить такую проблему?
« Последнее редактирование: Июль 09, 2012, 03:15 от Ground » Записан
alexis031182
Гость
« Ответ #1 : Июль 08, 2012, 11:03 »

1. Можно. Утечки не будет.
2. А где у Вас конектится disconnected()?

Update: не увидел скролл для второго листинга
« Последнее редактирование: Июль 08, 2012, 12:21 от alexis031182 » Записан
mutineer
Гость
« Ответ #2 : Июль 08, 2012, 12:12 »

1. Скорее всего QPointer не реагирует потому, что deleteLater() удаляет объект не сразу при вызове
Записан
Ground
Гость
« Ответ #3 : Июль 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() от объекта наблюдения.
Записан
alexis031182
Гость
« Ответ #4 : Июль 08, 2012, 12:31 »

Да вот сразу, после nextPendingConnection(). Баг редкий, проявляется только под стресс-тестированием
...
Тогда может между этими строками просто проверять состояние сокета, и если оно неудовлетворительное, то переходить к удалению сокета. Ещё можно чуть-чуть снизить латентность реакции на появление нового соединения путём перегрузки
Код
C++ (Qt)
void QTcpServer::incomingConnection(int socketDescriptor)
Записан
mutineer
Гость
« Ответ #5 : Июль 08, 2012, 12:32 »

1. Скорее всего QPointer не реагирует потому, что deleteLater() удаляет объект не сразу при вызове
Ну я вообще рассуждал так, что раз QPointer требует QObject в качестве объекта наблюдения, то они связываются сигналами (иначе зачем QObject нужен?). Ну и соответственно, логично было бы, если бы QPointer висел на сигнале destroyed() от объекта наблюдения.
Логично, да. Только destroyed не испускается непосредственно при вызове deleteLater(), он испускает уже при непосредственном удалении объекта
Записан
Ground
Гость
« Ответ #6 : Июль 08, 2012, 12:58 »

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

И еще один момент хотел спросить. Я правильно понимаю, при использовании стандартного QTcpServer, асинхронно идет только отправка данных клиентам, ожидание данных от клиента? Подключение новых клиентов, обработка входящих данных и пр. обрабатываются непосредственно в потоке с QTcpServer?
Записан
alexis031182
Гость
« Ответ #7 : Июль 08, 2012, 13:03 »

...
И еще один момент хотел спросить. Я правильно понимаю, при использовании стандартного QTcpServer, асинхронно идет только отправка данных клиентам, ожидание данных от клиента? Подключение новых клиентов, обработка входящих данных и пр. обрабатываются непосредственно в потоке с QTcpServer?
Всё, и отправка, и приём данных, и подключение с отключением клиентов выполняется в одном потоке. Асинхронность достигается лишь формированием соответствующих событий.
Записан
LisandreL
Птица говорун
*****
Offline Offline

Сообщений: 984


Надо улыбаться


Просмотр профиля
« Ответ #8 : Июль 08, 2012, 15:11 »

А у меня получается так, что даже через пол минуты (!) висит неудаленный сокет в состоянии QAbstractSocket::UnconnectedState.
Может редко в цикл обработки событий вываливаетесь?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #9 : Июль 08, 2012, 16:15 »

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

Косяк этого кода в том, что клиент может отключиться после вызова nextPendingConnection() и до подключения сигнала disconnected(). В итогу появляется висящий сокет.
Может так: в конце метода Server::slotAcceptClient() проверить состояние и если не гуд, просто удалить сокет, тогда проверка в disconnect сработает
Записан
Ground
Гость
« Ответ #10 : Июль 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 бросает предупреждение об отсутствующем получателе.
« Последнее редактирование: Июль 09, 2012, 03:17 от Ground » Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.229 секунд. Запросов: 23.