Цикл до тех пор, пока ответ TcpClient полностью не прочитается - PullRequest
11 голосов
/ 06 ноября 2011

Я написал простой 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 ]

28 голосов
/ 06 ноября 2011

Природа потоков, построенных поверх сокетов, заключается в том, что у вас есть открытый конвейер, который передает и получает данные до тех пор, пока сокет не будет закрыт.

Однако из-за характера взаимодействия клиент-сервер этот конвейер не всегда имеет контент для чтения. Клиент и сервер должны согласиться отправлять контент по конвейеру.

Когда вы берете абстракцию Stream в .NET и накладываете ее на концепцию сокетов, все еще действует требование соглашения между клиентом и сервером; вы можете звонить Stream.Read сколько хотите, но если сокет, к которому ваш Stream подключен с другой стороны, не отправляет контент, вызов будет просто ждать, пока контент не будет.

Вот почему существуют протоколы. На самом базовом уровне они помогают определить, что такое полное сообщение, которое отправляется между двумя сторонами. Обычно этот механизм является чем-то вроде:

  • Сообщение с префиксом длины, в котором отправляется число считываемых байтов до сообщения
  • Шаблон символов, используемых для обозначения конца сообщения (это менее распространено в зависимости от отправляемого содержимого, чем более произвольной может быть любая часть сообщения, тем менее вероятно, что это будет использовано)

Это сказало, что вы не придерживаетесь вышеупомянутого; ваш вызов Stream.Read просто говорит «прочитать 1024 байта», хотя в действительности не может быть прочитано 1024 байта. Если это так, вызов Stream.Read будет блокироваться, пока он не будет заполнен.

Причина, по которой вызов Thread.Sleep, вероятно, работает, заключается в том, что к тому времени, как проходит секунда, Stream имеет 1024 байта для чтения и не блокируется.

Кроме того, если вы действительно хотите прочитать 1024 байта, вы не можете предполагать, что вызов Stream.Read заполнит 1024 байта данных. Возвращаемое значение для метода Stream.Read сообщает вам, сколько байтов действительно было прочитано. Если вам нужно больше для вашего сообщения, то вам нужно сделать дополнительные звонки на Stream.Read.

Джон Скит написал точный способ сделать это , если вы хотите образец.

2 голосов
/ 06 ноября 2011

Попробуйте повторить

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

, в то время как bytesRead> 0. Это общий шаблон для этого, насколько я помню.Конечно, не забудьте передать соответствующие параметры для буфера.

1 голос
/ 11 октября 2013

Вы не знаете размер данных, которые вы будете читать, поэтому вам нужно установить механизм для принятия решения. Один тайм-аут, а другой использует разделители.

В вашем примере вы читаете любые данные только за одну итерацию (чтение), потому что вы не устанавливаете тайм-аут для чтения и использования значения по умолчанию, равного 0 миллисекунд. Таким образом, вы должны спать только 1000 мс. Вы получаете тот же эффект, используя время получения до 1000 мс.

Я думаю, использование длины данных в качестве префикса не является реальным решением, потому что, когда сокет закрыт обеими сторонами, ситуация ожидания сокета не может быть обработана должным образом. Одни и те же данные могут быть отправлены на сервер и вызвать исключение сервера. Мы использовали префиксную последовательность символов. После каждого чтения мы проверяем данные на начальную и конечную последовательность символов. Если мы не можем получить конечные символы, мы вызываем другое чтение. Но, конечно, это работает, только если у вас есть контроль над серверной стороной и клиентским кодом.

0 голосов
/ 06 ноября 2011

В клиенте / сервере TCP, который я только что написал, я генерирую пакет, который хочу отправить потоку памяти, затем беру длину этого потока и использую его в качестве префикса при отправке данных. Таким образом, клиент знает, сколько байтов данных ему нужно будет прочитать для полного пакета.

...