关于c ++:具有大量并发客户端的慢QTcpServer

Slow QTcpServer with lots of simultaneous clients

我正在Qt中编写TCP服务器,该服务器将提供大文件。应用程序逻辑如下:

  • 我已经将QTcpServer子类化并重新实现了incomingConnection(int)
  • 在incomingConnection中,我正在创建" Streamer"类的实例
  • " Streamer"使用的是QTcpSocket,它是通过传入连接的setSocketDescriptor初始化的
  • 当来自客户端的数据到达时,我从readyRead()插槽中发回初始响应,然后将套接字的信号bytesWritten(qint64)连接到Streamer的插槽bytesWritten()
  • 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的事件调度(或套接字通知程序?)太慢了,无法处理所有这些事件。

    我试过了:

  • 用-O3编译整个Qt和我的应用
  • 安装libglib2.0-dev并重新编译Qt(因为QCoreApplication使用QEventDispatcherGlib或QEventDispatcherUNIX,所以我想看看是否有任何区别)
  • 使用streamer-> moveToThread()根据当前特定线程中的客户端数量来产生一些线程并在incomingConnection(int)中生成-并没有任何改变(尽管我观察到速度变化很大)
  • 使用以下方式生成工作进程
  • 码:

    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)传递到辅助进程。它可以正常工作,但是下载速度仍然很慢。

    还尝试了:

  • fork()-ingingConnection()中的进程-几乎杀死了服务器:)
  • 为每个客户端创建单独的线程-速度降至50-100 KB / s
  • 将QThreadPool与QRunnable一起使用-没有区别
  • 我正在使用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对象移动到的线程数。

  • 您将需要在不同的机器上进行基准测试,以了解运行速度如何以及调整的效果如何。从台式机或笔记本电脑的开发机器上启动基准测试。由于这是您的日常工作,如果它的性能有问题,您可能会很快注意到。