2011-10-04 21 views
16

gram około z biblioteki NIO. Próbuję odsłuchać połączenie na porcie 8888 i gdy połączenie zostanie zaakceptowane, zrzuć wszystko z tego kanału do somefile.Java NIO: transferFrom do końca strumienia

Wiem, jak to zrobić z ByteBuffers, ale chciałbym, aby działało z rzekomo super wydajnym FileChannel.transferFrom.

To jest to, co mam:

ServerSocketChannel ssChannel = ServerSocketChannel.open(); 
ssChannel.socket().bind(new InetSocketAddress(8888)); 

SocketChannel sChannel = ssChannel.accept(); 
FileChannel out = new FileOutputStream("somefile").getChannel(); 

while (... sChannel has not reached the end of the stream ...)  <-- what to put here? 
    out.transferFrom(sChannel, out.position(), BUF_SIZE); 

out.close(); 

Więc moje pytanie brzmi: Jak mogę wyrazić "transferFrom jakiś kanał, aż zostanie osiągnięta end-of-stream"?


Edycja: Zmiana 1024 do BUF_SIZE, od wielkości użytego buforu, nie ma znaczenia dla kwestii.

Odpowiedz

4

Nie jestem pewien, ale JavaDoc mówi:

próba jest do zapoznania się liczyć bajtów z kanału źródłowego i zapisać je do pliku tego kanału zaczynając dana pozycja. Wywołanie tej metody może lub nie może przesłać wszystkich żądanych bajtów; czy to nie tak zależy na natur i stwierdza z kanałów. Mniej niż wymagana liczba bajtów zostanie przeniesiona, czy kanał źródło ma mniej niż liczyć bajtów Pozostała, czy kanał źródło nie jest blokowanie i ma mniej niż licznik bajtów bezpośrednio dostępne w buforze wejściowym.

Chyba można powiedzieć, że informując go skopiować nieskończone bajtów (oczywiście nie w pętli) będzie wykonać zadanie:

out.transferFrom(sChannel, out.position(), Integer.MAX_VALUE); 

Tak, myślę, że kiedy gra gniazdo jest zamknięty, stan zostanie zmieniony, co zatrzyma metodę transferFrom.

Ale jak już powiedziałem: Nie jestem pewien.

+0

pokonać mnie do niego. Można jednak użyć wartości "Long.MAX_VALUE" zamiast "Integer.MAX_VALUE". –

+0

Dobrze, ale co, jeśli w pierwszym wywołaniu przeniesiemy 3 bajty na 10? – aioobe

+0

