2010-08-08 10 views
8

Ho una piccola applicazione che invia i file attraverso la rete a un agente che si trova su un sistema operativo Windows.Cosa posso fare per evitare TCP Finestra Zero/Finestra TCP Completo sul lato ricevitore?

Quando questa applicazione viene eseguita su Windows, tutto funziona correttamente, la comunicazione è OK e tutti i file vengono copiati correttamente.

Ma, quando questa applicazione gira su Linux (RedHat 5.3, il ricevitore è ancora Windows) - Vedo nei messaggi di traccia della rete Wireshark di TCP Zero Window e TCP Window Full che appaiono ogni 1-2 secondi. L'agente chiude la connessione dopo alcuni minuti.

Il codice Windows - Linux è quasi lo stesso e molto semplice. L'unica operazione non banale è setockopt con SO_SNDBUF e valore di 0xFFFF. Rimozione di questo codice non ha aiutato.

Qualcuno può aiutarmi con questo problema?

EDIT: aggiungendo il codice di invio - sembra che gestisce scrive correttamente parziali:

int totalSent=0; 
while(totalSent != dataLen) 
{ 
    int bytesSent 
     = ::send(_socket,(char *)(data+totalSent), dataLen-totalSent, 0); 

    if (bytesSent ==0) { 
     return totalSent; 
    } 
    else if(bytesSent == SOCKET_ERROR){ 
#ifdef __WIN32 
     int errcode = WSAGetLastError(); 
     if(errcode==WSAEWOULDBLOCK){ 
#else 
      if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) { 
#endif 
      } 
      else{ 
       if(!totalSent) { 
        totalSent = SOCKET_ERROR; 
       } 
       break; 
      } 
     } 
     else{ 
      totalSent+=bytesSent; 
     } 
    } 
} 

Grazie in anticipo.

+0

Maggiori dettagli? Il file viene trasferito correttamente, solo a una velocità inferiore o il trasferimento non riesce? Se sta fallendo, dove sta fallendo? Qualcosa sta attraversando o sta fallendo a metà? –

+0

@Robert, grazie. Il trasferimento fallisce. Se trasferisco una cartella contiene, ad esempio, 2 GB di 3 KB - 50 KB di file, trasferisce a volte ~ 0,5 GB, a volte ~ 1,3 GB di dati e quindi non riesce. – rkellerm

+0

Che messaggi di errore si stanno ottenendo e da che parte si sta spegnendo la connessione? Stai usando I/O bloccanti o non bloccanti. Hai un thread dedicato facendo I/O? Più dettagli ci sono, meglio è, e se potessi pubblicare frammenti di codice sarebbe il migliore. –

risposta

0

Ho provato a disabilitare l'algoritmo di Nagle (con TCP_NODELAY), e in qualche modo, mi ha aiutato. La velocità di trasferimento è molto più alta, la dimensione della finestra TCP non è piena o ripristinata. La cosa strana è che quando ho scagliato le dimensioni della finestra non ha avuto alcun impatto.

Grazie.

+0

Questo è davvero strano. In genere la disattivazione di Nagle è utile solo per le app in tempo reale in cui si desidera avere una latenza molto bassa a scapito dello spreco di banda. Disabilitarlo per il trasferimento di file di massa sembra contro-intuitivo. Hai effettivamente testato e visto obiettivamente che disabilitare Nagle è ciò che fa la differenza? Forse qualche altro cambiamento che hai fatto potrebbe essere responsabile? –

+0

@Robert S. Barnes: È davvero strano, sono d'accordo. Ma questo è l'unico cambiamento che è stato fatto e ha aiutato. Inoltre, il lato ricevitore ha già disabilitato Nagle. So che potrebbe riferirsi a un problema fondamentale sottostante che si nasconde da qualche parte, in attesa di saltare fuori e mordere in un altro momento. Ma come soluzione è abbastanza buono. – rkellerm

0

Il problema più probabile è che si sia verificato un errore nel codice in cui non si gestiscono correttamente letture parziali o scritture parziali. Il protocollo TCP tra Linux e Windows è noto per funzionare.

1

Un errore comune durante lo sviluppo con socket TCP riguarda l'assunzione errata del comportamento read()/write().

