Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Работа с сетью > Как лучше раздать картинку по сокетам?


Автор: Proger10 19.4.2009, 18:26
Подскажите пожалуйста, как бы раздать картинку по другим программам?

Я предварительно пошлю им команду через сокеты (сервер NIO (каждому клиенту выделяю тред, где и обслуживаю клиента) ).
А вот далее чего бы придумать? Я пытался прямо на этом порту передавать всем, но возникают какие-то проблемы типа - сервер всё отправил, а клиент застрял в цикле на получении (прямо на первой строчки) и т.д. и т.п.
Не хотелось бы столкнуться с этим ещё раз. Хочу написать чего-то новое, что исключит эти проблемы. 

Нельзя ли как-то сделать так, чтобы клиенты подконнектились ко мне и слили к себе эту картинку? Но только какая при этом должна быть архитектура программы?

Можно конечно выделить отдельный порт под передачу файла.. да какая разница - и тот застрянет smile

Автор: ivg 19.4.2009, 19:27
Цитата(Proger10 @  19.4.2009,  20:26 Найти цитируемый пост)
а клиент застрял в цикле на получении (прямо на первой строчки)

В этом скорее всего проблема. 1. Картинки "обычно" smile не передаются построчно. 2. Кроме того, конец последовательности байт картинки должен быть каким-то образом обозначен.
Цитата(Proger10 @  19.4.2009,  20:26 Найти цитируемый пост)
Нельзя ли как-то сделать так, чтобы клиенты подконнектились ко мне и слили к себе эту картинку? Но только какая при этом должна быть архитектура программы?
 Embedded HTTP Server, например. Если хочется покопаться - в составе JDK есть пример реализации HTTP(S) сервера на NIO (/sample/nio).

Автор: Proger10 19.4.2009, 20:10
Не, читаю я ессно не построчно. Предварительно шлю размер файла и читаю стрим сокета на клиенте (read) до тех пор пока число байтов общего размера не достигнет. Но почему-то после первого read'а клиент виснет.. (видимо не получив почему-то.. данных. А сервер в это время весь цикл отработал - типа уже послал ему файл, справился smile ).
Вот таким образом я шлю картинки:
http://forum.vingrad.ru/forum/topic-252263.html
(только не зипованные)

Причём иногда оно работает, а иногда нет. Как понять в чём проблема - не пойму. Поэтому наверное проще использовать другой сервер, чем искать дыры в этом smile

Автор: ivg 19.4.2009, 21:24
Полный код сервера, ну и клиента тоже покажите. Судя по этому
Цитата(Proger10 @  19.4.2009,  22:10 Найти цитируемый пост)
Предварительно шлю размер файла и читаю стрим сокета на клиенте (read) до тех пор пока число байтов общего размера не достигнет.
 они явно отличаются от того, что приведено в той теме.

Автор: Platon 20.4.2009, 10:50
Если вы плотно работаете с сетью, но еще не чувствуете уверенности под ногами, рекомендую посмотреть http://vingrad.ru/blogs/COVD/, который ведет COVD.

По поводу http://forum.vingrad.ru/faq/topic-227026.html есть FAQ, мною оформленная.

Автор: Proger10 20.4.2009, 10:52
0. Открываю сокет и коннекчусь (всё ок!)
1. Посылаю через сокет bufferedWriter.write( line + "\n" ); информацию о размере файла с сервера клиенту (приходит то, что надо)
2. Посылка файла с сервера на клиент:
Код
public synchronized void sendFile( String file ) { 

    OutputStream os = null;
    FileInputStream fis = null;

    try{

        fis = new FileInputStream( file );

    } catch ( FileNotFoundException e1 ) {
    }

    System.out.println( "Start to sending file: " + file );
    try {

        os = s.getOutputStream();

        byte[] buffer = new byte[ 8192 ];
        int count;

        while ( ( count = fis.read( buffer ) ) > 0 ) {

            os.write( buffer, 0, count );

        }

        fis.close();
        os.flush();

        System.out.println( "file sent!" );

    } catch ( IOException e2 ) {
    }

}

