Komunikacja klient-serwer. Wykrywanie rozłączeń.

0

Piszę aktualnie projekt systemu wykorzystującego komunikację klient-serwer, gdzie aplikacja serwera jest napisana w Javie i uruchamiana na PC, a klientami mogą być urządzenia z Androidem.
Komunikacja ma odbywać się w dwie strony, więc zrobiłem odbieranie wiadomości na jednym wątku i wysyłanie wiadomości na drugim (jest to strona kliencka, serwer obsługuje wielu takich klientów). Odbieranie jest blokujące, więc w każdej sytuacji, w której jedna ze stron zamknie gniazdo sieciowe mogę to łatwo wykryć - readLine() zwraca mi wtedy null. Do takich sytuacji można zaliczyć np. wyłączenie apki na Androidzie - gniazdo się zamyka, serwer dostaje null i również zamyka gniazdo, na którym się komunikował z tym klientem. Oczywiście nic nie zamyka się samo - to już kwestia mojej obsługi wyjątków.

Nurtuje mnie jednak kwestia wykrywania rozłączeń pomiędzy klientem i serwerem w następujących sytuacjach:

  1. Utrata połączenia ze strony serwera poprzez np. manualne rozłączenie z siecią (karta sieciowa -> połącz/rozłącz)
  • zero wyjątków,
  • serwer nie wie, że nie ma sieci
  • klient nie wie, że w sieci nie ma serwera,
  1. Utrata połączenia ze strony serwera w przypadku awarii karty sieciowej lub jej wyłączenia
  • serwer zamyka gniazdo do komunikacji z klientem i nie wyrzuca innych wyjątków, przez co działa sobie bez połączenia jakby nigdy nic, nie zamykając swojego gniazda serwerowego
  • klient oczywiście znów o niczym nie wie,
  1. Utrata połączenia ze strony klienta poprzez np. wyłączenie wi-fi
  • klient zamyka swoje gniazdo
  • serwer o niczym nie wie, więc nie zamyka gniazda do komunikacji z klientem.

Jak rozwiązać te trzy przypadki, aby to wszystko miało swoją obsługę?

0

jedyne pewne rozwiązanie to ping-pong - wysyłanie do klienta co x czasu prostego komunikatu i czekanie na odpowiedź (np. wysłanie ping i oczekiwanie na pong). Jeśli błąd połączenia lub nie odpowie to uznajemy, że klient się rozłączył i ubijamy jego wątek czy co tam masz. W drugą stronę możesz zrobić tak samo. Dodatkowo zamiast co x czasu możesz sprawdzać co x czasu od ostatniej komunikacji.

0

Rozważałem takie rozwiązanie, ale nie wiem jak długo powinienem czekać na odpowiedź serwera (czy też klienta) w takim przypadku. W zależności od rosnącej liczby klientów serwer może zacząć mulić i odpowiadać w czasie dłuższym niż przewidziany nawet pomimo tego, że jeszcze jest w sieci - klienci będą się wtedy rozłączać myśląc, że serwer padł. Chciałbym, żeby taki klient jak najszybciej dostał informację o tym, że serwera nie ma i mógł wyświetlić jakiś komunikat, ale tylko wtedy, kiedy serwera faktycznie nie będzie w tej sieci - tylko, że takie coś chyba nie jest możliwe do osiągnięcia. Najwyżej będę zmuszony do robienia ping-ponga tuż przed wysłaniem czegokolwiek do serwera (i odwrotnie) - tylko czy to dobry pomysł i nie można inaczej?

0

ale po co wysyłać ping przed wysłaniem czegoś innego? Jak wyślesz coś innego to też stwierdzisz, że serwera nie ma (dostaniesz wyjątek). ping-ponga wysyła się tylko w czasie "nicnierobienia" przez obie strony aby podtrzymać lub sprawdzić stan połączenia

0

Do wysyłania komunikatów używam PrintWritera - w podanym przeze mnie przypadku nr 1, gdy serwer znika z sieci sprawdzenie błędów w wysyłaniu za pomocą metody checkError() nie podaje mi żadnych błędów i komunikat się wysyła. Gdy serwer uzyska połączenie komunikat do niego trafia i wtedy komunikacja odbywa się poprawnie, zupełnie, jakby nic się nie stało. Kwestia tylko taka, że serwer może do sieci nie wrócić i klient jest wtedy zawieszony bo nie dostaje żadnej odpowiedzi po wysłaniu własnego komunikatu.

