Slow QTcpServer with lots of simultaneous clients
我正在Qt中编写TCP服务器,该服务器将提供大文件。应用程序逻辑如下:
bytesWritten看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 MB / s(我的家庭互联网连接最大)
拥有约140个客户端-下载速度约为100-200 KB / s。
服务器的互联网连接速度为10 Gbps,有140个客户端,其使用速度约为100 Mbps,所以我认为这不是问题。
140个客户端的服务器内存使用情况-100 MB的2GB可用空间
服务器的CPU使用率-最高20%
我正在使用端口800。
当端口800上有140个客户端并且通过它的下载速度大约为100-200 KB / s时,我在端口801上运行了单独的副本,并且以1 MB / s的速度下载而没有问题。
我的猜测是,以某种方式,Qt的事件调度(或套接字通知程序?)太慢了,无法处理所有这些事件。
我试过了:
码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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,并将socketDescriptor从入站连接(int socketDescriptor)传递到辅助进程。它可以正常工作,但是下载速度仍然很慢。
还尝试了:
我正在使用Qt 4.8.1
我没主意了。
它与Qt相关还是与服务器配置有关?
还是我应该使用其他语言/框架/服务器?我需要可以提供文件服务的TCP服务器,但是我还需要在数据包之间执行一些特定的任务,因此我需要自己实现这一部分。
您的磁盘读取正在阻止操作,它们将停止任何处理,包括新网络连接等的处理。您的磁盘也具有有限的I / O吞吐量,您可以使其饱和。您可能不希望磁盘停止应用程序的其余部分。我认为Qt在这里没有任何问题-直到您运行了探查器并显示Qt的CPU消耗过多,或者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对象移动到的线程数。
您将需要在不同的机器上进行基准测试,以了解运行速度如何以及调整的效果如何。从台式机或笔记本电脑的开发机器上启动基准测试。由于这是您的日常工作,如果它的性能有问题,您可能会很快注意到。