Quando si esegue un'operazione di lettura/scrittura, è necessario controllare il valore restituito, potrebbero non aver letto/scritto la richiesta di byte, di solito è necessario un ciclo per tenere traccia e assicurarsi che l'intero dato sia stato trasferito.

12

Non vedendo il tuo codice dovrò indovinare.

Il motivo per cui si ottiene una finestra Zero in TCP è perché non c'è spazio nel buffer di ricezione del ricevitore.

Ci sono diversi modi in cui ciò può accadere. Una causa comune di questo problema è quando si invia una LAN o altra connessione di rete relativamente veloce e un computer è significativamente più veloce dell'altro computer. Come esempio estremo, diciamo che hai un computer 3Ghz che invia il più velocemente possibile su un Gigabit Ethernet a un altro computer che esegue una CPU da 1 Ghz. Poiché il mittente può inviare molto più velocemente di quanto il ricevitore sia in grado di leggere, il buffer del ricevitore si riempirà facendo in modo che lo stack TCP pubblicizzi una finestra Zero sul mittente.

Ora questo può causare problemi sia sul lato di invio che su quello di ricezione se non sono entrambi pronti ad affrontare questo problema. Sul lato dell'invio, ciò può causare il riempimento del buffer di invio e le chiamate da inviare a bloccare o fallire se si utilizza I/O non bloccante. Dal lato ricevente si potrebbe passare così tanto tempo sull'I/O che l'applicazione non ha la possibilità di elaborare alcuno dei suoi dati e dando l'impressione di essere rinchiuso.

Modifica

Da alcune delle sue risposte e il codice che suona come la vostra applicazione è a thread singolo e si sta cercando di fare non-blocking manda per qualche ragione. Presumo che tu stia impostando il socket su non-Blocking in qualche altra parte del codice.

In generale, direi che questa non è una buona idea. Idealmente, se siete preoccupati per la vostra applicazione appesa a un send(2) si dovrebbe impostare un lungo timeout sul socket utilizzando setsockopt e usare un thread separato per l'invio vero e proprio.

Vedi socket(7):

SO_RCVTIMEO e SO_SNDTIMEO Specificare la ricezione o l'invio di timeout fino segnalato un errore. Il parametro è una struct timeval. Se un input o output blocchi funzionali per questo periodo di tempo, ei dati sono stati inviati o ricevuti, il valore restituito di che funzione sarà la quantità di dati trasferiti ; se nessun dato è stato trasferito e il timeout è stato raggiunto allora -1 viene restituito con errno impostato su EAGAIN o EWOULDBLOCK come se il socket è stato specificato come non bloccante. Se il timeout è impostato su zero (il default) allora l'operazione non sarà mai timeout.

tuo thread principale può spingere ogni descrittore di file in un queue utilizzando dire un mutex spinta per l'accesso di coda, quindi avviare 1 - N thread per fare l'invio effettivo utilizzando il blocco di I/O con mando timeout.

La funzione di invio dovrebbe essere simile a questa (supponendo che si sta impostando un timeout):

// blocking send, timeout is handled by caller reading errno on short send 
int doSend(int s, const void *buf, size_t dataLen) {  
    int totalSent=0; 

    while(totalSent != dataLen) 
    { 
     int bytesSent 
      = send(s,((char *)data)+totalSent, dataLen-totalSent, MSG_NOSIGNAL); 

     if(bytesSent < 0 && errno != EINTR) 
      break; 

     totalSent += bytesSent; 
    } 
    return totalSent; 
} 

La bandiera MSG_NOSIGNAL assicura che l'applicazione non viene ucciso scrivendo ad una presa che è stato chiuso o resettato dal pari. A volte le operazioni di I/O vengono interrotte dai segnali e il controllo di EINTR consente di riavviare lo send.

In generale, si dovrebbe chiamare doSend in un ciclo con blocchi di dati che sono di TCP_MAXSEG dimensioni.

Sul lato ricezione è possibile scrivere una analoga funzione di blocco recv utilizzando un timeout in un thread separato.

+0

Grazie per questo post. È molto istruttivo soprattutto il 'MSG_NOSIGNAL' che credo sia il mio problema su una delle mie applicazioni. – kuchi