2012-05-13 20 views
9

Obecnie piszę naiwny kod sieci dla projektu, a kumpel podpowiadał mi, że kiedy wysyłam paczkę informacji z serwera do wszystkich klientów w sposób powtarzalny, może pojawić się silne opóźnienie, gdy jeden z klienci nie reagują poprawnie.Czy OutputStream w Java blokuje? (Gniazda)

Jest znany z trollingu, więc byłem sceptycznie nastawiony do implementacji wątku dodatkowego, który jest teraz odpowiedzialny za wysyłanie danych do klienta, mając kolejkę, którą serwer po prostu dodaje pakiety, a następnie odczytuje ją przez wątek wysłać dane.

Pytanie, które mam teraz po przemyśleniu, to pogoda, a nie OutputStream z Java Socket faktycznie kolejki rzeczy, które chce wysłać sam, eliminując w ten sposób konieczność kolejki wcześniej. Możliwość wystąpienia poważnych problemów występuje tylko wtedy, gdy serwer jest blokowany, o ile nie otrzyma odpowiedzi od klienta, że ​​odebrany obiekt został odebrany.

Dzięki.

+0

Wystąpił podobny problem, jeśli próbuję uzyskać ouput lub strumień wejściowy w funkcji uruchamiania wątku.Problem z blokowaniem (zarówno dla getInputStream, jak i getOutputStream) gniazda polega na tym, że jest on w funkcji uruchamiania ... rozwiązaniem wydaje się umieszczenie go w konstruktorze, zapisanie zmiennej, a następnie odwołanie do zmiennej w uruchomieniu. – Zimm3r

Odpowiedz

4

Oczywiście przy zapisie do gniazda zapis jest buforowany. Obiekt Socket ma metodę setSendBufferSize() do ustawiania tego rozmiaru bufora. Jeśli Twój zapis może zostać zbuforowany w tym buforze, to oczywiście możesz go natychmiast wykonać na następującym gnieździe. W przeciwnym razie ten bufor musi zostać natychmiast przepłukany do klienta. Podczas spłukiwania będziesz zablokowany. Jeśli chcesz uniknąć zablokowania podczas płukania bufora, musisz użyć SocketChannel w niezablokowanych we/wy. W każdym razie, najlepszą opcją jednoczesnego zapisu do wielu gniazd, jest zarządzanie każdym gniazdem za pomocą innego wątku, aby wszystkie zapisy mogły być wykonywane w tym samym czasie.

+5

"W przeciwnym razie ten bufor musi zostać natychmiast przepłukany do klienta" nie jest prawidłowym sposobem umieszczenia go. Bufor jest zapisywany do sieci przez cały czas, asynchronicznie, ale nie można go wysłać, dopóki nie ma miejsca w buforze odbiorczym peera. Jeśli uczestnik nie czyta lub czyta spowolnienie, jego bufor odbiorczy wypełni się, więc Twój bufor wysyłania wypełni się, więc twoje kolejne zapisy zostaną zablokowane. – EJP

+0

Mimo że stanowisko Toma było lepszym przykładem, to właśnie tego szukałem pod względem wyjaśnień EJP. Dziękuję Ci bardzo. – salbeira

8

Twój przyjaciel ma rację, ale ma więcej wspólnego z działaniem protokołu . W dużym uproszczeniu pakiety wysłane do klienta muszą zostać potwierdzone. Jeśli klient nie odpowiada (nie odczytuje danych przychodzących, komputer jest obciążony dużym obciążeniem itp.) Serwer nie otrzyma potwierdzeń i przestanie wysyłać dane. Ten mechanizm wbudowany w TCP/IP zapobiega wysyłaniu dużych ilości danych przez jeden koniec komunikacji bez upewnienia się, że drugi koniec je otrzymał. Na zmianę unika się konieczności ponownego wysyłania dużych ilości danych.

W języku Java oznacza to blokowanie zapisu na OutputStream. Bazowy stos TCP/IP/system operacyjny nie pozwala na wysyłanie większej ilości danych, dopóki klient nie będzie gotowy do odbioru.

Możesz to łatwo przetestować! I wdrożone prosty serwer, który akceptuje połączenie, ale nie może odczytać dane przychodzące:

new Thread(new Runnable() { 
    @Override 
    public void run() { 
     try { 
      final ServerSocket serverSocket = new ServerSocket(4444); 
      final Socket clientSocket = serverSocket.accept(); 
      final InputStream inputStream = clientSocket.getInputStream(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 
}).start(); 

i prosty klient, który po prostu wysyła tyle danych, ile to możliwe w 4K partiach:

final Socket client = new Socket("localhost", 4444); 
final OutputStream outputStream = client.getOutputStream(); 
int packet = 0; 
while(true) { 
    System.out.println(++packet); 
    outputStream.write(new byte[1024 * 4]); 
} 

Pętla klient wisi na mój komputer po 95 iteracjach (twój przebieg może się różnić). Jednak jeśli przeczytam od inputStream w wątku serwera - pętla będzie się włączać i wyłączać.

+0

Wystąpił podobny problem, jeśli próbuję uzyskać ouput lub strumień wejściowy w funkcji uruchamiania wątku. Problem z blokowaniem (zarówno dla getInputStream, jak i getOutputStream) gniazda polega na tym, że jest on w funkcji uruchamiania ... rozwiązaniem wydaje się umieszczenie go w konstruktorze, zapisanie zmiennej, a następnie odwołanie do zmiennej w uruchomieniu. – Zimm3r

0

Obiekt OutputStream jest blokowany. Prawdopodobnie ma pewne buforowanie, ale to ci nie pomoże, jeśli serwer nigdy nie zużywa bajtów (jakikolwiek stały bufor ostatecznie się zapełni). Twój przyjaciel ma rację, musisz napisać osobny wątek lub użyć czegoś bardziej zaawansowanego, na przykład nio.

Po stronie czytania można użyć opcji available(), aby uniknąć blokowania. Brak powiązania nie istnieje po stronie zapisu. Chciałbym, żeby tak było.