3. Приём:
Код
try {

    int sChunk = 8192;

    InputStream is = socket.getInputStream();
    FileOutputStream out = new FileOutputStream( "file.png" );

    byte[] buffer = new byte[ sChunk ];
    int length;

    int totalFileSize = Integer.parseInt( fileCmd[ 3 ] );

    System.out.println( "totalFileSize = " + totalFileSize ); // totalFileSize - пришёдший предварительно размер файла

    int currFileSize = 0;
    while ( currFileSize < totalFileSize ) {

        System.out.println( "got a portion" );

        length = is.read( buffer, 0, sChunk );

        System.out.println( "read" );

        out.write( buffer, 0, length );
        currFileSize += length;

    }

    out.close();

} catch ( IOException e ) {
}


Сервер по такому принципу: http://javatalks.ru/sutra33322.php#33322
Ну и клиент как клиент.. smile

Вот так и работает.. точнее не очень smile

Автор: ivg 20.4.2009, 12:15
Исправления к клиенту
Код

15:    int remains = totalFileSize;
16:    while (remains > 0) {
17:
18:        System.out.println("got a portion");
19:        
20:        length = is.read(buffer, 0, Math.min(sChunk, remains));
21:        if (length < 0) break;
22:        System.out.println("read");
23:
24:        out.write(buffer, 0, length);
25:        remains -= length;
26:
27:    }

Цитата из JavaDoc для InputStream#read(byte[] b, int off, int len)
Цитата
The default implementation of this method blocks until the requested amount of input data len has been read, end of file is detected, or an exception is thrown.

Ну а ни выходной поток, ни сокет на сервере вы не закрываете.
PS: Подавление исключений - дурной тон. smile 

Автор: Proger10 20.4.2009, 14:37
Ну выходной поток я не могу закрыть, потому как это приведёт к закрытию сокета, а мне в общем-то ещё работать через него далее... smile

Добавлено через 1 минуту и 5 секунд
Интересное исправление кода клиента! Спасибо!

Автор: Proger10 21.4.2009, 11:55
Кстати, по поводу:
Цитата
Цитата из JavaDoc для InputStream#read(byte[] b, int off, int len)Цитата
The default implementation of this method blocks until the requested amount of input data len has been read, end of file is detected, or an exception is thrown.

а когда этот экзепшн будет выброшен в случае чего? read ведь блочится намертво и хоть ты сколько жди походу..
Можно ли как-то поставить timeout на получение порции данных в read?
Тогда если бы он не получал данные, то не вешал бы прогу (ну или хотя бы на 20 секунд её вешал) - оно и так у меня в отдельном потоке, так что цикл не вешает программу, но неплохо бы, чтобы этот цикл всё-таки оно обходило бы как-то (после него тоже инструкции к выполнению есть smile ).

Автор: ivg 22.4.2009, 08:22
1. NIO неблокирующие операции.
2. http://java.sun.com/javase/6/docs/api/java/net/Socket.html#setSoTimeout(int)

Автор: Proger10 27.4.2009, 22:52
Кстати.. А я вот подумал тут.. А нафига я вообще делаю именно такой вызов read'a у клиента.. можно ведь сделать и так:
Код
length = is.read( buffer );

и действительно всё сработало! smile

Автор: Platon 28.4.2009, 11:49
Proger10, я бы не стал раньше времени ликовать. Сработало хорошо на локальной машине с маленьким файлом, вы большой файлик скачать. У вас этот метод прочитает только первую порцию данных.

Автор: Proger10 30.4.2009, 15:34
Platon, а почему? В чём проблема?

Большой, это насколько большой?

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

Добавлено через 1 минуту и 50 секунд
Предполагаю вот такой метод передачи использовать (первое сообщение)
http://forum.vingrad.ru/forum/topic-252355.html

Автор: Platon 30.4.2009, 20:48
Proger10, внимательней:
Цитата(Platon @  28.4.2009,  12:49 Найти цитируемый пост)
У вас этот метод прочитает только первую порцию данных. 


Тест подтверждает, что считываются только данные, которые уже получены в буффер сокета.
Код

byte[] arrr = new byte[16000000];
        InputStream s = new URL("http://forum.vingrad.ru/forum/java-forum.html").openStream();
        int am = s.read(arrr);
        System.out.println(am);

Читал локальный файл, программа считывает 16*10^6 байтов в легкую за раз.

Автор: COVD 2.5.2009, 07:37
Цитата

Читал локальный файл, программа считывает 16*10^6 байтов в легкую за раз.

