Медленный QTcpServer с множеством одновременных клиентов
Я пишу TCP-сервер в Qt, который будет обслуживать большие файлы. Логика приложения выглядит следующим образом:
- Я вложил в подкласс QTcpServer и повторно реализовал входящийКоннекция (int)
- Во входящей связи я создаю экземпляр класса "Streamer"
- "Streamer" использует QTcpSocket, который инициализируется с помощью setSocketDescriptor из входящего соединения
- Когда поступают данные от клиента, я отправляю первоначальный ответ из слота 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 (или уведомлений сокетов?) Слишком медленная, чтобы обрабатывать все эти события.
Я пробовал:
- Компиляция всего Qt и моего приложения с -O3
- Установка libglib2.0-dev и перекомпиляция Qt (потому что QCoreApplication использует QEventDispatcherGlib или QEventDispatcherUNIX, поэтому я хотел посмотреть, есть ли разница)
- Создает несколько потоков и в входящих Connection(int), используя streamer->moveToThread(), в зависимости от того, сколько клиентов в данный момент находится в определенном потоке - это не внесло никаких изменений (хотя я заметил, что скорости были гораздо более разными)
- Нерест рабочих процессов с использованием
Код:
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) в рабочие процессы. Он работал правильно, но скорость загрузки все еще была низкой.
Также попробовал:
- fork ()- входящий процесс во входящей связи ()- который чуть не убил сервер:)
- Создание отдельного потока для каждого клиента - скорость упала до 50-100 КБ / с
- Использование QThreadPool с QRunnable - без разницы
Я использую Qt 4.8.1
У меня кончились идеи.
Это связано с Qt или что-то с конфигурацией сервера?
Или, может быть, я должен использовать другой язык / рамки / сервер? Мне нужен TCP-сервер, который будет обслуживать файлы, но мне также нужно выполнять некоторые конкретные задачи между пакетами, поэтому мне нужно реализовать эту часть самостоятельно.
1 ответ
Ваши чтения с диска блокируют операции, они остановят любую обработку, включая обработку новых сетевых подключений и тому подобное. Ваш диск также имеет конечную пропускную способность ввода / вывода, и вы можете насытить это. Вы, вероятно, не хотите, чтобы ваш диск останавливал остальную часть вашего приложения. Я не думаю, что здесь что-то не так с Qt - только до тех пор, пока вы не запустите профилировщик и не покажете, что загрузка ЦП Qt чрезмерна или что каким-то образом Qt столкнулся с конфликтом блокировок в очередях событий (это единственные, которые здесь имеют значение).
Вы должны разделить обработку по объектам QObject следующим образом:
Прием входящих соединений.
Обработка написания и чтения из сокетов.
Обработка входящих сетевых данных и выдача любых нефайловых ответов.
Чтение с диска и запись в сеть.
Конечно, #1 и #2 - это существующие классы Qt.
Вы должны написать № 3 и № 4. Вероятно, вы можете переместить #1 и #2 в один поток, разделяемый между ними. № 3 и № 4 должны быть распределены по нескольким потокам. Экземпляр #3 должен быть создан для каждого активного соединения. Затем, когда придет время для отправки данных файла, #3 создает экземпляр #4. Количество потоков, доступных для # 4, должно быть изменяемым, вы, вероятно, обнаружите, что есть оптимальные настройки для этой конкретной рабочей нагрузки. Вы можете создавать экземпляры № 3 и № 4 по всем потокам в циклическом порядке. Поскольку доступ к диску блокируется, потоки, используемые для # 4, должны быть эксклюзивными и не использоваться ни для чего другого.
Объект # 4 должен выполнять чтение с диска, когда в буфере записи осталось меньше определенного объема данных. Эта сумма, вероятно, не должна равняться нулю - вы хотите, чтобы эти сетевые интерфейсы были постоянно заняты, если это возможно, и исчерпание данных для отправки - это один из надежных способов их простоя.
Таким образом, я вижу по крайней мере следующие настраиваемые параметры, для которых вам нужно будет тестировать:
minNetworkWatermark - минимальный уровень воды в буфере передачи сокета. Вы читаете с диска и записываете в сокет, когда для записи требуется меньше, чем столько байтов.
minReadSize - Размер минимального чтения с диска. Чтение файла будет иметь вид qMax (minNetworkWatermark - socket-> bytesToWrite (), minReadSize).
numDiskThreads - количество потоков, в которые перемещаются объекты #4.
numNetworkThreads - количество потоков, в которые перемещаются объекты #3.
Вы захотите провести тестирование на разных машинах, чтобы понять, как быстро все происходит и каков эффект от тюнинга. Запустите тесты с вашего компьютера для разработки, будь то настольный компьютер или ноутбук. Поскольку это ваша ежедневная рабочая лошадка, вы, вероятно, быстро заметите, если что-то не так с ее производительностью.