Джава. Чтение из BufferedInputStream и запись в FileOutputStream

Я пытаюсь написать клиентскую и серверную часть на java. Серверная часть в порядке (проверено на нескольких клиентах).

Значит, проблема на стороне клиента. Я выделяю память для bytearray, читаю из BufferedInputStream и пишу в этот bytearray. Затем запись из bytearray в FileOutputStream. Все в порядке, но свободное пространство bytearray заполняется NULL, поэтому полученный файл неверен (например, изображения).

Я нашел 2 решения этой проблемы:

  1. Прочитать до bytearray до конца файла (но я не знаю, где конец файла)
  2. Читать от BufferedInputStream до FileInputStream, но это не работает:

Мне действительно нужно получить заголовок и файл. Вывести заголовок на консоль и записать файл на диск.

Полный источник

public class SClient {
private static int bufferSize = 8192;
/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    System.out.println("Enter the address:");
    BufferedReader bufferRead = new BufferedReader
                (new InputStreamReader(System.in));

    try {
        String address = bufferRead.readLine();
        System.out.println("Enter the extention of receiving file:");
        String fileExt = bufferRead.readLine();
        // TODO code application logic here
        Socket socket = new Socket(address,4040);
        BufferedInputStream bis = new BufferedInputStream
                (socket.getInputStream());

        BufferedOutputStream bout = new BufferedOutputStream
                (socket.getOutputStream());
        System.out.println("Enter the request:");
        String message = bufferRead.readLine();// GET /index.html HTTP/1.0

        System.out.println("Header read");
        if(message!=null){
            bout.write(message.getBytes());
        }
        FileOutputStream fout = new FileOutputStream("out"+fileExt);
        String s1 = "\r\n\r\n";
        bout.write(s1.getBytes());
        bout.flush();
        System.out.println("Header sent");

        byte[] res = new byte[bufferSize];
        int got;
        while((got = bis.read(res))!=-1){
            fout.write(res,0,got);
        }
        fout.close();
        bout.flush();
        socket.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
  }
}

Источник на стороне сервера:

    String endLine = "\r\n";
    File f = new File(fileName);
    FileInputStream fstream;
    fstream = new FileInputStream(f);
    response = "HTTP/1.0 200 OK" + endLine;
    header = "Content-type: "+ contentType + endLine + "Content-length: " + f.length() + endLine + endLine;
    bout.write(response.getBytes());
    bout.write(header.getBytes());
    while(fstream.read(buffer) != -1) {
        bout.write(buffer);
    }
    System.out.println("Message sent");
    bout.flush();
    socket.close();

person sanatik    schedule 13.09.2013    source источник


Ответы (3)


Вы должны помнить количество байтов, которые вы читаете в свой буфер, и должны записывать только эти байты обратно. Как здесь:

