2011-11-06 15 views
11

Ho scritto un semplice client e server TCP. Il problema sta nel cliente.Ciclo finché la risposta TcpClient non è stata completamente letta

Ho difficoltà a leggere l'intera risposta dal server. Devo lasciar dormire il thread per consentire l'invio di tutti i dati.

Ho provato alcune volte a convertire questo codice in un ciclo che viene eseguito fino a quando il server non ha finito di inviare i dati.

// Init & connect to client 
TcpClient client = new TcpClient(); 
Console.WriteLine("Connecting....."); 
client.Connect("192.168.1.160", 9988); 

// Stream string to server 
input += "\n"; 
Stream stm = client.GetStream(); 
ASCIIEncoding asen = new ASCIIEncoding(); 
byte[] ba = asen.GetBytes(input); 
stm.Write(ba, 0, ba.Length); 

// Read response from server. 
byte[] buffer = new byte[1024]; 

System.Threading.Thread.Sleep(1000); // Huh, why do I need to wait? 

int bytesRead = stm.Read(buffer, 0, buffer.Length); 
response = Encoding.ASCII.GetString(buffer, 0, bytesRead); 
Console.WriteLine("Response String: "+response); 

client.Close(); 
+4

Il client ha bisogno di un modo per scoprire quando il server è finito (ad esempio, un prefisso di lunghezza per una sequenza di terminazione speciale) – SLaks

+1

Provare a utilizzare StreamReader http://msdn.microsoft.com/en-us/library/system. io.streamreader.aspx – David

+4

In che modo StreamReader può essere d'aiuto? Non sa neanche quando il server è finito. –

risposta

25

La natura degli stream che sono stati costruiti su socket è che si dispone di una pipeline aperta che trasmette e riceve dati fino alla chiusura del socket.

Tuttavia, a causa della natura delle interazioni client/server, questa pipeline non è sempre garantita per la lettura del contenuto. Il client e il server devono accettare di inviare il contenuto attraverso la pipeline.

Quando si prende lo Stream abstraction in .NET e lo si sovrappone al concetto di socket, si applica ancora il requisito per un accordo tra client e server; puoi chiamare lo Stream.Read tutto ciò che desideri, ma se la presa su cui è collegato il tuo Stream sull'altro lato non sta inviando il contenuto, la chiamata attenderà solo che ci sia del contenuto.

Ecco perché esistono protocolli. Al loro livello più elementare, aiutano a definire quale sia il messaggio completo che viene inviato tra due parti. Solitamente, il meccanismo è qualcosa sulla falsariga di:

  • Un messaggio lunghezza prefissato in cui il numero di byte da leggere viene inviato prima il messaggio
  • Un modello di caratteri utilizzato per indicare la fine di un messaggio (questo è meno comune a seconda del contenuto che viene inviato, la più arbitraria qualsiasi parte del messaggio può essere, meno è probabile che questo sarà usato)

detto questo non si sta aderendo alla sopra; la tua chiamata a Stream.Read sta dicendo "leggi 1024 byte" quando in realtà, potrebbero non esserci 1024 byte da leggere. In questo caso, la chiamata a Stream.Read verrà bloccata fino a quando non viene compilato.

Il motivo per cui la chiamata a Thread.Sleep probabilmente funziona è perché nel momento in cui passa un secondo, il Stream ha 1024 byte su di esso da leggere e non blocca.

Inoltre, se si desidera veramente leggere 1024 byte, non è possibile presumere che la chiamata a Stream.Read popolerà 1024 byte di dati. Il valore restituito per il metodo Stream.Read indica quanti byte sono stati effettivamente letti. Se hai bisogno di più per il tuo messaggio, devi effettuare ulteriori chiamate allo Stream.Read.

Jon Skeet wrote up the exact way to do this se si desidera un campione.

+3

L'articolo di Jon Skeet è fantastico, grazie per averlo saputo. +1 –

+0

La deserializzazione degli oggetti fornisce una soluzione al problema dei marcatori di lunghezza/fine del messaggio. Con protobuf ad esempio '.Deserialize (stream)' leggerà automaticamente finché non avrà l'intero oggetto. Sotto il cofano, protobuf ha il suo modo di determinare l'inizio/fine degli oggetti. Ciò presuppone che la libreria di serializzazione implementa correttamente i flussi. Anche i flussi di file restituiscono spesso meno del buffer completo dei byte (per motivi di prestazioni), quindi qualsiasi ciclo di lettura correttamente scritto dovrebbe verificare il numero effettivo di byte letti e non presupporre che il buffer completo sia stato riempito in un singolo '.Read'. – AaronLS

+0

Una correzione, dovresti usare 'DeserializeWithLengthPrefix' e la sua controparte' SerializeWithLengthPrefix' come per http://stackoverflow.com/a/8901333/84206 – AaronLS

2

provare a ripetere la

int bytesRead = stm.Read(buffer, 0, buffer.Length); 

mentre bytesRead> 0. Si tratta di un modello comune per che come mi ricordo. Ovviamente non dimenticare di passare i parametri appropriati per il buffer.

0

Nel client/server TCP che ho appena scritto, generare il pacchetto che si desidera inviare a un flusso di memoria, quindi prendere la lunghezza di tale flusso e utilizzarlo come prefisso quando si inviano i dati. In questo modo il client sa quanti byte di dati avrà bisogno di leggere per un pacchetto completo.

0

Non si conosce la dimensione dei dati che si stanno leggendo, quindi è necessario impostare un meccanismo per decidere. Uno è timeout e un altro sta usando i delimitatori.

Nell'esempio si legge qualsiasi dato da una sola iterazione (lettura) perché non si imposta il timeout per la lettura e si utilizza il valore predefinito che è "0" in millisecondi. Quindi devi dormire solo 1000 ms. Si ottiene lo stesso effetto con l'utilizzo del tempo di ricezione fino a 1000 ms.

Penso che usare la lunghezza dei dati come prefisso non sia la vera soluzione perché quando il socket è chiuso da entrambi i lati e il tempo del socket non può essere gestito correttamente, gli stessi dati possono essere inviati al server e lasciare che il server ottenga un'eccezione. Abbiamo usato il prefisso e terminare la sequenza di caratteri e controllare i dati dopo ogni lettura. Dopo ogni lettura controlliamo i dati per la sequenza dei caratteri di inizio e fine, se non riusciamo a trovare i caratteri finali chiamiamo un'altra lettura. Ma ovviamente questo funziona se si ha il controllo del lato server e del codice lato client.

+0

abbiamo davvero bisogno di implementare questo" carattere start/end "da noi stessi, o .NET lo gestisce già? – Seva

+1

"caratteri di inizio/fine" sono personalizzati in modo che .net non implementa tali cose. In realtà abbiamo impacchettato un sistema utilizzando la lunghezza dei dati come prefisso del messaggio e il sistema funziona correttamente. Ma devi implementare una robusta architettura socket. Puoi utilizzare questo codice http://stackoverflow.com/questions/869744/how-to-write-a-scalable-tcp-ip-based-server/20147995#20147995 –

Problemi correlati