Dans la programmation réseau java, existe-t-il un moyen de garder le côté serveur ouvert même lorsque le côté client s'arrête?


Supposons que nous ayons une paire client/serveur Echo simple en Java. Si je comprends bien, une fois qu'un côté de la prise se casse, toute la connexion a disparu.

Mais que faire si je veux un serveur qui peut toujours rester en vie, même si le côté client meurt. Je veux pouvoir reprendre une connexion rompue.

Serveur d'écho:

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();
        }
    }
}

Tous les conseils appréciés merci

Author: Coffee, 2015-07-21

4 answers

Ce qui vous manque, c'est le concept d'une "session". Pensez à la position d'un serveur, lorsque certains bits arrivent "sur le fil". Que faire avec ces? Avec TCP / IP, il y a des informations déjà présentes sur le fil, à savoir:

  • src addr
  • port src
  • dest addr
  • port dest
  • et la charge utile du message elle-même
  • et d'autres choses (comme les compteurs de séquence, pour s'assurer que les "paquets" ne sont pas brouillés pendant la transmission)

Le système d'exploitation du serveur utilise les informations src/dest addr/port pour décider s'il s'agit d'une "conversation déjà en cours" ou non. Principalement, il considère le dest port (puisque le message est déjà arrivé à la machine elle-même, via Internet et le pare-feu) pour décider si c'est pour votre programme Java d'écoute sur le 'port dest'. Cependant, il utilise l'ensemble src-addr / src-port / dest-addr / dest-port pour essayer de fournir la charge utile à votre programme dans l'ordre où l'expéditeur les a envoyés (ce qui pourrait ne pas être l'ordre dans lequel ils sont arrivés, en raison de l'inter-réseau intervenant). Notez qu'un seul "message" pourrait en fait être divisé en plusieurs paquets. La pile TCP/IP de votre système d'exploitation fait un peu de travail pour vous.

Cependant, notez que pour exécuter cette fonction en votre nom, le système d'exploitation doit consacrer une certaine quantité de ressources au "suivi de l'état" de la session TCP/IP. Au moins, pour chaque ensemble de port/addr src / dest, il doit y avoir un compteur du dernier "paquet" bien reçu, un espace tampon pour contenir les paquets jusqu'à ce que votre programme soit prêt à les consommer, et ainsi de suite.

Maintenant, la question posée à l'implémenteur de la pile TCP/IP est "combien de temps dois-je"m'accrocher" à cet état"? 10 secondes suffisent-elles? 20 minutes? À un moment donné, après ne pas avoir de nouvelles du client pendant un certain temps, la session doit 'timeout'. S'il y avait plus de paquets à envoyer dans une séquence, et le client recommence à les envoyer, le serveur doit pouvoir dire "désolé, vous m'envoyez le paquet 234 d'un message précédent, mais parce que je n'ai pas eu de vos nouvelles pendant un moment, j'ai jeté les paquets 1-233. Pourrions-nous commencer à nouveau?".

Donc, dans ce sens, il n'y a aucun moyen d'empêcher le client de "se déconnecter". Bien sûr, vous pouvez continuer à écouter sur le socket, au cas où le client récupère et envoie plus de données. Mais vous et votre client aurez besoin d'un moyen de 'reprendre là où nous sommes partis off'.

En HTTP, cela est réalisé à l'aide d'un 'cookie de session' - une longue chaîne unique que le serveur a donnée au client, et le client ré-envoie avec chaque nouvelle demande (que cela se produise sur la même session au niveau TCP ou non). Il s'agit d'une "session au niveau de l'application" qui a une durée de vie plus longue que celle de la session TCP.

Puisque vous écrivez une application, et il semble que vous ayez un certain degré de contrôle sur ce que le client et le serveur peuvent se dire (le 'protocole'), vous avez plus d'options sur la façon dont vous serez d'accord sur ce qu'est une session, comment gérer les choses si le client 's'en va' (timeout la session), et comment les deux parties vont récupérer et "reprendre là où nous nous étions arrêtés" (rétablir la session). N'oubliez pas l'authentification voulez-vous!

Comme d'autres l'ont dit, cela dépend beaucoup de ce que vous essayez de réaliser.

 11
Author: David Bullock, 2015-11-04 11:45:00

