C++ 具有大量同步客户端的慢速QTcpServer

C++ 具有大量同步客户端的慢速QTcpServer,c++,performance,qt,qtcpserver,C++,Performance,Qt,Qtcpserver,我正在用Qt编写TCP服务器,它将服务于大文件。应用逻辑如下: 我已经对qtcserver进行了子分类,并重新实现了incomingConnection(int) 在incomingConnection中,我正在创建“Streamer”类的实例 “拖缆”使用QTcpSocket,该QTcpSocket由incomingConnection中的setSocketDescriptor初始化 当来自客户机的数据到达时,我将从readyRead()插槽中发回初始响应,然后将套接字的信号byteswrit

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

  • 我已经对qtcserver进行了子分类,并重新实现了incomingConnection(int)
  • 在incomingConnection中,我正在创建“Streamer”类的实例
  • “拖缆”使用QTcpSocket,该QTcpSocket由incomingConnection中的setSocketDescriptor初始化
  • 当来自客户机的数据到达时,我将从readyRead()插槽中发回初始响应,然后将套接字的信号byteswrite(qint64)连接到拖缆的插槽byteswrite()
  • BytesWrite看起来像:

    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);   
        }
    }
    ...
    
    Streamer.h:
    ...
    私人:
    QFile*m_文件;
    字符m_readBuffer[64*1024];
    QTCP插座*m_插座;
    ...
    拖缆
    ...
    无效拖缆::字节写入(){
    if(m_socket->bytesToWrite()读取(m_readBuffer,64*1024);
    m_套接字->写入(m_读取缓冲区,字节读取);
    }
    }
    ...
    
    所以基本上我只在所有待处理的数据都被完全写入时才写入新数据。我认为这是最异步的方式

    所有的工作都是正确的,除了当有很多同时出现的客户机时,速度非常慢

    大约有5个客户端-我正在以大约1 MB/s的速度从该服务器下载(我的家庭互联网连接的最大速度)

    大约有140个客户端-下载速度约为100-200 KB/s

    服务器的互联网连接速度为10 Gbps,140个客户端的使用速度约为100 Mbps,所以我认为这不是问题所在

    140个客户端的服务器内存使用率-100 MB 2GB可用

    服务器的CPU使用率-最大20%

    我正在使用端口800

    当端口800上有140个客户端,下载速度大约为100-200kb/s时,我在端口801上运行了单独的拷贝,下载速度为1Mb/s,没有问题

    我的猜测是,Qt的事件调度(或套接字通知程序?)太慢,无法处理所有这些事件

    我试过:

  • 使用-O3编译整个Qt和我的应用程序
  • 安装libglib2.0-dev并重新编译Qt(因为QCoreApplication使用QEventDispatcherGlib或qeventdispatchernix,所以我想看看是否有任何区别)
  • 生成几个线程,并使用streamer->moveToThread()在incomingConnection(int)中生成线程,这取决于特定线程中当前有多少客户端-这没有做任何更改(尽管我观察到速度变化更大)
  • 使用
  • 代码:

    main.cpp:
    #包括
    int startWorker(无效*argv){
    int argc=1;
    QCOREA应用程序(argc,(char**)argv);
    工人;
    worker.Start();
    返回a.exec();
    }
    在main()中:
    ...
    长栈[16*1024];
    克隆(startWorker,(char*)堆栈+sizeof(stack)-64,克隆_文件,(void*)argv);
    
    然后在主进程中启动QLocalServer,并将socketDescriptor从incomingConnection(int socketDescriptor)传递到工作进程。它工作正常,但下载速度仍然很慢

    还尝试:

  • fork()-incomingConnection()中的进程几乎导致服务器死亡:)
  • 为每个客户端创建单独的线程-速度降至50-100 KB/s
  • 使用QRunnable的QThreadPool-没有区别
  • 我正在使用Qt4.8.1

    我没有主意了

    它是否与Qt相关,或者可能与服务器配置有关


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

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

    您应该在QoObject之间拆分处理,如下所示:

  • 接受传入连接

  • 处理从套接字的写入和读取

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

  • 从磁盘读取数据并写入网络

  • 当然#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-线程数
    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);