Проблема закрытия сокета - последняя часть данных потеряна - PullRequest
5 голосов
/ 16 декабря 2009

У меня есть Java-программа, которая принимает соединения, получает HTTP-запросы и отправляет HTTP-ответы, а некоторые данные хранятся в файле (это часть кэширующего прокси). Удаление всего несущественного, мой код выглядит так:

FileInputStream fileInputStream = new FileInputStream(file);
OutputStream outputStream = socket.getOutputStream();
byte[] buf = new byte[BUFFER_SIZE];
int len = 0;
while ((len = fileInputStream.read(buf)) > 0) {
    outputStream.write(buf, 0, len);
}
outputStream.flush();
socket.close();

Этот код выполняется в отдельном потоке для каждого подключенного клиента.

Когда я работаю с небольшими файлами (.htm, .gif, .swf и т. Д.), Все работает нормально (однако в браузере я не вижу ничего плохого). Но когда я загружаю большие файлы (.iso), особенно несколько файлов одновременно, когда система загружена, иногда я получаю действительно странное поведение. Браузер загружает 99,99% файла, и если количество не загруженных байтов меньше BUFFER_SIZE, загрузка останавливается на несколько секунд, а затем браузер сообщает, что произошла ошибка. Я не могу понять, что происходит, потому что все данные успешно прочитаны и даже все данные успешно записаны в outputStream. Как видите, я даже выполняю flush (), но это не дает результата.

Может кто-нибудь объяснить мне, что происходит?

EDIT
Загруженный проект на filehosting.org.
Скачать исходные файлы . Существует zip-архив с исходным кодом, Build.xml и Readme.txt. Используйте муравей, чтобы построить решение. Описанная проблема возникает в ClientManager.java, там вы найдете комментарий.

Ответы [ 4 ]

2 голосов
/ 16 декабря 2009

На основе быстрого траления на базе кода JDK 1.6:

  • socket.getOutputStream() возвращает SocketOutputStream экземпляр
  • flush() действительно не влияет на SocketOutputStream экземпляр
  • write() в экземпляре SocketOutputStream, похоже, ничего не буферизирует в коде Java
  • shutdownOutput() должен обеспечить запись любых ожидающих данных перед отключением выходной стороны сокета. По крайней мере, в комментариях так сказано.

Однако, некоторые реализации Socket и т. Д. Являются нативными методами, и я не стал вдаваться в подробности.

Исходя из того, что я могу сказать, "правильная" последовательность будет:

socket.shutdownOutput();
socket.close();

Однако вы говорите, что пробовали это. Возможно ли, что приложение на другом конце рано закрывает соединение TCP / IP?

Еще одна мысль: вы пытались setSoLinger(true, 60000), но 60000 секунд, возможно, дольше, чем позволяет ОС. Попробуйте setSoLinger(true, 60) и попробуйте сделать это, прежде чем открыть поток вывода.

1 голос
/ 16 декабря 2009

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

Вы можете попросить Java немного подождать,

socket.setOption(SocketOptions.SO_LINGER, new Integer(60));
1 голос
/ 16 декабря 2009

Не уверен, почему это произойдет, я не вижу проблемы с вашим кодом. Вы пытались использовать BufferedInputStream для включения FileInputStream?

0 голосов
/ 17 декабря 2009

Я привык сначала посылать длину, а затем ждать, пока я получу эти данные, но если вы не можете этого сделать, этот блог должен быть полезен:

http://vadmyst.blogspot.com/2008/04/proper-way-to-close-tcp-socket.html

Это для .NET, но основная логика должна быть такой же.

После того, как вы отправили данные, вам нужно, чтобы отправляющая сторона закрыла свое соединение, затем выполните shutdownOutput, и другая сторона продолжит чтение, пока не возникнет ситуация, когда ничего не может быть прочитано, или исключение не станет бросили, и тогда у тебя должно быть все.

...