EndReceive никогда не возвращает 0, как узнать, когда пакеты заканчиваются в tcp/ip?

Это похоже на мой последний вопрос. Я делаю простую программу чата tcp/ip, и у меня возникли небольшие трудности с функцией обратного вызова EndReceive. Я вставил в реализацию Microsoft (см. Ниже), и я заметил, что если сообщение было прочитано полностью, оно не будет запускать функцию обратного вызова, пока не будет отправлено следующее сообщение, т.е. EndReceive никогда не вернет 0. Например, если я отправлю текстовое сообщение из 5 символов и размер буфера будет равен 20, значение read будет равно 5, я обработаю строку и вызову BeginReceive, однако BeginReceive не будет работать, пока сервер не отправит другое сообщение. Раздел else никогда не достигается, потому что s.EndReceive никогда не возвращает 0. Как я узнаю, когда данные были получены полностью?

public static void Read_Callback(IAsyncResult ar){
 StateObject so = (StateObject) ar.AsyncState;
 Socket s = so.workSocket;

 int read = s.EndReceive(ar);

 if (read > 0) {
            so.sb.Append(Encoding.ASCII.GetString(so.buffer, 0, read));
            s.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0, 
                                  new AsyncCallback(Async_Send_Receive.Read_Callback), so);
 }
 else{
      if (so.sb.Length > 1) {
           //All of the data has been read, so displays it to the console
           string strContent;
           strContent = so.sb.ToString();
           Console.WriteLine(String.Format("Read {0} byte from socket" + 
                           "data = {1} ", strContent.Length, strContent));
      }
      s.Close();
 }
}

1 ответ

Решение

EndReceive вполне может получить 0, если поток закрыт и все данные были использованы. Однако для большинства других целей в потоке нет логического перерыва - и даже NetworkStream.DataAvailable только говорит вам, что доступно на клиенте, а не то, что было отправлено сервером. Следовательно: если вы отправляете несколько сообщений и т. Д. В одном и том же потоке, обычно необходимо либо добавлять к каждому блоку ожидаемое число байтов, либо использовать некоторую последовательность разделителей, которая обычно не ожидается в середине содержимого. Первое часто проще, если содержимое может быть произвольным двоичным содержимым (т.е. сложно предсказать последовательность, которая не ожидается), но второе позволяет выполнять потоковую передачу, не зная длины заранее (полезно, если вы вычисляете содержимое на - на лету, так как в противном случае вам может понадобиться сначала буферизовать содержимое, чтобы узнать длину (или выполнить пробный прогон, отбрасывая данные).

Также обратите внимание, что Read не гарантируется возврат всех доступных данных - было бы вполне законно отправить "HELLO", и имеют Read вернуть только 1 (H). Следовательно, вы должны ожидать объединения нескольких Read вызовы в одно сообщение (опять же, с использованием префикса длины или ожидаемой последовательности разделителя).

В этом случае я бы хотел отправить HELLO\0 (где \0 является нулевым байтом). Тогда я бы потреблял через Read пока либо не вернется <=0или \0 найден. в \0 случай, все, что буферизовано перед терминатором, представляет собой одно сообщение; все, что находится после разделителя, должно быть сохранено и обработано как часть следующего сообщения или сообщений. В качестве тестового примера представьте, что вы получаете Read возврат 13, с последовательностью a\0b\0cde\0f\0\0\0gкоторый должен "a", "b", "cde", "f", "", "", "g" (отмечая, что g не должны обрабатываться - это может быть частью более длинных последовательностей, которые еще не получены - буферизировать, пока вы не получите \0).

Другие вопросы по тегам