5

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

  1. 我将 QTcpServer 子类化并重新实现了incomingConnection(int)
  2. 在incomingConnection 中,我正在创建“Streamer”类的实例
  3. “Streamer”正在使用 QTcpSocket,它使用来自incomingConnection 的 setSocketDescriptor 初始化
  4. 当来自客户端的数据到达时,我从 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 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 编译整个 Qt 和我的应用程序
  2. 安装libglib2.0-dev并重新编译Qt(因为QCoreApplication使用的是QEventDispatcherGlib或者QEventDispatcherUNIX,所以想看看有什么不同)
  3. 使用 streamer->moveToThread() 生成几个线程并在incomingConnection(int) 中取决于当前在特定线程中的客户端数量 - 这没有做出任何改变(尽管我观察到速度变化更大)
  4. 使用生成工作进程

代码:

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

也试过:

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

我正在使用 Qt 4.8.1

我没有主意了。

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

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

4

1 回答 1

3

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

您应该跨 QObjects 拆分处理,如下所示:

  1. 接受传入连接。

  2. 处理套接字的写入和读取。

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

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

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

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

当写入缓冲区中剩余的数据少于一定数量时,#4 对象应该执行磁盘读取。这个数量可能不应该为零——如果可能的话,您希望这些网络接口始终保持忙碌,并且用完要发送的数据是让它们闲置的一种可靠方法。

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

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

  2. minReadSize - 最小磁盘读取的大小。文件读取将是 qMax(minNetworkWatermark - socket->bytesToWrite(), minReadSize)。

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

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

You will want to benchmark on different machines to get an idea how fast things can go and what is the effect of tuning. Start the benchmarks from your development machine, whether desktop or notebook. Since it's your daily workhorse, you'd probably notice quickly if there was something wrong with its performance.

于 2012-05-29T18:46:50.353 回答