QTcpServer速度慢,同时拥有大量客户端

时间:2012-04-22 18:01:02

标签: c++ performance qt qtcpserver

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

  1. 我已经将QTcpServer子类化并重新实现了incomingConnection(int)
  2. 在incomingConnection中,我正在创建“Streamer”类的实例
  3. “Streamer”正在使用QTcpSocket,它使用来自incomingConnection的setSocketDescriptor进行初始化
  4. 当来自客户端的数据到达时,我从readyRead()槽中发回初始响应,然后我将socket的信号bytesWritten(qint64)连接到Streamer的槽bytesWritten()
  5. 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 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. 使用-O3
    2. 编译整个Qt和我的应用程序
    3. 安装libglib2.0-dev并重新编译Qt(因为QCoreApplication使用QEventDispatcherGlib或QEventDispatcherUNIX,所以我想看看是否有任何区别)
    4. 使用streamer-&gt; moveToThread()生成几个线程并在incomingConnection(int)中,这取决于当前特定线程的客户端数量 - 没有做任何更改(尽管我发现速度更高)变的)
    5. 使用
    6. 产生工作进程

      代码:

      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并将来自incomingConnection(int socketDescriptor)的socketDescriptors传递给工作进程。它工作正常,但下载速度仍然很慢。

      也尝试过:

      1. fork() - 来自incomingConnection()的进程 - 几乎杀死了服务器:)
      2. 为每个客户端创建单独的线程 - 速度降至50-100 KB / s
      3. 将QThreadPool与QRunnable一起使用 - 没有区别
      4. 我正在使用Qt 4.8.1

        我的想法用完了。

        是Qt相关还是服务器配置?

        或许我应该使用不同的语言/框架/服务器?我需要提供文件的TCP服务器,但我还需要在数据包之间执行一些特定的任务,所以我需要自己实现该部分。

1 个答案:

答案 0 :(得分:3)

您的磁盘读取是阻止操作,它们将停止任何处理,包括处理新的网络连接等。您的磁盘也具有有限的I / O吞吐量,您可以使其饱和。您可能不希望磁盘停止应用程序的其余部分。我不认为Qt在这里有任何问题 - 直到你运行一个分析器并显示Qt的CPU消耗过多,或者某种程度上Qt在事件队列上遇到锁争用(这些是唯一重要的事情) )。

您应该将处理功能拆分为QObjects,如下所示:

  1. 接受传入连接。

  2. 处理来自插座的书写和阅读。

  3. 处理传入的网络数据并发出任何非文件回复。

  4. 从磁盘读取并写入网络。

  5. 当然,#1和#2是现有的Qt类。

    你必须写#3和#4。您可以将#1和#2移动到它们之间共享的一个线程中。 #3和#4应分布在多个线程中。应为每个活动连接创建#3的实例。然后,当发送文件数据的时候,#3实例化#4。可用于#4的线程数应该是可调整的,您可能会发现特定工作负载的最佳设置。您可以以循环方式在其线程中实例化#3和#4。由于磁盘访问是阻塞的,因此用于#4的线程应该是独占的,不能用于其他任何东西。

    当写入缓冲区中剩余少量数据时,#4对象应该执行磁盘读取。这个数量可能不应该为零 - 如果可能的话,您希望始终保持这些网络接口的忙碌状态,并且耗尽数据以便将它们空闲。

    因此,我至少看到了您需要进行基准测试的以下可调参数:

    1. minNetworkWatermark - 套接字传输缓冲区中的最低水位。当读写的字节少于许多字节时,您从磁盘读取并写入套接字。

    2. minReadSize - 最小磁盘读取的大小。读取的文件为qMax(minNetworkWatermark - socket-&gt; bytesToWrite(),minReadSize)。

    3. numDiskThreads - #4对象移动到的线程数。

    4. numNetworkThreads - #3对象移动到的线程数。

    5. 您需要在不同的机器上进行基准测试,以了解事情的进展速度以及调整的效果。从您的开发机器开始基准测试,无论是台式机还是笔记本电脑。既然这是你的日常工作,你可能会很快注意到它的性能是否有问题。