2012-04-22 19 views
5

Sto scrivendo server TCP in Qt che servirà file di grandi dimensioni. logica dell'applicazione è la seguente:QTcpServer lento con molti client simultanei

  1. ho sottoclasse QTcpServer e reimplementata incomingConnection (int)
  2. In incomingConnection, sto creando istanza di "Streamer" classe
  3. "Streamer" sta usando QTcpSocket che è inizializzato con setSocketDescriptor da incomingConnection
  4. Quando i dati da cliente arriva, sto rimandando prima risposta dall'interno readyRead() slot, quindi sto collegando bytesWritten segnale di presa (qint64) allo slot bytesWritten Streamer()

bytesWritten sembra qualcosa di simile:

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); 
    } 
} 
... 

Quindi, fondamentalmente sto solo scrivendo nuovi dati quando tutti i dati in sospeso viene completamente scritto. Penso che sia il modo più asincrono di farlo.

E tutto funziona correttamente, tranne che è piuttosto lento quando ci sono molti client simultanei.

Con circa 5 clienti - Sto scaricando da quel server con velocità di circa 1 MB/s (max della mia casa connessione internet)

Con circa 140 clienti - la velocità di download è di circa 100-200 KB/s .

La connessione Internet del server è 10 Gbps e con 140 client il suo utilizzo è di circa 100 Mbps, quindi non penso che questo sia il problema.

utilizzo della memoria del server con 140 clienti - 100 MB di 2GB disponibili

utilizzo della CPU del server - max 20%

sto usando la porta 800.

Quando ci sono stati 140 i clienti sulla porta 800 e la velocità di download era simile a 100-200 KB/s, ho eseguito una copia separata sulla porta 801 e stavo scaricando a 1 MB/s senza problemi.

La mia ipotesi è che in qualche modo, l'invio di eventi di Qt (oi notificatori di socket?) Sia troppo lento per gestire tutti quegli eventi.

ho provato:

  1. Compilazione tutta Qt e la mia app con -O3
  2. Installazione libglib2.0-dev e ricompilazione Qt (perché QCoreApplication utilizza QEventDispatcherGlib o QEventDispatcherUNIX, quindi volevo vedere se c'è qualche differenza)
  3. Creazione di alcuni thread e in incomingConnection (int) utilizzando streamer-> moveToThread() a seconda di quanti client sono attualmente in particolare filo - che non ha fatto alcun cambiamento (anche se ho osservato che le velocità erano molto più variare)
  4. processi di lavoro La deposizione delle uova usando

Codice:

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); 

e poi a partire un QLoc alServer nel processo principale e passando socketDescriptors da incomingConnection (int socketDescriptor) ai processi worker. Ha funzionato correttamente, ma le velocità di download erano ancora lente.

cercato anche:

  1. fork() - processo ing in incomingConnection() - che quasi ucciso il server :)
  2. Creazione di thread separato per ogni cliente - velocità è sceso a 50-100 KB/s
  3. utilizzando QThreadPool con QRunnable - nessuna differenza

sto usando Qt 4.8.1

Ho esaurito le idee.

E 'correlato a Qt o forse qualcosa con la configurazione del server?

O forse dovrei usare un diverso linguaggio/framework/server? Ho bisogno del server TCP che servirà i file, ma ho anche bisogno di svolgere alcune attività specifiche tra i pacchetti, quindi ho bisogno di implementare quella parte da solo.

+1

E l'utilizzo del disco del server? Potrebbe essere il collo di bottiglia? –

+0

È abbastanza possibile. Sembra che l'hardware del server sia difettoso. Ne sarò sicuro lunedì, e te lo farò sapere. Grazie! – AdrianEddy

+0

Il collo di bottiglia è sicuramente le operazioni IO del disco. Sopra i 80 file aperti si verifica un carico del server> 1 e una velocità di download di circa 150 KB/s. C'è qualcosa che posso cambiare nel mio programma, o devo giocare con configurazione/hardware del server? – AdrianEddy

risposta

3

Le letture del disco sono operazioni di blocco, interromperanno qualsiasi elaborazione, inclusa la gestione di nuove connessioni di rete e così via. Anche il tuo disco ha un throughput di I/O finito, e puoi saturare questo. Probabilmente non vuoi che il tuo disco fermi il resto della tua applicazione. Non penso che ci sia qualcosa di sbagliato in Qt qui - non finché non avresti eseguito un profiler e dimostrassi che il consumo di CPU di Qt è eccessivo, o che in qualche modo Qt colpisce la contesa di lock sulle code degli eventi (quelli sono gli unici che contano qui).

si dovrebbe avere la vostra divisa elaborazione tra QObject, come segue:

  1. accettare connessioni in ingresso.

  2. Gestione della scrittura e della lettura dalle prese.

  3. Elaborazione dei dati di rete in entrata e emissione di risposte non file.

  4. Lettura dal disco e scrittura nella rete.

Ovviamente, il primo e il secondo sono classi Qt esistenti.

Devi scrivere # 3 e # 4. Probabilmente puoi spostare # 1 e # 2 in un thread condiviso tra loro. # 3 e # 4 dovrebbero essere distribuiti su un numero di thread. Un'istanza di # 3 dovrebbe essere creata per ogni connessione attiva.Quindi, quando arriva il momento di inviare i dati del file, # 3 istanzia # 4. Il numero di thread disponibili per # 4 dovrebbe essere regolabile, probabilmente troverai un'impostazione ottimale per un particolare carico di lavoro. Puoi istanziare il n. 3 e il n. 4 attraverso i loro fili in modo round robin. Poiché l'accesso al disco sta bloccando, i thread utilizzati per # 4 dovrebbero essere esclusivi e non utilizzati per nient'altro.

L'oggetto n. 4 dovrebbe eseguire letture del disco quando c'è meno di una certa quantità di dati rimasti nel buffer di scrittura. Probabilmente questo importo non dovrebbe essere zero: si desidera mantenere sempre attive le interfacce di rete, se possibile, e l'esaurimento dei dati da inviare è un modo sicuro per inattività.

Così vedo almeno i seguenti parametri regolabili che sarà necessario punto di riferimento per:

  1. minNetworkWatermark - Livello minimo dell'acqua nel buffer di presa di trasmissione. Si legge dal disco e si scrive nel socket quando c'è meno di quel numero di byte da scrivere.

  2. minReadSize - Dimensione di una lettura del disco minima. Un file letto sarebbe di qMax (minNetworkWatermark - socket-> bytesToWrite(), minReadSize).

  3. numDiskThreads: numero di thread a cui vengono spostati # 4 oggetti.

  4. numNetworkThreads: numero di thread a cui vengono spostati # 3 oggetti.

Si consiglia di eseguire il benchmark su diverse macchine per avere un'idea di quanto velocemente le cose possono andare e qual è l'effetto della messa a punto. Inizia i benchmark dal tuo computer di sviluppo, sia desktop che notebook. Dato che è il tuo cavallo di battaglia quotidiano, probabilmente noteresti rapidamente se c'era qualcosa di sbagliato nelle sue prestazioni.

Problemi correlati