2012-10-27 10 views
16

Ho avuto difficoltà con questo e non riesco a trovare una ragione per cui il mio codice non riesca a leggere correttamente da un server TCP che ho anche scritto. Sto usando la classe TcpClient e il suo metodo GetStream() ma qualcosa non funziona come previsto. O i blocchi di operazione indefinitamente (l'ultima operazione di lettura non ha scaduto come previsto), oi dati vengono ritagliati (per qualche ragione un'operazione di lettura restituisce 0 ed esce dal ciclo, forse il server non risponde abbastanza velocemente). Si tratta di tre tentativi di attuazione di questa funzione:Qual è il modo corretto di leggere da NetworkStream in .NET

// this will break from the loop without getting the entire 4804 bytes from the server 
string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytes = stm.Read(resp, 0, resp.Length); 
    while (bytes > 0) 
    { 
     memStream.Write(resp, 0, bytes); 
     bytes = 0; 
     if (stm.DataAvailable) 
      bytes = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

// this will block forever. It reads everything but freezes when data is exhausted 
string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytes = stm.Read(resp, 0, resp.Length); 
    while (bytes > 0) 
    { 
     memStream.Write(resp, 0, bytes); 
     bytes = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

// inserting a sleep inside the loop will make everything work perfectly 
string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytes = stm.Read(resp, 0, resp.Length); 
    while (bytes > 0) 
    { 
     memStream.Write(resp, 0, bytes); 
     Thread.Sleep(20); 
     bytes = 0; 
     if (stm.DataAvailable) 
      bytes = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

L'ultimo "funziona", ma sembra certamente brutto a mettere un sonno hard-coded all'interno del ciclo si considera che le prese già supportano leggere timeout! Devo impostare alcune proprietà (i) sullo TcpClient del NetworkStream? Il problema risiede nel server? Il server non chiude le connessioni, è compito del client farlo. Quanto sopra è anche in esecuzione all'interno del contesto del thread UI (programma di test), forse ha qualcosa a che fare con questo ...

Qualcuno sa come utilizzare correttamente NetworkStream.Read per leggere i dati fino a quando non sono disponibili più dati? Immagino che ciò che desidero sia qualcosa di simile alle vecchie proprietà di timeout Win32 di Win32 ... ReadTimeout, ecc. Prova a leggere finché non viene raggiunto il timeout, quindi restituisce 0 ... Ma a volte sembra che restituisca 0 quando i dati dovrebbe essere disponibile (o sulla strada .. può leggere return 0 se è disponibile?) e quindi blocca indefinitamente sull'ultima lettura quando i dati non sono disponibili ...

Sì, sono in perdita!

+0

Attenzione. Il codice nel tentativo (e le risposte) non chiude il client o il flusso, il che causa una perdita di risorse con grandi conseguenze se richiesto più volte. Probabilmente si dovrebbe fare 'utilizzando (var client = new System.Net.Sockets.TcpClient (ip, port)) utilizzando (var STM = client.GetStream())' quindi racchiudere il resto del metodo tra parentesi graffe. Ciò garantirà che all'uscita del metodo, indipendentemente dal motivo, le connessioni siano chiuse, le risorse vengano recuperate. –

+0

Hai visto il codice per la classe TcpClient? Ti consiglio di dargli un'occhiata ...Se si desidera ancora rispondere all'endpoint, non chiudere il flusso né il tcpclient (che chiude lo stream, se non si sbaglia). Ma il codice che sto usando è diverso, lo scaverò e aggiornerò anche qui le risposte. – Loudenvier

+0

grazie per il suggerimento. Ho trovato [TCPClient.cs] (http://referencesource.microsoft.com/#system/net/System/Net/Sockets/TCPClient.cs). Infatti la chiusura o lo smaltimento di 'TCPClient' elimina lo stream. In realtà ho visto guasti di connessione quando un programma creava migliaia di connessioni senza troppa cura (per altri, cattivi motivi). Perché dovremmo allontanarci dal solito schema identificabile qui? L'implementazione di IDisposable è soggetta a errori, 'using()' è facile e sicura. –

risposta

8

Impostazione sottostante presa ReceiveTimeout proprietà ha fatto il trucco. Puoi accedervi in ​​questo modo: yourTcpClient.Client.ReceiveTimeout. È possibile leggere il docs per ulteriori informazioni.

Ora il codice "si addormenta" solo quando è necessario che alcuni dati arrivino nel socket o genera un'eccezione se nessun dato arriva, all'inizio di un'operazione di lettura, per più di 20 ms. Posso modificare questo timeout se necessario. Ora non sto pagando il prezzo di 20ms in ogni iterazione, lo sto pagando solo all'ultima operazione di lettura. Dal momento che ho il contenuto-lunghezza del messaggio nei primi byte letti dal server posso usarlo per ottimizzare ancora di più e non provare a leggere, se tutti i dati previsti è stato già ricevuto.

Trovo usando ReceiveTimeout molto più facile di attuazione lettura asincrono ... Ecco il codice di lavoro:

string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    var bytes = 0; 
    client.Client.ReceiveTimeout = 20; 
    do 
    { 
     try 
     { 
      bytes = stm.Read(resp, 0, resp.Length); 
      memStream.Write(resp, 0, bytes); 
     } 
     catch (IOException ex) 
     { 
      // if the ReceiveTimeout is reached an IOException will be raised... 
      // with an InnerException of type SocketException and ErrorCode 10060 
      var socketExept = ex.InnerException as SocketException; 
      if (socketExept == null || socketExept.ErrorCode != 10060) 
       // if it's not the "expected" exception, let's not hide the error 
       throw ex; 
      // if it is the receive timeout, then reading ended 
      bytes = 0; 
     } 
    } while (bytes > 0); 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 
+2

Stai attento a usare questo trucco. Abbiamo un'applicazione Java che fa affidamento sul timeout del socket per rilevare la fine del messaggio e di volta in volta abbiamo perso un byte nel flusso. Certo io non so se questo vale anche in .NET, ma ho il sospetto che è sottostante stack TCP/IP che è stata la causa. Semplicemente non si dovrebbe riutilizzare una presa dopo un timeout. –

+0

@ SørenBoisen Non sto riutilizzare la presa dopo il timeout ... lo farò dati non sciolto, ma posso avere un falso timeout "positiva" (se la rete è lenta dei dati può richiedere più tempo del timeout intercharacter per arrivare) , ma in questo caso l'altra parte gestirà semplicemente una disconnessione e, si spera, riproverà. Questo non è in realtà un hack, il protocollo, che non posso cambiare, invia messaggi di dimensioni sconosciute, quindi devo fare affidamento sui timeout. In realtà ho reso il codice più solido con il tempo, ma non ha aggiornato l'articolo. Ora è un buon momento. – Loudenvier

0

Secondo le vostre esigenze, Thread.Sleep è perfettamente adatto per l'uso perché non si è sicuri quando i dati saranno disponibili, quindi potrebbe essere necessario attendere che i dati diventino disponibili. Ho leggermente modificato la logica della tua funzione, questo potrebbe aiutarti un po 'oltre.

string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 

    int bytes = 0; 

    do 
    { 
     bytes = 0; 
     while (!stm.DataAvailable) 
      Thread.Sleep(20); // some delay 
     bytes = stm.Read(resp, 0, resp.Length); 
     memStream.Write(resp, 0, bytes); 
    } 
    while (bytes > 0); 

    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

Spero che questo aiuti!

+1

Ho scritto il metodo esattamente come questo dopo aver postato la domanda! Ancora non lo trovo accettabile. Se usassi il buon vecchio IO WIN32 sovrapposto, avrei potuto "bloccare" per 20 o più msec fino all'avvio dei dati, quindi il mio codice potrebbe essere più sicuro con qualcosa di simile a questo pseudo-codice: 'stm.ReadIfDataBecomesAvailableInUpTo (timeout = 2000)' I sapere 20ms è un piccolo prezzo da pagare, ma sarà pagato in ogni interation! Per preparare una risposta di dimensioni MB sarà un ENORME SOVRAPPOSIZIONE! Non volevo ricorrere a scrivere la mia logica di timeout ... :-( – Loudenvier

+5

Devi stare attento con Sleeping sui thread Se il tempo è troppo piccolo, stai forzando gli interruttori di contesto non necessari sul sistema operativo, quindi CPU: un meccanismo migliore sarebbe probabilmente basato su interrupt o 'event'. Per questo è possibile utilizzare stm.BeginRead() che consente di avere un evento chiamato quando i dati sono pronti, in questo modo il processo può essere in un stato bloccato in cui il sistema operativo si risveglierà solo quando la risorsa è pronta.Il sonno tornerà al sistema operativo ogni volta che il processo si risveglierà – user1132959

14

Il codice di rete è notoriamente difficile da scrivere, testare e eseguire il debug.

Spesso un sacco di cose da considerare come:

  • che cosa intende utilizzare per i dati che vengono scambiati (Intel x86/x64 si basa su little-endian) "Endian" - sistemi che usa big-endian può ancora leggere i dati che sono in little-endian (e viceversa), ma devono riorganizzare i dati. Quando si documenta il "protocollo" basta chiarire quale si sta utilizzando.

  • ci sono delle "Impostazioni" che sono stati impostati sulle prese che possono influenzare il modo le si comporta "stream" (ad esempio SO_LINGER) - potrebbe essere necessario accendere certuni o disattivare se il codice è molto sensibile

  • come fa la congestione nel mondo reale che provoca ritardi nel flusso di influenzare la lettura/scrittura logica

Se il "messaggio" vengono scambiati tra un client e un server (in entrambe le direzioni) può variare in termini di dimensioni quindi spesso è necessario utilizzare una strategia per far sì che quel "messaggio" venga scambiato in modo affidabile (ovvero Protocol).

Qui ci sono diversi modi per gestire lo scambio:

  • hanno la dimensione del messaggio codificato in un colpo di testa che precede i dati - questo potrebbe essere semplicemente un "numero" nel primo 2/4/8 byte inviati (in base alla dimensione massima del messaggio) o potrebbe essere un'intestazione più esotica

  • utilizzare un indicatore speciale "fine messaggio" (sentinella), con i dati reali codificati/fuggiti se esiste la possibilità di dati reali essere confuso con una "fine del marcatore"

  • utilizzare un timeout .... i.e. un certo periodo di ricezione di nessun byte significa che non ci sono più dati per il messaggio - tuttavia, questo può essere soggetto a errori con timeout brevi, che possono essere facilmente colpiti su flussi congestionati.

  • hanno un "comando" e un canale "dati" su "connessioni" separate .... questo è l'approccio utilizzato dal protocollo FTP (il vantaggio è la chiara separazione dei dati dai comandi ... a scapito di un 2 ° collegamento)

Ogni approccio ha i suoi pro e contro per "correttezza".

Il seguente codice utilizza il metodo "timeout", come che sembra essere quello che si desidera.

Vedi http://msdn.microsoft.com/en-us/library/bk6w7hs8.aspx. È possibile ottenere l'accesso al NetworkStream sul TCPClient in modo da poter cambiare il ReadTimeout.

string SendCmd(string cmd, string ip, int port) 
{ 
    var client = new TcpClient(ip, port); 
    var data = Encoding.GetEncoding(1252).GetBytes(cmd); 
    var stm = client.GetStream(); 
    // Set a 250 millisecond timeout for reading (instead of Infinite the default) 
    stm.ReadTimeout = 250; 
    stm.Write(data, 0, data.Length); 
    byte[] resp = new byte[2048]; 
    var memStream = new MemoryStream(); 
    int bytesread = stm.Read(resp, 0, resp.Length); 
    while (bytesread > 0) 
    { 
     memStream.Write(resp, 0, bytesread); 
     bytesread = stm.Read(resp, 0, resp.Length); 
    } 
    return Encoding.GetEncoding(1252).GetString(memStream.ToArray()); 
} 

Come nota per le altre varianti di questo codice di rete scrittura ... quando si fa un Read in cui si vuole evitare un "blocco", è possibile controllare il flag DataAvailable e poi leggere solo ciò che è nel buffer controllando la proprietà .Length es stm.Read(resp, 0, stm.Length);

+0

La dimensione è sconosciuta, può essere nell'intervallo MB ... Come ho detto sul domanda, la terza opzione legge tutto correttamente, ma non posso accettare la necessità di dormire lì, e non riesco nemmeno a spiegarlo. So cosa fa: dormendo do il tempo che i dati arrivino nello zoccolo ma non volevo dormire 20ms se i dati fossero arrivati ​​prima, so come farlo con non-blocki ng legge e il mio IO sovrapposto ... ma non credo che avrei bisogno di ricorrere a quello in .NET! – Loudenvier

+0

Quale metodo si usa per consentire alla lettura di sapere se sono letti tutti i dati, cioè la giusta quantità/dimensione? Il modo tipico è utilizzare un'intestazione di dimensioni codificata nella prima parte della risposta. –

+0

Ho una lunghezza del contenuto (il protocollo è quasi identico a HTTP), ma per semplicità userei un "timeout dei dati in entrata" e ho pensato che Read avrebbe bloccato per un po 'e poi avrebbe timeout e non avrebbe aspettato per sempre che arrivo! Con il sonno "inside-loop" pagherò il prezzo di 20ms in ogni iterazione, con un "timeout dei dati in entrata", lo pagherei solo quando necessario e all'ultima lettura. Posso ottimizzarlo usando ContentLenght, ma fare affidamento solo sulla lunghezza è rischioso in quanto il server potrebbe comportarsi male e non inviare tutto! – Loudenvier

Problemi correlati