Петля до полного ответа 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();

Ответ 1

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

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

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

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

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

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

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

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

Jon Skeet написал точный способ сделать это, если вам нужен образец.

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

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