Что означает «конец потока» при работе с сокетами - PullRequest
18 голосов
/ 16 марта 2009

Когда вы работаете с сокетами в 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 для текущего потока байтов.

Ответы [ 4 ]

23 голосов
/ 16 марта 2009

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

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

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

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

7 голосов
/ 16 марта 2009

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

4 голосов
/ 09 июня 2011

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

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

 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!");
    }
  }
2 голосов
/ 16 марта 2009

как Никита сказал, что это больше задача протокола. Либо вы можете использовать подход заголовка и тела, либо вы можете отправить специальный символ или символ в конце потока, чтобы прервать цикл обработки. Примерно так, если вы отправите слово «[[END]]» в сокет для обозначения конца потока.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...