2015-07-21 8 views
7

Załóżmy, że mamy prostą parę klient/serwer Echo w Javie. Jak rozumiem, gdy tylko jedna strona gniazda się zepsuje, całe połączenie zniknie.W programowaniu w sieci java, czy istnieje sposób na utrzymanie otwartej strony serwera nawet po wyłączeniu strony klienta?

Ale jeśli chcę serwer, który zawsze może pozostać przy życiu, nawet po śmierci klienta. Chcę móc wznowić przerwane połączenie.

Echo Server:

import java.net.Socket; 
import java.net.ServerSocket; 

public class EchoServer { 

    public static void main(String[] args) throws Exception { 

     // create socket 
     int port = 4444; 
     ServerSocket serverSocket = new ServerSocket(port); 
     System.err.println("Started server on port " + port); 

     // repeatedly wait for connections, and process 
     while (true) { 

      // a "blocking" call which waits until a connection is requested 
      Socket clientSocket = serverSocket.accept(); 
      System.err.println("Accepted connection from client"); 

      // open up IO streams 
      In in = new In (clientSocket); 
      Out out = new Out(clientSocket); 

      // waits for data and reads it in until connection dies 
      // readLine() blocks until the server receives a new line from client 
      String s; 
      while ((s = in.readLine()) != null) { 
       out.println(s); 
      } 

      // close IO streams, then socket 
      System.err.println("Closing connection with client"); 
      out.close(); 
      in.close(); 
      clientSocket.close(); 
     } 
    } 
} 

jakieś wskazówki docenione dzięki

+3

Przeczytaj o TCP i UDP. W przypadku TCP nie jest to możliwe, sam będziesz musiał sobie poradzić z ponownym połączeniem. Z UDP jest to możliwe, ale ma inne implikacje. –

+0

Dlaczego chcesz to zrobić? Ponowne połączenie przerwanego połączenia jest niemożliwe, ale może istnieć inny sposób robienia tego, co chcesz. – tbodt

+0

@tbodt - inny sposób - hmm, jak co?> – Coffee

Odpowiedz

5

Gdy chcesz stworzyć serwer i wielu klientów, co normalnie jest stworzenie wątek odpowiedzialny za komunikację między serwerem i jeden z klientów, więc za każdym razem, gdy klient łączy się z serwerem, serwer rozpocznie nowy wątek i da mu odpowiednie gniazdo.

Kod powinien być mniej więcej tak:

import java.net.ServerSocket; 
import java.net.Socket; 
import java.util.ArrayList; 

public class EchoServer { 

    public static void main(String[] args) throws Exception { 

     ArrayList<ClientHandler> clientHandlersList = new ArrayList<ClientHandler>(); 

     // create socket 
     int port = 4444; 
     boolean isOver = false; 
     ServerSocket serverSocket = new ServerSocket(port); 
     System.err.println("Started server on port " + port); 

     // repeatedly wait for connections, and process 
     while (!isOver) { 

      // a "blocking" call which waits until a connection is requested 
      Socket clientSocket = serverSocket.accept(); 
      System.err.println("Accepted connection from client"); 

      ClientHandler handler = new ClientHandler(clientSocket); 
      handler.start(); 
      clientHandlersList.add(handler); 
     } 

     serverSocket.close(); 
    } 
} 

a klasa ClientHandler:

import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.net.Socket; 


public class ClientHandler extends Thread { 

    private Socket socket; 
    private ObjectInputStream in; 
    private ObjectOutputStream out; 
    private boolean disconnect = false; 


    public ClientHandler(Socket socket) { 
     this.socket = socket; 
    } 