int got;
while ((got = bis.read(res)) != -1) {

    fout.write(res, 0, got);
}
person Matthias    schedule 13.09.2013
comment
Спасибо, ваш код помог мне удалить NULL после заголовка, но изображение по-прежнему не отображается, потому что в конце файла есть нули. - person sanatik; 13.09.2013
comment
@sanatik Socket потоки не заканчиваются на -1, потому что пока они не закрыты, они не заканчиваются. Вместо этого они полагаются на символы терминатора (например) или известные длины байтов... - person MadProgrammer; 13.09.2013
comment
@sanatik Я думаю, вы уже правильно закрыли файл, поэтому вы не видите частично очищенный файл или что-то в этом роде. Я также предполагаю, что вы просто хотите отправить один файл через свой сокет, а не что-то еще. Потому что код читает все из сокета, пока он не закроется. Может быть, покажите код для загрузки / показа изображения или объясните, что вы хотите сделать, чтобы я мог проверить, что с этим не так. - person Matthias; 13.09.2013
comment
@sanatik Если у вас есть нули в конце файла, вы не использовали этот код. Вам нужно использовать его на обоих концах. - person user207421; 13.09.2013
comment
@EJP что означает оба конца? - person sanatik; 13.09.2013
comment
@Matthias смотрите полный источник выше - person sanatik; 13.09.2013
comment
Код клиента @sanatik выглядит нормально, как ваш сервер отправляет файл. Вам нужно куда-то записать байты, не могли бы вы также поделиться этим фрагментом кода? Вероятно, ошибка кроется в нем. - person Matthias; 13.09.2013
comment
@матиас посмотри - person sanatik; 13.09.2013
comment
@sanatik ааа ок. Итак, ваш сервер не отправляет файл изображения или что-то в этом роде, он отправляет ответ http. Итак, вы ожидаете получить html-файл на своем клиенте. Если html, который вы получаете, недействителен, то причиной, вероятно, является аналогичная ошибка при чтении fstream и записи этих данных в ваш сокет. Если вы посмотрите это в браузере, браузер может удалить дополнительные данные, поскольку вы отправляете контент определенной длины в своем http-заголовке. Однако если вы сохраните ответ в файле, вы увидите конечные нулевые значения, которые браузер игнорирует? - person Matthias; 13.09.2013
comment
@sanatik ЕСЛИ ваш сервер соответствует правильным требованиям http, тогда он должен отправлять кучу информации заголовка ПЕРЕД изображением, например HTTP/1.1 200 OK Date: Fri, 13 Sep 2013 07:26:10 GMT Server: Apache/2.2.24 (FreeBSD) PHP/5.4.13 mod_ssl/2.2.24 OpenSSL/0.9.8y Last-Modified: Sun, 25 Aug 2013 23:42:46 GMT ETag: "376467-3d47f-4e4ce325a0980" Accept-Ranges: bytes Content-Length: 251007 Connection: close Content-Type: image/jpeg - person MadProgrammer; 13.09.2013
comment
@ Матиас Су, что мне делать? Как удалить дополнительные данные на моем клиенте? - person sanatik; 13.09.2013
comment
@MadProgrammer в переменной заголовка У меня есть эта информация, может быть не полная, а часть. Это действительно необходимо? - person sanatik; 13.09.2013
comment
Я так понимаю сервер будет присылать число новых строк между шапкой и картинкой, это терминатор между разделами, для этого нужно читать... - person MadProgrammer; 13.09.2013
comment
@sanatik Чтобы решить вашу проблему, не записывайте дополнительные данные на стороне сервера. Таким образом, проверьте, сколько байтов вы читаете из файла, и записывайте эти файлы только в выходной поток вашего сокета, как показано в ответе выше. Я не знаю, чего вы пытаетесь достичь с помощью своего клиента и сервера, но обычно http не очень простой протокол для реализации. Если вы хотите установить связь между клиентом и сервером, который вы проектируете, вам следует взглянуть на комментарии MadProgrammer, это очень ценный совет. - person Matthias; 13.09.2013
comment
@MadProgrammer Я добавляю \r\n\r\n в конец заголовка, разве этого недостаточно? Также мне нужно будет разделить на стороне клиента заголовок и файл после того, как будет выполнено получение. - person sanatik; 13.09.2013
comment
@sanatik Это то, что вы написали серверу, но сервер должен отправлять заголовок ответа, который вы не принимаете во внимание ... - person MadProgrammer; 13.09.2013
comment
@Mattias MadProgrammer, спасибо, Гюйс! Оно работает! Мне просто нужно было добавить int lol; while((смеется = fstream.read(буфер)) != -1) { bout.write(буфер,0,смеется); } На мой сервер! - person sanatik; 13.09.2013
comment
Также у меня недостаточно репутации, чтобы добавить ваши ответы! Большое спасибо! - person sanatik; 13.09.2013
comment
@sanatik «Оба конца» означает оба узла: сервер и клиент. - person user207421; 12.09.2015

Не ответ, а предложение...

Это не выглядит правильно. По сути, вы читаете массив байтов максимум до bufferSize, а затем просто записываете один byte в выходной поток. Вы также рискуете получить ошибку индекса за пределами границ, потому что вы увеличиваете i в каждом несвязанном цикле...

while (bis.read(res) != -1) {
    fout.write(res[i]);
    i++;
}

Вместо этого вы должны использовать что-то более похожее на...

int bytesRead = -1;
while ((bytesRead = bis.read(res)) != -1) {
    fout.write(res, 0, bytesRead);
}

Который запишет массив байтов до количества прочитанных байтов...

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

Теперь вы можете вставить какой-нибудь символ терминатора, но это потенциально может повредить выходной файл.

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

Это потребует небольшого изменения вашего сервера, он должен иметь возможность отправлять значение long, указывающее размер изображения (в байтах), которое нужно прочитать, и \n

BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String header = br.readLine();
long expectedBytes = Long.parseLong(header);

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

int bytesRead = 0;
int totalBytes = 0;
while (totalBytes < expectedBytes) {
    bytesRead = bis.read(res);
    fout.write(res, 0, bytesRead);
    totalBytes += expectedBytes;
}

// Flush and close your steams as required.

Я сделал что-то похожее на вопрос о совместном использовании экрана

person MadProgrammer    schedule 13.09.2013
comment
Как я читал, чтение по байтам - лучший способ. Использование BufferedReader может привести к некоторым искажениям. - person sanatik; 13.09.2013
comment
@sanatik Возможно, вам придется немного расширить это, я не уверен, что понимаю, что вы пытаетесь сказать. Да, чтение/запись массива байтов более эффективно, чем простое чтение/запись одного байта, если размер буфера соответствует базовым сетевым требованиям. - person MadProgrammer; 13.09.2013
comment
@sanatik «Искажения правок», например что? - person user207421; 12.09.2015

Запомните канонический способ копирования потоков в Java:

while ((count = in.read(buffer)) > 0)
{
    out.write(buffer, 0, count);
}

Используйте любой размер буфера больше нуля, обычно 4 КБ или 8 КБ.

person user207421    schedule 13.09.2013