Lorsque vous souhaitez créer un serveur et plusieurs Clients, ce que nous faisons normalement est de créer un Thread responsable de la communication entre le serveur et l'un des clients, donc chaque fois qu'un client se connecte au serveur, le serveur démarre un nouveau Thread et lui donne le socket correspondant.

Le code devrait être quelque chose comme ceci:

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();
    }
}

Et la classe 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();
        }
    }
}
 5
Author: Lourenco, 2015-07-21 06:21:25

Comme quelqu'un l'a souligné, vous ne pouvez pas le faire avec TCP.

Voici un exemple simple d'un serveur d'écho 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();
        }
    }
}

Mise à jour: Ajout d'informations de test

Vous pouvez le tester avec nc

[gustaf@gustf-air]$ nc -u 127.0.0.1 4444
testing echo server
testing echo server
killing it now
killing it now
^C
[gustaf@gustf-air]$ nc -u 127.0.0.1 4444
now we are back
now we are back
 4
Author: gustf, 2016-02-27 20:32:32

Avec l'utilisation de l'api socket comme indiqué dans votre code, cela ne devrait pas être un problème. Pour une seule connexion si l'un des côtés est fermé (ou par exemple des matrices) l'association complète est "down" et ne peut plus être utilisée. C'est ce qui se passe avec clientSocket à partir de votre code.

Cependant, serverSocket est toujours ouvert et accepte de nouvelles connexions. À cet égard, votre serveur "reste toujours en vie".

Donc, votre code montré est un exemple simple d'utilisation de l'api socket, pour création d'un serveur qui attend les connexions incomming et distribue les appels individuels (incomming) en cours de traitement.

Le seul "inconvénient" de votre code est qu'en effectuant un traitement dans le même thread que accept (), votre serveur ne pourra pas répondre aux nouvelles demandes tant qu'il est le traitement de l'previos un. Pour éviter ce traitement pourrait être délégué à un nouveau thread (comme d'autres l'ont déjà souligné).

Vous pouvez trouver des explications supplémentaires avec la documentation sur accepter(). par exemple, vous pouvez lire Explication de l'API Socket

Veuillez reconnaître que ce n'est pas directement une question Java. Java ne fait qu'envlopes et fournit aux programmes Java les fonctionnalités du système d'exploitation sous-jacent en ce qui concerne la communication socket. C'est surtout qu'est-ce que documenté dans le cadre de la norme Posix, il s'agit en fait (en ce qui concerne les sockets) de répéter (et d'agréger) ce qui a été spécifié avec divers RFC (voir https://www.ietf.org/rfc.html pour les détails de ces spécifications).

Avec des sockets gardant une connexion ouverte (fonctionnant pleinement) après qu'un côté l'ait terminée, il n'est pas possible si le socket est défini sur utiliser le protocole TCP. Il y a - juste pour ajouter pour la complétude-un mécanisme (arrêt) pour déclarer que le côté d'envoi local est en panne tout en acceptant d'autres données pour l'entrée. (Mais je suppose que nous ne parlons pas de cette fonctionnalité très spéciale.) En gardant un connexion ouverte lorsque le côté connecté refuse de parler causerait des problèmes logiques et serait également un cauchemar pour la gestion des ressources. (Combien de temps le serveur conserverait-il les ressources?)

Le modèle standard pour utiliser TCP avec un démon (un processus qui fournit continuellement un service sur un port donné), est au moyen de accept () comme indiqué ci-dessus:

  • Le démon boucle sur accept().
  • un client appelle connect () au serveur
  • accepter() renvoie une nouvelle connexion (dédiée) pour communiquer avec le client
  • client et le serveur échangent des données
  • un côté (client ou serveur) met fin à la connexion.

La stratégie utilisée par le serveur pour exécuter plusieurs requêtes (le traitement synchrone l'un après l'autre, les files d'attente de demande, un thread par connexion est une décision de conception complètement distincte. Cela dépend du degré de parallélisme, des ressources disponibles, etc.

Le serveur aura deux "types" différents de prises en cours d'utilisation:

  1. le socket sur lequel il appelle accept() est celui sur lequel le serveur "écoute" les connexions entrantes.
    Cette prise est celle qui est ouverte en continu.
  2. un socket renvoyé par accept() est pour une connexion spécifique.
    Ceci est utilisé pour communiquer avec un client spécifique et va " mourir" à la fin de l'interaction.
 0
Author: rpy, 2016-03-12 18:58:34