Медленный QTcpServer с множеством одновременных клиентов

Я пишу TCP-сервер в Qt, который будет обслуживать большие файлы. Логика приложения выглядит следующим образом:

  1. Я вложил в подкласс QTcpServer и повторно реализовал входящийКоннекция (int)
  2. Во входящей связи я создаю экземпляр класса "Streamer"
  3. "Streamer" использует QTcpSocket, который инициализируется с помощью setSocketDescriptor из входящего соединения
  4. Когда поступают данные от клиента, я отправляю первоначальный ответ из слота readyRead(), а затем подключаю сигнал bytesWritten сокета (qint64) к слоту Streamer bytesWritten ()

bytesWritten выглядит примерно так:

Streamer.h:
...
private:
    QFile *m_file;
    char m_readBuffer[64 * 1024];
    QTcpSocket *m_socket;
...

Streamer.cpp
...
void Streamer::bytesWritten() {
    if (m_socket->bytesToWrite() <= 0) {
        const int bytesRead = m_file->read(m_readBuffer, 64 * 1024);
        m_socket->write(m_readBuffer, bytesRead);   
    }
}
...

Так что в основном я пишу только новые данные, когда все ожидающие данные полностью записаны. Я думаю, что это самый асинхронный способ сделать это.

И все работает правильно, за исключением того, что это довольно медленно, когда есть много одновременных клиентов.

Приблизительно с 5 клиентами - я загружаю с этого сервера со скоростью около 1 МБ / с (максимум моего домашнего интернет-соединения)

Около 140 клиентов - скорость загрузки составляет около 100-200 КБ / с.

Интернет-соединение сервера составляет 10 Гбит / с, а при 140 клиентах его использование составляет около 100 Мбит / с, поэтому я не думаю, что это проблема.

Использование памяти сервером 140 клиентами - доступно 100 МБ 2 ГБ

Загрузка ЦП сервера - не более 20%

Я использую порт 800.

Когда на порту 800 было 140 клиентов, и скорость загрузки через него была равна 100-200 КБ / с, я запустил отдельную копию на порту 801 и без проблем загружал со скоростью 1 МБ / с.

Я предполагаю, что каким-то образом диспетчеризация событий Qt (или уведомлений сокетов?) Слишком медленная, чтобы обрабатывать все эти события.

Я пробовал:

  1. Компиляция всего Qt и моего приложения с -O3
  2. Установка libglib2.0-dev и перекомпиляция Qt (потому что QCoreApplication использует QEventDispatcherGlib или QEventDispatcherUNIX, поэтому я хотел посмотреть, есть ли разница)
  3. Создает несколько потоков и в входящих Connection(int), используя streamer->moveToThread(), в зависимости от того, сколько клиентов в данный момент находится в определенном потоке - это не внесло никаких изменений (хотя я заметил, что скорости были гораздо более разными)
  4. Нерест рабочих процессов с использованием

Код:

main.cpp:
#include <sched.h>

int startWorker(void *argv) {
    int argc = 1;
    QCoreApplication a(argc, (char **)argv);

    Worker worker;
    worker.Start();

    return a.exec();
}

in main():
...
long stack[16 * 1024]; 
clone(startWorker, (char *)stack + sizeof(stack) - 64, CLONE_FILES, (void *)argv);

а затем запуск QLocalServer в главном процессе и передача socketDescriptors из входящего соединения (int socketDescriptor) в рабочие процессы. Он работал правильно, но скорость загрузки все еще была низкой.

Также попробовал:

  1. fork ()- входящий процесс во входящей связи ()- который чуть не убил сервер:)
  2. Создание отдельного потока для каждого клиента - скорость упала до 50-100 КБ / с
  3. Использование QThreadPool с QRunnable - без разницы

Я использую Qt 4.8.1

У меня кончились идеи.

Это связано с Qt или что-то с конфигурацией сервера?

Или, может быть, я должен использовать другой язык / рамки / сервер? Мне нужен TCP-сервер, который будет обслуживать файлы, но мне также нужно выполнять некоторые конкретные задачи между пакетами, поэтому мне нужно реализовать эту часть самостоятельно.

1 ответ

Решение

Ваши чтения с диска блокируют операции, они остановят любую обработку, включая обработку новых сетевых подключений и тому подобное. Ваш диск также имеет конечную пропускную способность ввода / вывода, и вы можете насытить это. Вы, вероятно, не хотите, чтобы ваш диск останавливал остальную часть вашего приложения. Я не думаю, что здесь что-то не так с Qt - только до тех пор, пока вы не запустите профилировщик и не покажете, что загрузка ЦП Qt чрезмерна или что каким-то образом Qt столкнулся с конфликтом блокировок в очередях событий (это единственные, которые здесь имеют значение).

Вы должны разделить обработку по объектам QObject следующим образом:

  1. Прием входящих соединений.

  2. Обработка написания и чтения из сокетов.

  3. Обработка входящих сетевых данных и выдача любых нефайловых ответов.

  4. Чтение с диска и запись в сеть.

Конечно, #1 и #2 - это существующие классы Qt.

Вы должны написать № 3 и № 4. Вероятно, вы можете переместить #1 и #2 в один поток, разделяемый между ними. № 3 и № 4 должны быть распределены по нескольким потокам. Экземпляр #3 должен быть создан для каждого активного соединения. Затем, когда придет время для отправки данных файла, #3 создает экземпляр #4. Количество потоков, доступных для # 4, должно быть изменяемым, вы, вероятно, обнаружите, что есть оптимальные настройки для этой конкретной рабочей нагрузки. Вы можете создавать экземпляры № 3 и № 4 по всем потокам в циклическом порядке. Поскольку доступ к диску блокируется, потоки, используемые для # 4, должны быть эксклюзивными и не использоваться ни для чего другого.

Объект # 4 должен выполнять чтение с диска, когда в буфере записи осталось меньше определенного объема данных. Эта сумма, вероятно, не должна равняться нулю - вы хотите, чтобы эти сетевые интерфейсы были постоянно заняты, если это возможно, и исчерпание данных для отправки - это один из надежных способов их простоя.

Таким образом, я вижу по крайней мере следующие настраиваемые параметры, для которых вам нужно будет тестировать:

  1. minNetworkWatermark - минимальный уровень воды в буфере передачи сокета. Вы читаете с диска и записываете в сокет, когда для записи требуется меньше, чем столько байтов.

  2. minReadSize - Размер минимального чтения с диска. Чтение файла будет иметь вид qMax (minNetworkWatermark - socket-> bytesToWrite (), minReadSize).

  3. numDiskThreads - количество потоков, в которые перемещаются объекты #4.

  4. numNetworkThreads - количество потоков, в которые перемещаются объекты #3.

Вы захотите провести тестирование на разных машинах, чтобы понять, как быстро все происходит и каков эффект от тюнинга. Запустите тесты с вашего компьютера для разработки, будь то настольный компьютер или ноутбук. Поскольку это ваша ежедневная рабочая лошадка, вы, вероятно, быстро заметите, если что-то не так с ее производительностью.

Другие вопросы по тегам