Что означает "конец потока" при работе с сокетами

При работе с Sockets в Java, как вы можете определить, завершил ли клиент отправлять все (двоичные) данные, прежде чем вы сможете начать их обрабатывать. Рассмотрим, например:

istream = new BufferedInputStream (socket.getInputStream());
ostream = new BufferedOutputStream(socket.getOutputStream());

byte[] buffer = new byte[BUFFER_SIZE];

int count;
while(istream.available() > 0 && (count = istream.read(buffer)) != -1)
{
    // do something..
}

// assuming all input has been read
ostream.write(getResponse());       
ostream.flush();

Я читал подобные сообщения на SO, такие как this, но не смог найти окончательного ответа. Хотя мое решение выше работает, я понимаю, что вы никогда не можете сказать, завершил ли клиент отправлять все данные. Если, например, клиентский сокет отправляет несколько фрагментов данных, а затем блокирует ожидание данных из другого источника данных, прежде чем он сможет отправить больше данных, приведенный выше код вполне может предположить, что клиент завершил отправку всех данных с istream.available() будет возвращать 0 для текущего потока байтов.

Ответы

Ответ 1

Да, вы правы - используя available(), как это ненадежно. Лично я редко использую available(). Если вы хотите прочитать, пока не достигнете конца потока (в соответствии с заголовком вопроса), продолжайте называть read(), пока он не вернет -1. Это легкий бит. Жесткий бит - это то, что вы не хотите конца потока, но конец "того, что сервер хочет отправить вам в данный момент".

Как говорили другие, если вам нужно поговорить по сокету, вы должны объяснить протокол, где данные заканчиваются. Лично я предпочитаю "префикс длины" решения "конец токена сообщения", где это возможно - обычно это делает код чтения намного проще. Тем не менее, это может сделать код записи более сложным, так как вам нужно выработать длину, прежде чем отправлять что-либо. Это боль, если вы можете отправлять много данных.

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

Конечно, все эти материалы для разработки протокола являются спорными, если вы не контролируете протокол: (

Ответ 2

Я думаю, что это больше задача протокола, предполагая, что вы - человек, который пишет и передающую, и принимающую стороны приложения. Например, вы можете реализовать некоторый простой логический протокол и разделить ваши данные на пакеты. Затем разделите пакеты на две части: головку и тело. А затем сказать, что ваша голова состоит из предопределенной начальной последовательности и содержит количество байтов в теле. Забудьте о начальной последовательности и simpy передайте количество байтов в bofy в качестве первого байта пакета. Тогда вы можете решить свою проблему.

Ответ 3

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

На стороне сервера у вас есть:

 void sendMSG(PrintWriter out){
    try {
        //just for example..
        Process p = Runtime.getRuntime().exec("cmd /c dir C:");
        BufferedReader br = new BufferedReader(new InputStreamReader(
            p.getInputStream()));

        //and then send all this crap to the client
        String s = "";
        while ((s = br.readLine()) != null) {
          out.println("MSG");
          out.println(s);
        }
      } catch (Exception e) {
        System.out.println("Command incorrect!");
      }
      out.println("END");
}
//You are not supposed to close the stream or the socket, because you might want to send smth else later..

На стороне клиента у вас есть:

void recieveMSG(BufferedReader in) {
    try {
      while (in.readLine().equals("MSG")) {
        System.out.println(in.readLine());
      }
    } catch (IOException e) {
      System.out.println("Connection closed!");
    }
  }

Ответ 4

как Nikita сказал, что это скорее задача протокола. Либо вы можете пойти по принципу заголовка и тела, либо вы можете отправить специальный символ или символ для конца потока, чтобы прервать цикл обработки. Что-то вроде, если вы отправляете say '[[END]]' на сокет, чтобы обозначить конец потока.