IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Entrée/Sortie Java Discussion :

Créer un protocole de communication, TCP et UDP


Sujet :

Entrée/Sortie Java

  1. #1
    Invité
    Invité(e)
    Par défaut Créer un protocole de communication, TCP et UDP
    Je suis en train de créer une application "réseau" composée de 3 parties : client, serveur, protocole.
    Ces 3 parties sont aussi 3 projet différents sur Eclipse.
    Les projets client et serveur utilisent le projet protocole.
    (C'est une application de type contrôle à distance, mais ce n'est pas important ici).

    Pour le moment, la partie protocole utilise UDP pour plusieurs raisons :
    - latence plus faible (il parait)
    - découpage en "paquet" plus facile qu'en TCP (mais j'espère me tromper)

    Mais j'ai plusieurs problèmes avec UDP, et je vais peut être finalement utiliser TCP :
    - j'ai des pertes de paquets un peu gênantes
    - un bug sur ma plateforme "client" (Android), m'empeche de recevoir en UDP.

    Tout mon protocole est basé sur des données "binaires". Pas de texte, pas de xml. Je manipule les données grâce à la classe ByteBuffer.

    J'ai donc des questions en ce qui concerne cette migration UDP vers TCP

    Jusqu'à présent, pour une action "bouger la souris de tant de pixels", j'envoyais un paquet UDP.
    Comment reproduire la même notion de "paquet" dans TCP? Je crois que TCP fonctionne sur un système de "flux" continu.
    J'ai pensé à inclure avant chaque "action" envoyée, la taille en octets de cette action.

    Comment "forcer" l'envoi de "données en cache" dans TCP (pour éviter d'attendre qu'un buffer soit rempli avant l'envoie des données)? La latence est primordiale dans mon application :p


    Merciiii

  2. #2
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par PierreD87 Voir le message
    - latence plus faible (il parait)
    Non: la latence est la même avec TCP et UDP en fonctionnement 'normal'. La seule différence se situe quand il y a eu pertes de paquets, par exemple au paquet N : les paquets N+x (même s'ils sont reçus) ne seront pas délivrés à ton application tant que N n'a pas été redemandé et re-reçu.

    Ceci est dû au fait que TCP garantit également le bon ordre des paquets reçus (et ce n'est pas désactivable).

    - j'ai des pertes de paquets un peu gênantes
    Ben là il y a plus à hésiter : TCP ! Parce que si tu dois toi-même implémenter une surcouche à UDP pour gérer la perte / ré-émission de paquets, ça va vite devenir très complexe.

    Comment reproduire la même notion de "paquet" dans TCP? Je crois que TCP fonctionne sur un système de "flux" continu. J'ai pensé à inclure avant chaque "action" envoyée, la taille en octets de cette action.
    Exactement. Attention à bien coder la taille sur un nombre d'octets suffisant pour test besoins (exemple: 1 octet te limite à des tailles maxi de 256 octets ; 2 octets te limite à des paquets de 2^16 octets (65Ko), etc...).

    Comment "forcer" l'envoi de "données en cache" dans TCP (pour éviter d'attendre qu'un buffer soit rempli avant l'envoie des données)?
    Il faut passer par la désactivation de l'algorithme de Nagle. En Java, il faut appeler setTcpNoDelay(true) sur tes instance de Socket.

    Une fois ceci fait, il te faut faire très attention à n'effectuer qu'un seul appel à write() pour chaque paquet que tu veux émettre (y compris pour le bout d'octets représentant la taille du paquet). En effet, chaque appel à write() générera un paquet distinct.


    A noter que la même possibilité est offerte dans à peu près tous les langages ayant le support des sockets TCP.

  3. #3
    Invité
    Invité(e)
    Par défaut
    Ok, merci beaucoup pour ces conseils !

    Le problème, avec le fait d'inclure la taille du "paquet virtuel" avant, c'est qu'il faut avoir 100% confiance dans l'application qu'on a en face :/
    Et aussi, le moindre petit bug de "décalage" dans le flux peut devenir vite catastrophique

  4. #4
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Le problème, avec le fait d'inclure la taille du "paquet virtuel" avant, c'est qu'il faut avoir 100% confiance dans l'application qu'on a en face :/
    Pas spécialement: si tu détectes côté serveur une taille de paquet farfelue (genre 100Mo alors que tu sais que tes paquets ne dépassent jamais quelques centaines d'octets), rien ne t'empêche de déconnecter immédiatement le client (plus ban d'IP et pendaison sur la place publique ).
    De plus, taille de paquet ou pas, TCP ou pas, il te faudra de toute façon vérifier systématiquement l'ensemble des données (présence & contenu) que le client envoie ; ce n'est finalement qu'un (tout petit) check supplémentaire.

    A la limite, si tu veux renforcer tes vérifications, une autre solution est d'inclure -avant même la taille du paquet- le type de message envoyé sur un nombre d'octets fixe; ainsi tu peux en plus vérifier que:

    - le type de paquet fait partie des paquets susceptibles d'être envoyés à ce moment là par le client.

    - la taille du paquet est cohérente avec le type de paquet.

    Et aussi, le moindre petit bug de "décalage" dans le flux peut devenir vite catastrophique
    Oui, mais:

    - TCP te garantit qu'il n'y aura aucune altération du contenu entre ce qui est émis et ce qui est reçu. Donc de ce côté, t'es peinard.

    - une fois cette partie codée (elle est commune au client et au serveur et pas bien complexe au final) et bien testée, tu n'as plus à y toucher et ça roulera sans souci.

    - bien entendu, il faut bien veiller à ce que cette gestion des paquets envoyés est encapsulée dans ton API: aucune autre partie du programme ne doit pouvoir fourrer son nez dedans hormis la classe 'bas niveau' qui s'occupe de gérer concrètement la socket. A noter que ceci pourrait te donner quelques idées pour une approche 'par couches'.

  5. #5
    Invité
    Invité(e)
    Par défaut
    Oui j'avais pensé à la déconnexion brutale en cas de taille farfelue ^^
    J'ai aussi bien sûr le type de message envoyé !

    Pour le bug de "décalage", je pensais plutôt à une erreur de ma part :p

    Et évidemment, j'ai déjà complètement encapsulé ma partie "bas niveau"

  6. #6
    Invité
    Invité(e)
    Par défaut
    autre question bête,
    c'est quoi les receive/sendBufferSize des Socket ET DatagramSocket ?
    quand j'utilise UDP, j'ai remarqué que certains paquets trop gros n'étaient tout simplement pas transmis ! plus de 8 ko je crois.
    C'est bien 65000 octets la limite théorique d'un paquet UDP ? (approximativement)

  7. #7
    Invité
    Invité(e)
    Par défaut
    Pour ceux que ça intéresse, j'ai trouvé les classes DataInput/OutputStream.
    Je pense que ça va me servir ^^

  8. #8
    Invité
    Invité(e)
    Par défaut
    J'ai activé le tcpNoDelay sur mon socket.
    Il se trouve que j'envoie (avec write sur le OutputStream) un tableau de bytes de 17000 cases. C'est trop ?

    Car mon programme à tendance à planter à ce moment là ...

  9. #9
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par PierreD87 Voir le message
    J'ai activé le tcpNoDelay sur mon socket.
    Oui, mais j'avoue ne jamais avoir testé ensemble le NODELAY avec les DataOutputStream. Je ne sais pas comment s'organise cette classe en interne pour les appels au write, donc il ne serait pas inutile amha de faire quelques tests pratiques pour s'assurer qu'un appel à une fonction de DataOutputStream équivaut strictement à un write() sur le stream sous-jacent.

    Un mini proof-of-concept lancé en parallèle d'un WireShark devrait suffire pour se rassurer.

    Il se trouve que j'envoie (avec write sur le OutputStream) un tableau de bytes de 17000 cases. C'est trop ? Car mon programme à tendance à planter à ce moment là ...
    Je ne pense pas: il n'y a aucune raison que l'activation du NO_DELAY restreigne au write() d'une quantité de données inférieure à ce que peut contenir un paquet. Logiquement, la socket en TCP-NODELAY doit être capable de scinder le gros message en plusieurs sous-paquets sans ton intervention.

  10. #10
    Invité
    Invité(e)
    Par défaut
    Je n'utilise pas le DataOutputStream sur l'OutputStream.
    Je fais comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
        public void sendAction(PRemoteDroidAction action) throws IOException
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            action.toDataOutputStream(new DataOutputStream(baos));
     
            synchronized (this.socket.getOutputStream())
            {
                this.socket.getOutputStream().write(baos.toByteArray());
            }
        }
    Je passe donc par un tableau temporaire et j'envoie tout d'un coup.

    Mais je vais peut etre faire autrement :
    - je desactive le nodelay
    - je branche directement le DataOutputStream sur l'OutputStream de mon socket
    - j'appelle un flush sur un de mes OutputStream (je ne sais pas lequel, peux tu m'aider ?)

    ça devrait aller, non ?

  11. #11
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par PierreD87 Voir le message
    Je fais comme ça : Je passe donc par un tableau temporaire et j'envoie tout d'un coup.
    Ta solution me paraît idéale (c'est le même genre de chose que j'utilise personellement).

    Citation Envoyé par PierreD87 Voir le message
    ça devrait aller, non ?
    Je ne pense pas: j'imagine qu'en désactivant le NODELAY, ta socket va recommencer à attendre quelques dizaines de millisecondes pour voir s'il n'y a rien d'autre à envoyer juste après, donc l'émission de tes paquets sera retardée.


    Pourrais-tu préciser le "plantage" de ton appli (exception levée, ...) ?

    Éventuellement voir si en augmentant les tampons d'émission/réception (setReceiveBufferSize et setSendBufferSize) ça améliore les choses.

  12. #12
    Invité
    Invité(e)
    Par défaut
    ok, je vais rebasculer sur ma 1ere solution, en augmentant les tailles des buffers.

    Pour préciser mon problème, je fais une application de type VNC (pour Android).
    Quand je transmets juste les actions de controle (souris, etc), aucun probleme
    Quand j'active la transmission d'image, ça plante quand les images sont trop lourdes.
    Je compresse les images en PNG.
    Quand l'image est simple/legere, j'ai 0% de probleme.
    Quand l'image est complexe/lourde, ça plante très rapidement

    Voici un exemple de log lorsque ça plante (c'est sous Android) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    11-19 15:49:49.490: INFO/System.out(7503): RECEPTION CAPTURE 82 bytes
    11-19 15:49:49.490: INFO/System.out(7503): TAILLE REELLE CAPTURE 82bytes
    11-19 15:49:49.900: INFO/System.out(7503): RECEPTION CAPTURE 2408 bytes
    11-19 15:49:49.900: INFO/System.out(7503): TAILLE REELLE CAPTURE 1435bytes
    11-19 15:49:50.000: INFO/System.out(7503): RECEPTION CAPTURE -28097416 bytes
    11-19 15:49:50.030: WARN/dalvikvm(7503): threadid=15: thread exiting with uncaught exception (group=0x40013140)
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503): Uncaught handler: thread Thread-8 exiting due to uncaught exception
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503): java.lang.NegativeArraySizeException
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503):     at org.pierre.remotedroid.protocol.action.ScreenCaptureResponse.parse(ScreenCaptureResponse.java:26)
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503):     at org.pierre.remotedroid.protocol.action.PRemoteDroidAction.parse(PRemoteDroidAction.java:44)
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503):     at org.pierre.remotedroid.protocol.PRemoteDroidConnection.receiveAction(PRemoteDroidConnection.java:30)
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503):     at org.pierre.remotedroid.client.activity.ControlActivity.run(ControlActivity.java:314)
    11-19 15:49:50.030: ERROR/AndroidRuntime(7503):     at java.lang.Thread.run(Thread.java:1058)
    et le code correspondant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        public static ScreenCaptureResponse parse(DataInputStream dis) throws IOException
        {
            short x = dis.readShort();
            short y = dis.readShort();
            int dataSize = dis.readInt();
            System.out.println("RECEPTION CAPTURE " + dataSize + " bytes");
            byte[] data = new byte[dataSize];
            int i = dis.read(data);
            System.out.println("TAILLE REELLE CAPTURE " + i + "bytes");
     
            return new ScreenCaptureResponse(x, y, data);
        }
    Moi... ça me dit rien de bon

    Voilà comment je l'envoie :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
           public void toDataOutputStream(DataOutputStream dos) throws IOException
        {
            System.out.println("ENVOI CAPTURE " + this.data.length + " bytes");
     
            dos.writeByte(SCREEN_CAPTURE_RESPONSE);
            dos.writeShort(this.x);
            dos.writeShort(this.y);
            dos.writeInt(this.data.length);
            dos.write(this.data);
        }
    Le 1er byte envoyé (SCREEN_CAPTURE_RESPONSE) est lu ailleurs.

  13. #13
    Invité
    Invité(e)
    Par défaut
    En fait, il ne me lit pas assez de bytes.
    Il faudrait que j'arrive à le forcer !

  14. #14
    Invité
    Invité(e)
    Par défaut
    et voilàààà !
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
        public static ScreenCaptureResponse parse(DataInputStream dis) throws IOException
        {
            short x = dis.readShort();
            short y = dis.readShort();
            int dataSize = dis.readInt();
            System.out.println("RECEPTION CAPTURE " + dataSize + " bytes");
            byte[] data = new byte[dataSize];
            int i = 0;
            while (i < dataSize)
            {
                i += dis.read(data, i, dataSize - i);
            }
            System.out.println("TAILLE REELLE CAPTURE " + i + " bytes");
     
            return new ScreenCaptureResponse(x, y, data);
        }
    Comme ça, je suis assuré que mon tableau de bytes est bien rempli !

  15. #15
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par PierreD87 Voir le message
    En fait, il ne me lit pas assez de bytes.
    Il faudrait que j'arrive à le forcer !
    Non, c'est à toi de gérer les différents cas:

    - quand tu reçois des octets, tu les mets dans un buffer intermédiaire, puis:

    - si tu n'as pas encore la taille du prochain message, et si ton buffer intermédiaire contient assez de bytes pour lire la taille du message, alors tu décode la taille et tu la sotckes quelque part.

    - si la taille est déjà décodée et que ton buffer intermédiaire contient au moins 'taille' octets, tu as un nouveau message complet disponible. Tu le traites et tu le supprime de ton buffer intermédiaire.

    - sinon, tu attends de recevoir plus de données.


    Plus globalement, il faut envisager le cas limite où ta socket reçoit des octets un par un (et donc t'exécute ta callback à chaque fois pour un octet). Si tu gères ce cas là, tu gèreras de fait n'importe quel cas où les données sont découpées en plusieurs paquets.

  16. #16
    Invité
    Invité(e)
    Par défaut
    en fait, ça vient de l'instruction read(byte[]) ou read(byte[],int,int)
    Je croyais que ça remplissais automatiquement le tableau au maximum.
    Mais en fait, ça lit ce que ça veut, et ça dit combien ça a lu.

    Il faut donc s'en assurer, et stocker dans un "buffer intermédiaire"
    En l'occurrence, ce buffer est le tableau final, puisque je peux le remplir à l'endroit que je veux.

    Par contre, j'ai un autre problème maintenant :/
    Mon PC envoie trop d'informations au client, qui est un terminal Android, donc l'affichage prend du retard >.<

    Merci beaucoup !

  17. #17
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par PierreD87 Voir le message
    Mais en fait, ça lit ce que ça veut, et ça dit combien ça a lu.
    Oui, enfin ça lit surtout ce que ça peut (ie. ce qui a déjà été reçu)

    Bon courage pour la suite

  18. #18
    Invité
    Invité(e)
    Par défaut
    Plus simple, le readFully(byte[]) qui je crois, attend que le tableau soit complètement rempli.

  19. #19
    Modérateur
    Avatar de nouknouk
    Homme Profil pro
    Inscrit en
    Décembre 2006
    Messages
    1 655
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations forums :
    Inscription : Décembre 2006
    Messages : 1 655
    Points : 2 161
    Points
    2 161
    Par défaut
    Citation Envoyé par PierreD87 Voir le message
    Plus simple, le readFully(byte[]) qui je crois, attend que le tableau soit complètement rempli.
    Attention au multi-client alors: puisque la fonction va être bloquante, cette solution ne pourra être utilisée que si tu utilises un thread par client.

    Par contre, si tu optes pour une archi serveur en Java NIO (aussi appelé communément 'select/poll') avec donc un seul thread pour l'ensemble des clients, cette solution ne sera plus envisageable.

  20. #20
    Invité
    Invité(e)
    Par défaut
    J'ai plusieurs clients.
    J'ai un thread par "connexion client".
    Donc pas de problèmes ?

    Mais normalement, on a bien un Socket par client connecté?
    Donc un InputStream par client ? (en supposant qu'il y a 1 InputStream par Socket)

    Je ne vois pas bien de quelle architecture tu parles ...

Discussions similaires

  1. Protocoles TCP et UDP
    Par gabgab dans le forum Protocoles
    Réponses: 6
    Dernier message: 10/01/2014, 17h38
  2. Réponses: 1
    Dernier message: 09/10/2013, 21h41
  3. Création d'un protocole : TCP ou UDP ?
    Par ram-0000 dans le forum Contribuez
    Réponses: 2
    Dernier message: 24/04/2013, 09h00
  4. Communication TSX Premium -> TCP ou UDP
    Par abyssetique dans le forum Automation
    Réponses: 2
    Dernier message: 28/10/2011, 21h55
  5. développement d'un protocole de communication
    Par olymat dans le forum Développement
    Réponses: 5
    Dernier message: 09/09/2005, 09h23

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo