Цикл до тех пор, пока ответ TcpClient полностью не прочитается
Я написал простой TCP-клиент и сервер. Проблема заключается в клиенте.
У меня возникли проблемы с чтением всего ответа с сервера. Я должен позволить потоку спать, чтобы разрешить отправку всех данных.
Я несколько раз пытался преобразовать этот код в цикл, который выполняется до тех пор, пока сервер не закончит отправку данных.
// 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 ответа
Природа потоков, построенных поверх сокетов, заключается в том, что у вас есть открытый конвейер, который передает и получает данные до тех пор, пока сокет не будет закрыт.
Однако из-за характера взаимодействия клиент-сервер этот конвейер не всегда имеет контент для чтения. Клиент и сервер должны согласиться отправлять контент по конвейеру.
Когда вы берете Stream
абстрагирование в.NET и наложение его на концепцию сокетов; все еще действует требование соглашения между клиентом и сервером; ты можешь позвонить Stream.Read
все, что вы хотите, но если розетка, что ваш Stream
подключен к другой стороне, не отправляет контент, вызов будет просто ждать, пока контент не будет.
Вот почему существуют протоколы. На самом базовом уровне они помогают определить, что такое полное сообщение, которое отправляется между двумя сторонами. Обычно этот механизм является чем-то вроде:
- Сообщение с префиксом длины, в котором перед байтом отправляется количество байтов, которые нужно прочитать.
- Шаблон символов, используемых для обозначения конца сообщения (это менее распространено в зависимости от отправляемого содержимого, чем более произвольной может быть любая часть сообщения, тем меньше вероятность ее использования)
Это сказало, что вы не придерживаетесь вышеупомянутого; ваш звонок в Stream.Read
просто говорит "прочитать 1024 байта", когда на самом деле не может быть 1024 байта для чтения. Если это так, вызов Stream.Read
будет блокировать, пока это не будет заполнено.
Причина вызова Thread.Sleep
вероятно, работает, потому что к тому времени, как проходит секунда, Stream
имеет 1024 байта для чтения, и он не блокируется.
Кроме того, если вы действительно хотите прочитать 1024 байта, вы не можете предполагать, что вызов Stream.Read
заполнит 1024 байта данных. Возвращаемое значение для Stream.Read
Метод говорит вам, сколько байтов было фактически прочитано. Если вам нужно больше для вашего сообщения, то вам нужно сделать дополнительные звонки Stream.Read
,
Джон Скит написал точный способ сделать это, если вы хотите образец.
Попробуйте повторить
int bytesRead = stm.Read(buffer, 0, buffer.Length);
в то время как bytesRead > 0. Это общий шаблон для этого, насколько я помню. Конечно, не забудьте передать соответствующие параметры для буфера.
Вы не знаете размер данных, которые вы будете читать, поэтому вы должны установить механизм для принятия решения. Один - тайм-аут, а другой использует разделители.
В вашем примере вы читаете любые данные только за одну итерацию (чтение), потому что вы не устанавливаете тайм-аут для чтения и используете значение по умолчанию, равное 0 миллисекунд. Таким образом, вы должны спать только 1000 мс. Вы получаете тот же эффект, используя время получения до 1000 мс.
Я думаю, использование длины данных в качестве префикса не является реальным решением, потому что, когда сокет закрыт обеими сторонами, ситуация ожидания сокета не может быть обработана должным образом. Одни и те же данные могут быть отправлены на сервер и вызвать исключение сервера. Мы использовали префиксную последовательность символов. После каждого чтения мы проверяем данные на начальную и конечную последовательность символов, если мы не можем получить конечные символы, мы вызываем другое чтение. Но, конечно, это работает, только если у вас есть контроль над серверной стороной и клиентским кодом.
В клиенте / сервере TCP, который я только что написал, я генерирую пакет, который хочу отправить потоку памяти, затем беру длину этого потока и использую его в качестве префикса при отправке данных. Таким образом, клиент знает, сколько байтов данных ему нужно будет прочитать для полного пакета.