Кстати, вот решение в ситуации, когда размер данных точно не известен - удобно их сначала в ByteArrayOutputStream записывать, а потом уже оттуда извлекать в виде byte[] - http://developers.sun.com/mobility/midp/questions/calcbyte/

Автор: Proger10 12.5.2009, 22:20
COVD, так там изначально получают длину данных или нет?
Код
int length = connection.getLength();


А как же я её получу, это ж поток... Который читается до тех пор пока другая сторона не закроет сокет..

Автор: COVD 12.5.2009, 22:37
Цитата

А как же я её получу, это ж поток... 

В политбюро не дураки сидят - ночью полетите (шутка). Там предлагается читать из файла (или соединения) в другой, временный поток, который по сути выполняет роль безразмерного буфера ( как ArrayList, например). После того, как чтение завершено, и, следовательно, фактическая длина данных известна, из этого временного буферного потока данные копируются в byte[], т.е. массив создается, когда уже известен необходимый размер. Это просто удобное решение, потому что не надо заранее создавать массив большого размера и увеличивать его длину при необходимости. Этим временный поток занимается.  

Автор: Proger10 13.5.2009, 00:26
Мы вот об этом коде?
Код
HttpConnection connection = (HttpConnection)
Connector.open("http://www.test.com/servlet/readData");

InputStream inputStream = connection.openDataInputStream();

int length = connection.getLength();
byte[] byteArray;
if (length == -1) {
// Reading from an HTTP 1.0 server or a chunked HTTP 1.1
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int c ;
while (true) {
c = inputStream.read();
if (c == -1)
break;
baos.write(c);
}
byteArray = baos.getBytes();
} else {
// Reading a Content-Length labeled payload
byteArray = new byte[length];
inputStream.readFully(byteArray);
}


А.. ну насколько я понял тут length вообще не нужен?
Идея этого кода в том, чтобы читать очередной байт через: inputStream.read() и по мере каждого байта подсчитать длину?
Чего-то походу здесь уже какая-то ошибка.. инае зачем нам повторно расширять массив и перекачивать в него данные, если мы уже при подсчёте получаем эти байты.. smile

Автор: Proger10 13.5.2009, 00:57
Кстати! А что плохого вот в таком методе приёма?

Код
int c = -1;
while( ( c = socketInputStream.read() ) != -1 ) {
    fileOutputStream.write( c );
}

Потихоньку тянем файл.. Таким образом и большой вытащить получится наверное..? smile

Автор: COVD 13.5.2009, 03:09
Цитата

Мы вот об этом коде?


об этом, вестимо

Цитата

Идея этого кода в том, чтобы читать очередной байт через: inputStream.read() и по мере каждого байта подсчитать длину?


читаем байт и пихаем его в ByteArrayOutputStream. Мы длину сами не подсчитываем. ByteArrayOutputStream обо всем заботится. 

Цитата

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


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

Цитата

Кстати! А что плохого вот в таком методе приёма?


Наоборот, это самый лучший вариант. Для перевалочного пункта. Например, сервлет. Он откуда-то читает (БД, файл,другой компьютер) и тут же отправляет клиенту (браузеру) не буферизуя и, следовательно, не расходуя памяти. Идеальный посредник.  А вот на клиенте, т.е. у конечного потребителя, буферизация бывает неизбежна, если данные имеют смысл только при полной загрузке. Например, jpeg. Пока картинка полностью не загрузилась, ее невозможно употребить по назначению.

Автор: Platon 13.5.2009, 08:53
Цитата(COVD @  13.5.2009,  04:09 Найти цитируемый пост)
ее невозможно употребить по назначению.

Однако браузеры справляются smile

Автор: COVD 13.5.2009, 15:18
Спасибо за поправку. Рад за браузеры smile . Ну, тогда не jpeg, а другой тип данных. Тут ведь не только вопрос умения клиента "потреблять" загружаемые данные "на лету", не дожидаясь конца загрузки. Что будет, если данные загрузятся наполовину и произойдет разрыв соединения? Клиент может попытаться повторить попытку. Если загружаемые данные накапливались в буфере, то буфер просто очищается и все начинается сначала. А что будет, если данные уже пошли в дело, породили какие-то действия, новые данные или сообщения? Откатывать назад последствия неполной обработки данных в этом случае может оказаться намного сложнее. Тут мы к понятию "транзакция" подбираемся. Хотя для клиентов это не типично, а для тонких и вообще, наверное, не применимо.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)