0

Dzięki - zgłębię temat i sprawdzę czy w moim przypadku zadziała.

EDIT: Czy istnieje możliwość zmiany czasu oczekiwania 2 godzin na zamknięcie połączenia dla takiego rozwiązania? Jeśli nie - no to rozwiązanie niestety nie dla mnie. Klient by sobie posiedział 2 godziny zanim by mu się wyświetlił komunikat.

0

W kliencie można przecież ustawić i czas oczekiwania na połączenie jak i czas oczekiwania na odpowiedź (już po połączeniu z serwerem). Jak go przekroczysz to rzuci wyjątkiem.
https://docs.oracle.com/javase/7/docs/api/java/net/Socket.html#connect(java.net.SocketAddress,%20int)
https://docs.oracle.com/javase/7/docs/api/java/net/Socket.html#setSoTimeout(int)

0

Przy samym łączeniu robię to właśnie w taki sposób jaki wyżej podałeś. Jednak w wymaganiach systemu mam uwzględnioną sytuację, gdy klient musi czekać na odpowiedź serwera tak długo dopóki ona nie nadejdzie - ta sytuacja wiąże mi ręce bo timeout na gnieździe musi być wtedy ustawiony na 0 (nieskończone oczekiwanie). System służy do przeprowadzania ankiety bez użycia z góry wiadomej listy pytań - klient czeka na pytanie (bez timeoutu) i gdy je dostanie, to wtedy może na nie odpowiedzieć, a następnie znów czeka na kolejne (pytania wprowadza osoba zarządzająca aplikacją serwera).

0

panie takie rzeczy to tylko w erze. Albo masz timeout aby stwierdzić, że coś jest nie tak albo nie masz i czekasz.

0

No to panie - co teraz z tym zrobić? Wymagania są takie jakie są - system ma mieć dwa tryby: jeden ze znaną listą pytań, drugi z pytaniami wprowadzanymi na bieżąco. Da się na to coś poradzić?

EDIT: Jeśli olanie wymienionych sytuacji wyjątkowych byłoby dopuszczalnym zachowaniem, to bym się nad tym nie zastanawiał. Tylko czy jest dopuszczalne jeśli to system tworzony w celach pracy dyplomowej? Przypuszczam, że nie bardzo :P

0

ja widzę tylko jedno rozwiązanie - dodatkowe połączenie tylko do ping-ponga. Ale co z tego jak wykryjesz rozłączenie na osobnym gnieździe jeśli "główne" gniazdo wisi na nieskończonym czekaniu?

0

Zamknę oba gniazda i wywalę komunikat o błędzie? Właśnie o to mi chodzi, żebym wiedział kiedy zamknąć to główne gniazdo - aktualnie nie mogę bo mam tylko jedno, które tak jak powiedziałeś wisi na nieskończonym czekaniu.

0

no ale jak czeka na odpowiedź to jak je zamkniesz? Ubijesz wątek na chama?

0

Blokującego czytania raczej nie da się przerwać inaczej niż poprzez zamknięcie mu gniazda (a u mnie takie czytanie wykonywane jest na dodatek w pętli). Sprowadza się to do tego, że zawsze, gdy chcę przerwać takie czytanie ubijam je na chama zamykając gniazdo, na którym ono czyta. Jest inne rozwiązanie?

0

No to ten klient ma w końcu czekać w nieskończoność czy nie? : P Bo raz napisałeś, że jak serwer padł to ma zerwać połączenie a później, że ma czekać aż serwer wróci i go nie zrywać. A może w drugą stronę spróbuj, niech serwer sam się zorientuję czy jest pod dużym obciążeniem i jeśli tak to niech poinformuje klient, że żyje ale zamula. Ale o ile nie zrobisz tego odpowiednio wcześnie to ta informacja też może nie dolecieć, no i jak serwer padnie to nic już nie wyśle. Generalnie nie bardzo rozumiem te wymagania, jakby sprzeczność sama w sobie.

EDIT: nie wiem czy to w ogóle znajdzie zastosowanie w Twojej sytuacji ale może podpatrz jak to się odbywa w JMS, tutaj takie info ze stack'a:

JMS also adds robustness. For instance, you can configure it so that if the server dies while the client sends messages or the other way around, you can still send messages from the client or poll messages from the server. If you ever tried implementing this directly with sockets - it's a nightmare.

0

Może napisz dokładnie, use-casem co chcesz osiągnąć.

  • Mam A zrywa połączenie oczekuje B.
0

Wymaganie jest proste: jak serwer zniknie z sieci, to klienci mają się o tym dowiedzieć jak najszybciej i się z nim rozłączyć, a właściwie, to zamknąć swoje gniazda sieciowe (serwer również mógłby pozamykać wszystkie połączenia z klientami po swojej stronie, gdy nie ma sieci) - później jak klienci będą chcieli, to mogą próbować się podłączyć od nowa (oczywiście uda im się tylko wtedy, gdy serwer wróci do sieci, więc nie ma problemu). Od strony serwera ma to również podobnie działać - jeśli klient traci sieć, to serwer ma zamknąć swoje gniazdo do komunikacji z tym klientem.

Problem jest w tym, że w wymienionych przeze mnie sytuacjach o rozłączeniu wie tylko jedna strona lub żadna o tym nie wie (a muszą jakoś wiedzieć obie). Rozwiązanie podane przez @abrakadaber z kolejnym gniazdem byłoby dobre - tylko, że ten sposób będzie powielał gniazda sieciowe (dwa na klienta zamiast jednego) oraz wymagałby kolejnych dwóch wątków do czytania i odbierania (czyli podwójna ilość tego co do tej pory było dla każdego z klientów).

@pedegie
Może trochę pogmatwałem, ale nie chodziło mi o to, że klient ma czekać na powrót serwera. Jak serwer znika, to ma się z nim rozłączyć. Czekać ma tylko wtedy, gdy serwer jest w sieci i trwa oczekiwanie na wprowadzenie pytania po stronie serwera przez kogoś, kto tym serwerem zarządza.

0

ale rozumiesz, że nie zrobisz tego na jednym gnieździe, które ma timeout = 0

0

Rozumiem - i raczej tego rozwiązania będę się trzymał, jeśli nie natknę się na coś innego, co mogłoby rozwiązać problem w łatwiejszy sposób (o ile będzie taki istniał). Muszę również przedyskutować wymagania ze swoim promotorem - żeby nie wyszło, że zrobiłem coś niepotrzebnie i żeby czasem wydajność takiego rozwiązania nie była zbyt niska. Powinienem obsługiwać do 100 klientów w jednym czasie przy czym wysyłane komunikaty mają być szyfrowane - obecnie mam już pewne kłopoty natury wydajnościowej, które szczerze mówiąc nie wiem z czego wyszły. Czasem nawet pojedynczy klient potrzebuje kilkunastu albo kilkudziesięciu sekund na połączenie z serwerem. Pewnie duża część tego czasu idzie na wymianę klucza do szyfrowania symetrycznego.

Dla wszystkich, którym pogmatwałem:

K - Klient
S - Serwer

Utrata połączenia od strony klienta (K)

  1. Ustanowiono połączenie K z S
  2. K traci połączenie (znika z sieci)
  3. K zamyka swoje gniazdo sieciowe (Socket)
  4. K wyświetla komunikat dla użytkownika o błędzie w połączeniu
  5. S dowiaduje się o tym, że K nie ma w sieci
  6. S zamyka gniazdo do komunikacji z K (Socket)

K - Klienty
S - Serwer

Utrata połączenia od strony serwera (S)

  1. Ustanowiono połączenie S z K
  2. S traci połączenie (znika z sieci)
  3. S zamyka wszystkie gniazda sieciowe do komunikacji z K (Socket)
  4. S zamyka własne gniazdo serwerowe (ServerSocket)
  5. S kończy wątek serwera
  6. S wyświetla komunikat o utracie połączenia
  7. K dowiadują się o tym, że S nie ma w sieci
  8. Wszystkie K zamykają swoje gniazda sieciowe (Socket)
0

użyj Netty problem zerwanych połączeń (bo widzę że to problem dla Ciebie) rozwiąże za Ciebie, masz NIO, dobry remory model, ping serwera jest do zaimplementowania jest wręcz trywialny .

1 użytkowników online, w tym zalogowanych: 0, gości: 1