Może przetestuj swój kanał źródłowy: ma on ['int read (ByteBuffer)'] (http://download.oracle.com/javase/6/docs/api/java/nio/channels/ReadableByteChannel.html#read% 28java.nio.ByteBuffer% 29). Jeśli to zwróci '-1', to nie ma w nim nic, a więc wszystko zostało przeniesione do' FileChannel'. Jeśli chcesz czegoś solidniejszego, to o wiele lepiej z 'ByteBuffer's (** ponowne użycie ** są i tak wydajne). –

4

Odpowiadając na zapytanie bezpośrednio:

while((count = socketChannel.read(this.readBuffer)) >= 0) { 
    /// do something 
} 

Ale jeśli to jest to, czego robić nie wolno używać żadnych korzyści z non-blocking IO bo faktycznie używać go dokładnie tak, jak blokowanie IO. Punktem niezablokowania IO jest to, że jeden wątek sieciowy może obsługiwać wielu klientów jednocześnie: jeśli nie ma niczego do odczytu z jednego kanału (tj. count == 0), możesz przełączyć się na inny kanał (który należy do innego połączenia klienta).

Tak, pętla powinna faktycznie iteracji różnych kanałów zamiast czytania z jednego kanału, dopóki nie jest skończona.

Spójrz na ten samouczek: http://rox-xmlrpc.sourceforge.net/niotut/ Wierzę, że pomoże ci zrozumieć problem.

+1

Ale wywołanie może zwrócić 0 bajtów bez osiągnięcia końca strumienia, prawda? – aioobe

+0

@aioobe Only (a) w trybie bez blokowania, gdy powinieneś używać selektora w każdym razie, nie pętli, lub (b) jeśli długość bufora wynosi zero, co jest błędem programowania. – EJP

0

transferFrom() zwraca liczbę. Po prostu wywołuj to, przesuwając pozycję/offset, aż zwróci zero.Ale zacznij od znacznie większej liczby niż 1024, bardziej jak megabajt lub dwa, w przeciwnym razie nie otrzymasz wiele korzyści z tej metody.

EDIT rozwiązać wszystkie poniższe komentarze, dokumentacja mówi, że „Mniej niż wymaganej liczby bajtów zostanie przeniesiona jeśli kanał źródło ma mniej niż liczyć bajtów pozostały, lub jeżeli kanał źródłowy jest nieblokującą ma mniej niż liczba bajtów natychmiast dostępnych w swoim buforze wejściowym. " Pod warunkiem, że jesteś w trybie blokowania, nie zwróci zero, dopóki nie pozostanie w źródle. Tak więc pętla, dopóki nie zwróci zero, jest poprawna.

EDIT 2

Sposoby transferu pewnością MIS zaprojektowane. Powinny one zostać zaprojektowane tak, aby zwracały -1 na końcu strumienia, podobnie jak wszystkie metody read().

+1

Dokumentacja metody mówi, że zwraca 'Liczba bajtów, być może zera, które zostały faktycznie przesłane. Nie oznacza to, że nie może wrócić z '0' bezpośrednio po pierwszym wywołaniu bez przeniesienia czegokolwiek. (Kolejne pytanie brzmi: kiedy i dlaczego miałoby to nastąpić?) Jednak OP nie może na tym polegać. Zaczynam myśleć, że jedynym sposobem, by wiedzieć na pewno, że ta metoda przenosi wszystko z kanału do pliku jest wiedzieć z góry, ile bajtów zostanie przesłanych. –

+0

Zostało to już omówione w sekcji komentarzy dla [tej odpowiedzi] (http://stackoverflow.com/questions/7651528/java-nio-transferfrom-until-end-of-stream/7651704#7651704). Jednak OP chce rozwiązania typu "one-liner_" (mogę się do tego odnieść, ponieważ byłoby to niesamowite). –

+0

@EJP, czy masz jakieś argumenty lub dobre referencje dla faktu, że powrót do zwykłego "odczytu" (i ewentualnie pisanie takiego bajtu "ręcznie") jest rzeczywiście najlepszym rozwiązaniem tego problemu? – aioobe

1

rzekomo super wydajny FileChannel.transferFrom.

Jeśli chcesz zarówno korzyści dostępu DMA i powodują blokowania IO najlepszym sposobem jest pamięć-map plik, a następnie po prostu czytać z gniazda w buforach pamięci odwzorowany.

Ale to wymaga wcześniejszego przydzielenia pliku.

9

Istnieje kilka sposobów na załatwienie sprawy. Niektóre informacje dodatkowe, jak program trasnferTo/From jest implementowany wewnętrznie i kiedy może być lepszy.

  • 1. i wszystkim należy wiedzieć, ile bajtów trzeba Xfer, to znaczy używać FileChannel.size() określić maksymalną dostępną i podsumować wynik. Przypadek odnosi się do: FileChannel.trasnferTo(socketChanel)
  • Metoda nie zwraca -1
  • Metoda jest emulowana w systemie Windows. Windows nie ma funkcji API, która prześle dane z pliku filedescriptor do gniazda, ma jedno (dwa) do wypakowania z pliku oznaczonego nazwą - ale jest to niezgodne z interfejsem API języka Java.
  • W systemie Linux używany jest standard sendfile (lub sendfile64), w systemie Solaris jest to sendfilev64.

w skrócie for (long xferBytes=0; startPos + xferBytes<fchannel.size();) doXfer() będzie działać do przesyłania z pliku -> gniazda. Nie ma funkcji systemu operacyjnego, która przesyła z gniazda do pliku (który jest zainteresowany programem OP). Ponieważ dane gniazda nie są w pamięci podręcznej systemu operacyjnego, nie można tego zrobić tak skutecznie, jest to emulowane. Najlepszym sposobem zaimplementowania kopii jest standardowa pętla za pomocą odpytanego bezpośredniego ByteBuffera z buforem odczytu gniazda. Ponieważ używam tylko niezablokowanego IO, które obejmuje również selektor.

takiej sytuacji: Chciałbym dostać pracy z rzekomo bardzo wydajny” - nie jest skuteczny i jest emulowane na wszystkich systemów operacyjnych, a więc będzie to skończyć transferu gdy gniazdo jest zamknięty wdzięcznie Funkcja nie będzie nawet rzucić odziedziczony wyjątek IOException, pod warunkiem, że istnieje JAKIKOLWIEK transfer (Jeśli gniazdo było czytelne i otwarte)

Mam nadzieję, że odpowiedź jest jasna: jedyne interesujące użycie File.transferFrom dzieje się, gdy źródło jest Plik najbardziej efektywny (i interesujący przypadek) to plik-> gniazdo i plik-> plik jest realizowany przez filechanel.map/unmap(!!).

+1

"Po zaakceptowaniu połączenia, zrzuć wszystko z tego kanału do pliku". Tak więc wejście jest kanałem gniazda, więc nie może on wiedzieć, ile danych w nim zawartych, chyba że nadawca przesyła najpierw słowo długości. Nie wiem, co oznacza "użyj FileChannel.size() do określenia maksymalnego dostępnego i podsumuj wynik". – EJP

+0

@EJP - czytaj dalej, mówi gniazdko -> plik jest daremny i emulowany. Używanie dedykowanego bezpośredniego ByteBuffera, zwymiarowanego do bufora odczytu gniazda, jest sposobem na wykonanie tego zadania. 1024 jest bardzo złym rozmiarem do odczytu (przydzielona pamięć jest zawsze> pageSize [4k], więc 1024 po prostu marnuje pamięć i wiąże się z większą liczbą wyświetleń jądra). Pociski przed sobą pokazują wewnętrznie metodę 'transferu'. – bestsss

+1

Oczywiście, ale pierwsza część odpowiedzi na temat pliku-> gniazda pozostaje nieistotna dla pytania OP. – EJP

1

ten sposób:

URLConnection connection = new URL("target").openConnection(); 
File file = new File(connection.getURL().getPath().substring(1)); 
FileChannel download = new FileOutputStream(file).getChannel(); 

while(download.transferFrom(Channels.newChannel(connection.getInputStream()), 
     file.length(), 1024) > 0) { 
    //Some calculs to get current speed ;) 
} 
0

Opierając się na górze, co inni ludzie tutaj pisałem, oto prosty sposób pomocnikiem, który realizuje cel:

public static void transferFully(FileChannel fileChannel, ReadableByteChannel sourceChannel, long totalSize) { 
    for (long bytesWritten = 0; bytesWritten < totalSize;) { 
     bytesWritten += fileChannel.transferFrom(sourceChannel, bytesWritten, totalSize - bytesWritten); 
    } 
}