2010-01-08 22 views
13

Jaki jest najlepszy sposób wykrywania, czy gniazdo zostało upuszczone, czy nie? Albo czy pakiet rzeczywiście został wysłany?Gniazda Java i odłączone połączenia

Mam bibliotekę do wysyłania powiadomień Apple Push na iPhone przez Apple Gatways (available on GitHub). Klienci muszą otworzyć gniazdo i wysłać binarną reprezentację każdej wiadomości; ale niestety Apple nie zwraca żadnego potwierdzenia. Połączenie może być ponownie użyte do wysyłania wielu wiadomości. Używam prostych połączeń Java Socket. Odpowiedni kod to:

Socket socket = socket(); // returns an reused open socket, or a new one 
socket.getOutputStream().write(m.marshall()); 
socket.getOutputStream().flush(); 
logger.debug("Message \"{}\" sent", m); 

W niektórych przypadkach, gdy połączenie zostanie zerwane podczas wysyłania wiadomości lub wcześniej; Socket.getOutputStream().write() kończy się pomyślnie. Spodziewam się, że jest to spowodowane tym, że okno TCP nie jest jeszcze wyczerpane.

Czy jest jakiś sposób, aby stwierdzić na pewno, czy pakiet rzeczywiście trafił do sieci, czy nie? I eksperymentowali z dwóch następujących rozwiązań:

  1. umieścić dodatkowy socket.getInputStream().read() operację na 250 ms czas oczekiwania. Wymusza to operację odczytu, która kończy się niepowodzeniem, gdy połączenie zostało przerwane, ale zawiesza się w inny sposób na 250ms.

  2. ustawić rozmiar bufora wysyłania TCP (na przykład Socket.setSendBufferSize()) na rozmiar binarny komunikatu.

Obie metody działają, ale znacznie obniżają jakość usługi; przepustowość dochodzi ze 100 komunikatów na sekundę do około 10 wiadomości na sekundę.

Wszelkie sugestie?

UPDATE:

kwestionowane przez wielu odpowiedziach kwestionujących możliwość opisany. Skonstruowałem testy "jednostkowe" zachowania, które opisuję. Sprawdź skrzynki jednostkowe pod numerem Gist 273786.

Oba testy jednostkowe mają dwa wątki, serwer i klienta. Serwer jest zamykany, gdy klient wysyła mimo to dane bez wywołania IOException. Oto główne metody:

public static void main(String[] args) throws Throwable { 
    final int PORT = 8005; 
    final int FIRST_BUF_SIZE = 5; 

    final Throwable[] errors = new Throwable[1]; 
    final Semaphore serverClosing = new Semaphore(0); 
    final Semaphore messageFlushed = new Semaphore(0); 

    class ServerThread extends Thread { 
     public void run() { 
      try { 
       ServerSocket ssocket = new ServerSocket(PORT); 
       Socket socket = ssocket.accept(); 
       InputStream s = socket.getInputStream(); 
       s.read(new byte[FIRST_BUF_SIZE]); 

       messageFlushed.acquire(); 

       socket.close(); 
       ssocket.close(); 
       System.out.println("Closed socket"); 

       serverClosing.release(); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    class ClientThread extends Thread { 
     public void run() { 
      try { 
       Socket socket = new Socket("localhost", PORT); 
       OutputStream st = socket.getOutputStream(); 
       st.write(new byte[FIRST_BUF_SIZE]); 
       st.flush(); 

       messageFlushed.release(); 
       serverClosing.acquire(1); 

       System.out.println("writing new packets"); 

       // sending more packets while server already 
       // closed connection 
       st.write(32); 
       st.flush(); 
       st.close(); 

       System.out.println("Sent"); 
      } catch (Throwable e) { 
       errors[0] = e; 
      } 
     } 
    } 

    Thread thread1 = new ServerThread(); 
    Thread thread2 = new ClientThread(); 

    thread1.start(); 
    thread2.start(); 

    thread1.join(); 
    thread2.join(); 

    if (errors[0] != null) 
     throw errors[0]; 
    System.out.println("Run without any errors"); 
} 

[Nawiasem mówiąc, ja też mam bibliotekę testów współbieżności, który sprawia, że ​​konfiguracja nieco lepiej i wyraźniej. Sprawdź również próbkę w istocie].

Po uruchomieniu pojawia się następujący komunikat:

+0

Zaktualizowana odpowiedź. –

+0

Jak już stwierdziłem w mojej odpowiedzi, jeśli chcesz mieć pewność, że nie wystąpił błąd, musisz wykonać pełne wdzięku zakończenie połączenia. Wykonaj shutdownOutput(), a następnie przeczytaj, aż otrzymasz EOF, a następnie zamknij gniazdo. Łagodne zakończenie połączenia jest w istocie otrzymaniem potwierdzenia od rówieśnika, że ​​otrzymał on OK, co jest dokładnie tym, czego chcesz. –

Odpowiedz

17

nie być zbyt pomocne dla ciebie, ale technicznie zarówno swoich proponowanych rozwiązań są nieprawidłowe. OutputStream.flush() i jakiekolwiek inne wywołania API, o których myślisz, nie będą robić tego, czego potrzebujesz.

Jedynym przenośnym i niezawodnym sposobem określenia, czy pakiet został odebrany przez peera, jest oczekiwanie na potwierdzenie od peera. To potwierdzenie może być rzeczywistą reakcją lub płynnym zamknięciem gniazda. Koniec historii - tak naprawdę nie ma innej drogi, a to nie jest specyficzne dla Javy - jest to podstawowe programowanie sieciowe.

Jeśli to nie jest trwałe połączenie - to znaczy, jeśli po prostu wyślesz coś, a następnie zamkniesz połączenie - tak jak to robisz, łapiecie wszystkie IOExceptions (każdy z nich wskazuje na błąd) i wykonujemy wdzięczne gniazdo zamknięcie:

1. socket.shutdownOutput(); 
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket 
+1

Całkowicie poprawne. Jeśli warstwa aplikacji nie daje żadnych potwierdzeń, protokół jest po prostu niewiarygodny, i będziesz musiał się z tym pogodzić (tylko świadomość, że pakiet wyszedł na przewód, nie mówi ci, że faktycznie w końcu dostałem się na drugi koniec). – caf

2

Jeśli wysyłasz informacje za pomocą protokołu TCP/IP do Apple, musisz otrzymywać potwierdzenia. Jednak pan stwierdził:

Apple nie zwraca żadnych potwierdzenie jakiejkolwiek

Co pan przez to rozumie? TCP/IP gwarantuje dostawę, dlatego odbiornik MUSI potwierdzić odbiór. Nie gwarantuje jednak, że dostawa zostanie zrealizowana.

Jeśli wyślesz powiadomienie do Apple i przerwiesz połączenie przed odebraniem ACK, nie ma możliwości sprawdzenia, czy Ci się udało, a więc po prostu musisz wysłać je ponownie. Jeśli dwukrotne naciśnięcie tych samych informacji jest problemem lub nie jest obsługiwane prawidłowo przez urządzenie, oznacza to, że wystąpił problem. Rozwiązaniem jest naprawienie sposobu obsługi duplikatu powiadomienia push: nic nie można zrobić po stronie pchającej.

@comment Wyjaśnienie/Pytanie

Ok. Pierwszą częścią tego, co rozumiesz, jest twoja odpowiedź na drugą część. Tylko pakiety, które otrzymały ACKS zostały poprawnie wysłane i odebrane. Jestem pewien, że moglibyśmy wymyślić jakiś bardzo skomplikowany schemat śledzenia każdego pojedynczego pakietu, ale TCP powinien usunąć tę warstwę i obsłużyć ją dla Ciebie. Na końcu musisz po prostu poradzić sobie z mnóstwem niepowodzeń, które mogą wystąpić (w Javie, jeśli wystąpią którekolwiek z nich, powstaje wyjątek). Jeśli nie ma wyjątków, dane, które próbujesz wysłać, są wysyłane z gwarancją protokołu TCP/IP.

Czy istnieje sytuacja, w której dane pozornie są "wysyłane", ale nie gwarantowane, że zostaną odebrane w przypadku braku wyjątków? Odpowiedź brzmi: nie.

@Examples

Nicea przykłady, to wyjaśnia rzeczy całkiem sporo. Myślałem, że zostanie rzucony błąd. W zamieszczonym przykładzie błąd jest zgłaszany przy drugim zapisie, ale nie pierwszym. To interesujące zachowanie ... i nie byłem w stanie znaleźć wielu informacji wyjaśniających, dlaczego tak się zachowuje. Wyjaśnia to jednak, dlaczego musimy opracować własne protokoły na poziomie aplikacji, aby zweryfikować dostawę.

Wygląda na to, że masz rację, że bez protokołu potwierdzenia ich nie ma gwarancji, że urządzenie Apple otrzyma powiadomienie. Apple także ostatnia wiadomość tylko w kolejce. Przyglądając się nieco usługom, które udało mi się ustalić, ta usługa jest dla wygody bardziej wygodna dla klienta, ale nie może być używana do zagwarantowania usługi i musi być połączona z innymi metodami. Przeczytałem to z następującego źródła.

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

Wydaje się, że odpowiedź nie jest włączony, czy nie można powiedzieć na pewno. Możesz użyć sniffera pakietów, takiego jak Wireshark, aby powiedzieć, czy został wysłany, ale to nadal nie gwarantuje, że zostało odebrane i wysłane do urządzenia ze względu na charakter usługi.

+0

Mam na myśli to, że Apple nie zwraca żadnego potwierdzenia protokołu poza TCP ACK. Nie wiem też, w jaki sposób mogę stwierdzić, które pakiety zostały wysłane poprawnie, a które nie. – notnoop

+0

Dziękujemy za aktualizację. Zdaję sobie sprawę, że powinienem był skupić się na TCP na przykładach, zamiast omawiać usługi Apple. Myślę, że na razie zrezygnuję z pomocy polegającej na wykryciu połączenia. – notnoop

3

Po wielu kłopotów z zerwanych połączeń, przeniosłem mojego kodu use the enhanced format, który dość dużo znaczy zmienić swój pakiet wyglądać następująco:

enter image description here

ten sposób Apple nie spadnie połączenia jeśli Wystąpił błąd, ale zostanie zapisany kod zwrotny do gniazda.

Powiązane problemy