具有大量同时客户端的慢 QTcpServer

Posted

技术标签:

【中文标题】具有大量同时客户端的慢 QTcpServer【英文标题】:Slow QTcpServer with lots of simultaneous clients 【发布时间】:2012-05-03 11:26:31 【问题描述】:

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

    我继承了 QTcpServer 并重新实现了incomingConnection(int) 在incomingConnection 中,我正在创建“Streamer”类的实例 “Streamer”正在使用 QTcpSocket,该 QTcpSocket 使用来自incomingConnection 的 setSocketDescriptor 进行初始化 当来自客户端的数据到达时,我会从 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 的事件调度(或套接字通知器?)太慢了,无法处理所有这些事件。

我试过了:

    使用 -O3 编译整个 Qt 和我的应用程序 安装libglib2.0-dev并重新编译Qt(因为QCoreApplication使用的是QEventDispatcherGlib或者QEventDispatcherUNIX,所以想看看有什么不同) 根据当前在特定线程中的客户端数量,使用 streamer->moveToThread() 生成几个线程并在 incomingConnection(int) 中产生任何变化(尽管我观察到速度变化更大) 使用生成工作进程

代码:

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

也试过了:

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

我使用的是 Qt 4.8.1

我的想法用完了。

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

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

【问题讨论】:

服务器磁盘使用情况如何?会不会是瓶颈? 很有可能。看起来服务器硬件可能有缺陷。我会在星期一确定,我会告诉你的。谢谢! 瓶颈肯定是磁盘IO操作。超过 80 个打开的文件会导致服务器负载 > 1,下载速度约为 150 KB/s。我的程序有什么可以改变的吗,或者我必须使用服务器配置/硬件? 【参考方案1】:

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

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

    接受传入连接。

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

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

    从磁盘读取并写入网络。

当然 #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 对象移动到的线程数。

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

【讨论】:

以上是关于具有大量同时客户端的慢 QTcpServer的主要内容,如果未能解决你的问题,请参考以下文章

使用 QTcpSocket 和 QTcpServer 进行控制台聊天

QTcpServer实现多客户端连接

处理大量微依赖关系

telnet 客户端未连接 - 到 QTCPserver

QTcpServer服务器无法监听和客户端连接不上问题

ZeroMQ - 为一台服务器模拟多个客户端的标准套接字