    @Override 
    public void run() { 
     try { 
      in = new ObjectInputStream(socket.getInputStream()); 
      out = new ObjectOutputStream(socket.getOutputStream()); 
      while(!disconnect){ 
       Object message = readMessage(); 

       //DO something 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public Object readMessage(){ 
     try { 
      Object obj = in.readObject(); 
      return obj; 

     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     return null; 
    } 

    public void writeMessage(Object obj){ 
     try { 
      out.writeObject(obj); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

    public void disconnect(){ 
     try { 
      disconnect = false; 
      out.close(); 
      in.close(); 
      socket.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 
} 
+2

To jest grzeczna i pomocna odpowiedź (+1). Jest to uczciwy przykład obsługi wielu żądań przychodzących do gniazda. Nie * bezpośrednio * odnosi się do pytania "jak odbieramy tam, gdzie zostało przerwane", ale daje dobre ramy do tego. NB. zamiast podklasy "Wątek", zaimplementuj 'Runnable' i wykonaj' (new Thread (myRunnable)). start() '. NB (2). projekt "wątek na żądanie" jest generalnie uważany za niezbyt dobrze "skalujący" (np.> 100 równoczesnych żądań) i istnieją inne projekty. (por. NodeJS dla opinii, vert.x dla Javy). NB3. HTTP przechodzi przez zapory ogniowe, ma "pliki cookie". –

+0

@ DavidBullock - Byłem ciekawy vert.x, dla Javy. Czy to coś, co można zrobić na jednym komputerze? Spojrzę na to teraz. – Coffee

11

Co tracisz jest pojęcie 'sesji'. Pomyśl o pozycji serwera, gdy niektóre bity pojawią się "na drucie". Co z tym zrobić? Z TCP/IP, istnieją pewne informacje już obecne na drucie, a mianowicie:

  • src addr
  • portu src
  • dest addr
  • portu docelowego
  • i sama ładunku wiadomość
  • i inne rzeczy (takie jak liczniki sekwencji, aby zapewnić, że "pakiety" nie są pomieszane podczas transmisji)

System operacyjny serwera używa informacji o src/dest addr/port, aby zdecydować, czy jest to "rozmowa, która jest już w toku", czy nie. Głównie bierze pod uwagę dest port (ponieważ wiadomość już dotarła do samego komputera, przez Internet i zaporę), aby zdecydować, czy jest to do słuchania twojego programu w Javie na "porcie docelowym". Jednak używa całego src-addr/src-port/dest-addr/dest-port, aby spróbować dostarczyć ładunek do twojego programu w kolejności, w której nadawca wysłał je (która może nie być taka, w jakiej przybyli z powodu interweniującego Internetu). Zauważ, że pojedyncza "wiadomość" może być faktycznie podzielona na wiele pakietów. Stos TCP/IP twojego systemu operacyjnego wykonuje za ciebie sporo pracy.

Należy jednak zauważyć, że aby wykonać tę funkcję w Twoim imieniu, system operacyjny musi poświęcić trochę zasobów na "śledzenie stanu" sesji TCP/IP. Przynajmniej dla każdego zestawu portów/addr src/dest potrzebny jest licznik ostatniego dobrze odebranego "pakietu", trochę przestrzeni bufora do przechowywania pakietów, dopóki program nie będzie gotowy do ich użycia, a więc naprzód.

Teraz pytanie, przed którym stoi implementator stosu TCP/IP, brzmi: "na jak długo powinienem" pozostać w tym stanie "? Czy wystarczy 10 sekund? 20 minut?W pewnym momencie, po nie słyszeniu od klienta przez chwilę, sesja musi "przekroczyć limit czasu". Jeśli było więcej pakietów do wysłania w sekwencji, a klient ponownie zaczął je wysyłać, serwer musi powiedzieć "przepraszam, przysyłasz mi pakiet 234 z poprzedniej wiadomości, ale ponieważ nie usłyszałem od przez chwilę wyrzucałem pakiety 1-233. Czy moglibyśmy zacząć od nowa? ".

W tym sensie nie ma sposobu, aby uniemożliwić klientowi "odłączenie". Oczywiście, możesz nadal słuchać na gnieździe, na wypadek, gdyby klient odzyskał i wysłał więcej danych. Ale ty i twój klient będziecie potrzebować sposobu, aby "podnieść miejsce, w którym skończyliśmy".

W protokole HTTP można to osiągnąć za pomocą "pliku cookie sesji" - długiego unikatowego ciągu, który serwer przekazał klientowi, a klient ponownie wysyła z każdym nowym żądaniem (niezależnie od tego, czy dzieje się to w ramach tej samej sesji na poziomie TCP, czy nie). Jest to "sesja na poziomie aplikacji", która trwa dłużej niż sesja TCP.

Od pisania aplikacji i brzmi to jak masz pewną kontrolę nad tym, co klient i serwer mają do powiedzenia sobie nawzajem ("protokół"), masz więcej opcji o tym, jak się zgodzisz na czym polega sesja, jak radzić sobie z rzeczami, gdy klient "odejdzie" (czas oczekiwania na sesję) i jak obie strony odzyskają i "podniosą tam, gdzie przerwaliśmy" (przywrócić sesję). Nie zapomnij o uwierzytelnieniu!

Jak powiedzieli inni, zależy to od tego, co próbujesz osiągnąć.

4

Jak ktoś wskazał, nie można tego zrobić z TCP.

Oto prosty przykład serwera echo DatagramSocket (UDP).

package test; 

import java.io.*; 
import java.net.*; 

public class UDPEchoServer { 

    public static void main(String[] args) throws IOException 
    { 
     new ServerThread().start(); 
    } 


    static class ServerThread extends Thread 
    { 

     protected DatagramSocket socket = null; 

     public ServerThread() throws IOException 
     { 
      this("ServerThread"); 
     } 

     public ServerThread(String name) throws IOException 
     { 
      super(name); 
      socket = new DatagramSocket(4444); 

     } 

     public void run() 
     { 
      try { 
       while (true) { 
        byte[] buf = new byte[256]; 

        // receive data 
        DatagramPacket packet = new DatagramPacket(buf, buf.length); 
        socket.receive(packet); 

        // send back the response to the client at "address" and "port" 
        InetAddress address = packet.getAddress(); 
        int port = packet.getPort(); 
        packet = new DatagramPacket(buf, buf.length, address, port); 
        socket.send(packet); 
       } 
      } 
      catch (IOException e) { 
       e.printStackTrace(); 
      } 
      socket.close(); 
     } 
    } 
} 

Aktualizacja: Dodany informacji Test

Można przetestować go z nc

[[email protected]]$ nc -u 127.0.0.1 4444 
testing echo server 
testing echo server 
killing it now 
killing it now 
^C 
[[email protected]]$ nc -u 127.0.0.1 4444 
now we are back 
now we are back 
+1

OK, wygląda to całkiem zgrabnie. btw, używam systemu Windows. Wydaje mi się, że kod jest tworzony na systemach Linux/Mac? '[gustaf @ gustf-air] $ nc -u 127.0.0.1 4444' Kiedy próbowałem uruchomić' java UDPEchoServer 4444', wystąpił błąd 'Wyjątek w wątku" main "java.net.BindException: Adres już w użyciu: Can not bind na java.net.DualStackPlainDatagramSocketImpl.socketBind (Metoda natywna) ', dzięki! – Coffee

+2

Witaj, 'BindException' może być, ponieważ masz już uruchomiony" stary "program. Porty nie mogą być udostępniane. Po prostu zmień '4444' na coś, co nie jest w użyciu. Tak, masz rację, używam mac. Prawdopodobnie istnieje podobne polecenie dla Windows, ale nie wiem które. Ale prostą rzeczą byłoby po prostu napisanie klienta. Oto dobry samouczek od Oracle. https://docs.oracle.com/javase/tutorial/networking/datagrams/clientServer.html – gustf

0

Z pomocą gniazda API, jak pokazano w kodzie, to nie powinno być problemem. Dla pojedynczego połączenia, jeśli któraś strona jest zamykana (lub np. Umiera) , kompletne powiązanie jest "wyłączone" i nie można go już używać. To właśnie dzieje się z clientSocket z Twojego kodu.

Jednak serverSocket wciąż jest otwarty i akceptuje nowe połączenia. W związku z tym twój serwer zawsze "pozostaje żywy".

Tak, twój pokazany kod jest zwykłym przykładem wanilii użycia socket api, do tworzenia serwera, który czeka na przychodzące połączenia i wywołuje indywidualne (przychodzące) połączenia do przetworzenia.

Jedynym "minusem" twojego kodu jest to, że przetwarzając w tym samym wątku, co accept(), twój serwer nie będzie mógł odpowiadać na nowe żądania , dopóki przetwarza poprzedni. Aby tego uniknąć, można przekazać do nowego wątku (jak już wspomniano).

Możesz znaleźć dodatkowe wyjaśnienia w dokumentacji na temat accept(). np. możesz przeczytać Socket API explanation

Proszę zrozumieć, to nie jest bezpośrednio pytanie Java.Java po prostu envlopes i zapewnia programom Java funkcjonalność systemu operacyjnego w odniesieniu do komunikacji gniazda. To jest głównie to, co jest udokumentowane jako część standardu Posix, w rzeczywistości jest to (w odniesieniu do gniazd) tylko powtarzanie (i agregowanie) tego, co zostało określone w różnych RFC (patrz https://www.ietf.org/rfc.html w celu uzyskania szczegółowych informacji na temat tej specyfikacji).

Z gniazdkami utrzymującymi połączenie otwarte (działa w pełnym zakresie) po zakończeniu jednej strony, nie jest możliwe, jeśli gniazdo jest ustawione na korzystanie z protokołu TCP. Istnieje - po prostu dla uzupełnienia - mechanizm (zamknięcie) do deklarowania lokalnej strony wysyłającej, która nie działa, a jednocześnie akceptuje dalsze dane do wprowadzenia. (Ale zakładam, że nie mówimy o tej wyjątkowej funkcji). Pozostawienie otwartego połączenia, gdy połączona strona odmawia rozmowy, spowodowałoby problemy logiczne, a także byłoby koszmarem dla zarządzania zasobami. (Jak długo serwer będzie przechowywać zasoby?)

Standardowy wzorzec używania TCP z demonem (proces, który nieprzerwanie świadczy usługę na danym porcie), odbywa się za pomocą metody accept(), jak wskazano powyżej :

  • Demon zapętla się w accept().
  • klient dzwoni connect() na serwerze
  • accept() zwraca nowy (dedykowane) Podłączenie do komunikacji z klientem
  • klient i serwer wymiany danych
  • jednej strony (klient lub serwer) kończy połączenie.

Strategia serwer używa do wykonania kilka wniosków (synchroniczne przetwarzanie jeden po drugim, kolejki żądań, jeden wątek na connnection jest kompletnie oddzielna decyzja projektowa. Zależy to od stopnia równoległości, dostępnych zasobów itp

serwer będzie miał dwa różne „rodzaje” gniazd w użyciu.

  1. gniazdo jest wywołanie accept() na jedną serwer jest „słuchanie” dla połączeń przychodzących
    Ten soc ket to ta, która jest ciągle otwarta.
  2. Gniazdo zwróciło z accept() jest dla określonego połączenia.
    Służy do komunikacji z określonym klientem i "umrze" pod koniec interakcji .
Powiązane problemy