Partage
  • Partager sur Facebook
  • Partager sur Twitter

Serveur multiprocess et websocket

librairie Websockify

Sujet résolu
Anonyme
    17 juin 2013 à 19:53:04

    Bonjour,

    je me suis mis a Python y'a 2 jours par nécessité, je fais joujou avec les websockets, et la seule librairie potable était en python, donc je me suis dit "pourquoi pas".

    Le soucis que j'ai, c'est que malgré que j'ai bien compris et adapté le code, j'ai un soucis de dictionnaire.
    je doit créer un dictionnaire que j'apelle usersList qui contient la liste des utilisateurs ainsi que leur cqueue (liste qui correspond a la pile de frame a envoyer sur le socket),
    je stocke aussi son nickname, un simple string 

    Le premier index de mon dictionnaire est un ID unique généré avec time et md5, cet ID je l'apelle PUB_ID.

     Donc on a cette structure :

    usersList{

            "f292c8886b8467ca5cd001cb47a92a87" : {

                         cqueue : [liste de fames],

                         nickname : monSuperSurnom

            },

            "d5qsd5ddd5665220564qs5d105640s5" : {

                        cqueue : [liste de frames],

                        nickname : PseudoMachin

            },

            etc....

    }

    Le probleme c'est que quand je fais appel a cqueue il me dit que ce n'est pas un attribut/propriété du dictionnaire. De même il n'y a pas de fonction extend().

    je suis un peu perdu :/ 

    Le code en question :

    class GameServer(WebSocketServer):
        buffer_size = 8096
                
        usersList = {};
        #usersList[pub_id] .cqueue|.nickname
        #   [pub_id] = pub_id of user
        #       [cqueue] = [] of data
        #       [nickname] = str
        
        def generate_pub_id(self):
            print "generate MD5 pub_id";
            pub_id = md5.new(str(time.time())).hexdigest()
            print "pubi_id : " + pub_id
            return pub_id
        
        def connect_chat(self, pub_id, args):
            print "connection"
            self.usersList[pub_id].cqueue.extend("{'CONNECT':'OK','PUB_ID':'" + pub_id + "'}")
            self.usersList[pub_id].nickname = args['NICKNAME']
            print "new user " + args['NICKNAME']
            return True
        
        def receive_message(self, pub_id, args):
            print "receiving message"
            for pubid in self.users:
            	self.usersList[pubid].cqueue.extend("{'MESSAGE':'" + args['MESSAGE'] + "','FROM':'" + self.usersList[pub_id].nickname + "'}")
            return True
            
        def quit_chat(self, pub_id):
            print "user quit chat"
            del self.usersList[pub_id]
            return True
    
        def new_client(self):
            cqueue = []
            c_pend = 0
            cpartial = ""
            rlist = [self.client]
            
            pub_id = self.generate_pub_id()
            self.usersList[pub_id] = {cqueue : [], nickname : ""}
    
            while True:
                wlist = []
    
                if self.usersList[pub_id].cqueue or c_pend: wlist.append(self.client)
                ins, outs, excepts = select.select(rlist, wlist, [], 1)
                if excepts: raise Exception("Socket exception")
    
                if self.client in outs:
                    # Send queued target data to the client
                    c_pend = self.send_frames(self.usersList[pub_id].cqueue)
    
                if self.client in ins:
                    # Receive client data, decode it, and send it back
                    frames, closed = self.recv_frames()
                    
                    self.usersList[pub_id].cqueue.extend("{'CHAT_NBUSERS':" + len(self.usersList) + "}")
                    
                    for i in range(0,len(frames)):
                        elem=frames[i]
                        jsonElem = json.loads(elem)
                        for key in jsonElem.keys():
                            if key == 'CONNECT':
                                self.connect_chat(pub_id, jsonElem['ARGS'])
                            elif key == 'SEND':
                                self.receive_message(pub_id, jsonElem['ARGS'])
                            elif key == 'QUIT':
                                self.quit_chat(pub_id, jsonElem['ARGS'])
                    
                    if closed:
                        self.send_close()
                        raise self.EClose(closed)



    -
    Edité par Anonyme 26 juin 2013 à 19:17:39

    • Partager sur Facebook
    • Partager sur Twitter
      17 juin 2013 à 20:13:27

      C'est simplement parce que tu n'utilises pas la bonne syntaxe :

      >>> dico = {'plop': {'cqueue': [1,2,3,4], 'username': 'plop'}, 'plap': {'cqueue': [5,6,7,8], 'username':'plap'}}
      >>> dico['plop']
      {'username': 'plop', 'cqueue': [1, 2, 3, 4]}
      >>> dico['plop']['cqueue']
      [1, 2, 3, 4]
      

      Pour ce qui est de "extend", si tes éléments sont des listes, alors l'usage que tu en fais correspond plus à la méthode append que extend : la première ajoute un élément en fin de liste, alors que la second lui ajoute tout une liste (ou un itérable) d'éléments.

      -
      Edité par nohar 17 juin 2013 à 20:17:10

      • Partager sur Facebook
      • Partager sur Twitter
      Zeste de Savoir, le site qui en a dans le citron !
      Anonyme
        17 juin 2013 à 20:58:09

        Merci Nohar, j'ai en effet corrigé le probleme, j'y étais presque.
        Et j'ai aussi remplacé mes extend par append quand j'ajoutais qu'une entrée.
        J'ai corrigé d'autre truc, ca a l'air de fonctionner.

        Je laisse ouvert le temps de vérifier que tout fonctionne ;) 

        • Partager sur Facebook
        • Partager sur Twitter
        Anonyme
          17 juin 2013 à 22:53:35

          Bon j'ai un nouveau soucis, ma variable usersList n'est pas globale, je comprends pas :/

          Comment c'est possible et comment je dois la déclarer ??

          EDIT : je me dis que comme tout autre langage les GLOBALS c'est mal, donc je vais faire une nouvelle class qui gérera ma usersList.
          Par contre est-ce que j'y aurais accès sans probleme ??
          De toute facon je vais voir ;) 

          modif du code :

          #!/usr/bin/env python
          
          '''
          A WebSocket server that echos back whatever it receives from the client.
          Copyright 2010 Joel Martin
          Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
          
          You can make a cert/key with openssl using:
          openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
          as taken from http://docs.python.org/dev/library/ssl.html#certificates
          '''
          
          import os, sys, select, optparse, json, time, md5
          sys.path.insert(0,os.path.dirname(__file__) + "/websockify")
          from websocket import WebSocketServer
          
          usersList = {}
          
          
          class GameServer(WebSocketServer):
              buffer_size = 8096
                      
              global usersList
              #usersList[pub_id] .cqueue|.nickname
              #   [pub_id] = pub_id of user
              #       [cqueue] = [] of data
              #       [nickname] = str
              
              def generate_pub_id(self):
                  print "generate MD5 pub_id";
                  pub_id = md5.new(str(time.time())).hexdigest()
                  print "pubi_id : " + pub_id
                  return pub_id
              
              def connect_chat(self, pub_id, args):
                  print "connection"
                  usersList[pub_id]['cqueue'].append('{"CONNECT":"OK","PUB_ID":"' + pub_id + '"}')
                  usersList[pub_id]['nickname'] = args['NICKNAME'].decode().encode('utf-8')
                  print "new user " + args['NICKNAME'].decode().encode('utf-8')
              
              def receive_message(self, pub_id, args):
                  print "receiving message"
                  print usersList.keys()
                  for pubid in usersList.keys():
                      usersList[pubid]['cqueue'].append('{"MESSAGE":"' + args['MESSAGE'].decode().encode('utf-8') + '","FROM":"' + usersList[pub_id]['nickname'] + '"}')
                      print "message sent to " + pubid + ":" + usersList[pubid]['nickname']
                  
              def quit_chat(self, pub_id, args):
                  print "user quit chat"
                  del usersList[pub_id]
              
              def send_info(self, pub_id, args):
                  usersList[pub_id]['cqueue'].append('{"CHAT_NBUSERS":' + str(len(usersList)) + '}')
          
              def new_client(self):
                  c_pend = 0
                  cpartial = ""
                  rlist = [self.client]
                  
                  pub_id = self.generate_pub_id()
                  usersList[pub_id] = {'cqueue' : [], 'nickname' : ""}
          
                  while True:
                      wlist = []
          
                      if usersList[pub_id]['cqueue'] or c_pend: wlist.append(self.client)
                      ins, outs, excepts = select.select(rlist, wlist, [], 1)
                      if excepts: raise Exception("Socket exception")
          
                      if self.client in outs:
                          # Send queued target data to the client
                          c_pend = self.send_frames(usersList[pub_id]['cqueue'])
                          usersList[pub_id]['cqueue'] = []
          
                      if self.client in ins:
                          # Receive client data, decode it, and send it back
                          frames, closed = self.recv_frames()
                          
                          for i in range(0,len(frames)):
                              elem=frames[i]
                              jsonElem = json.loads(elem)
                              for key in jsonElem.keys():
                                  if   key == 'CONN':
                                      self.connect_chat(pub_id, jsonElem['ARGS'])
                                  elif key == 'SEND':
                                      self.receive_message(pub_id, jsonElem['ARGS'])
                                  elif key == 'QUIT':
                                      self.quit_chat(pub_id, jsonElem['ARGS'])
                                  elif key == 'INFO':
                                      self.send_info(pub_id, jsonElem['ARGS'])
                          
                          if closed:
                              self.send_close()
                              raise self.EClose(closed)
          
          if __name__ == '__main__':
              parser = optparse.OptionParser(usage="%prog [options] listen_port")
              parser.add_option("--verbose", "-v", action="store_true",
                      help="verbose messages and per frame traffic")
              #parser.add_option("--cert", default="self.pem",
              #        help="SSL certificate file")
              #parser.add_option("--key", default=None,
              #        help="SSL key file (if separate from cert)")
              #parser.add_option("--ssl-only", action="store_true",
              #        help="disallow non-encrypted connections")
              (opts, args) = parser.parse_args()
          
              try:
                  if len(args) != 1: raise
                  opts.listen_port = int(args[0])
              except:
                  parser.error("Invalid arguments")
          
              opts.web = "."
              server = GameServer(**opts.__dict__)
              server.start_server()
          
          



          -
          Edité par Anonyme 17 juin 2013 à 23:06:18

          • Partager sur Facebook
          • Partager sur Twitter
            17 juin 2013 à 23:38:10

            Dudule13 a écrit:

            Bon j'ai un nouveau soucis, ma variable usersList n'est pas globale, je comprends pas :/

            C'est quoi le message d'erreur complet ? Normalement tu n'as pas besoin de la déclarer en global vu que tu n'essayes pas de lui affecter une nouvelle valeur.

            D'ailleurs si tu ne la déclares pas globale avec le mot-clé global, elle appartient au namespace du module, ce qui est largement suffisant (et non, ce n'est pas sale dans ce cas, c'est juste pragmatique. Inutile de te prendre la tête avec une classe pour si peu).

            Le fait que tu aies collé cette ligne global usersList peut en revanche générer cette erreur. Tu devrais la dégager.

            Edit :

            Ta classe ne sert à rien : la usersList ne lui appartient pas, donc tu perds le seul intérêt d'une classe ici (pouvoir lancer plusieurs instances idépendantes de ton serveur). Si tu tiens à faire une classe, pour faire propre, il faudrait qu'elle ait un attribut self.usersList que tu initialiserais à {} dans son constructeur :

            def __init__(self):
                self.usersList = {}
            
            # ... remplace ensuite toutes les autres occurences de usersList par self.usersList
            

            Edit2: Autre remarque :

            if   key == 'CONN':
                self.connect_chat(pub_id, jsonElem['ARGS'])
            elif key == 'SEND':
                self.receive_message(pub_id, jsonElem['ARGS'])
            elif key == 'QUIT':
                self.quit_chat(pub_id, jsonElem['ARGS'])
            elif key == 'INFO':
                self.send_info(pub_id, jsonElem['ARGS'])
            

            Y'a moyen de faire ça plus élégamment :

            action_handler = {'CONN': self.connect_chat,
                              'SEND': self.receive_message,
                              'QUIT': self.quit_chat,
                              'INFO': self.send_info}
            
            # ...
            action_handler[key](pub_id, jsonElem['ARGS'])
            

            Je trouverais même naturel que ce action_handler soit aussi déclaré comme attribut de ta classe server dans son constructeur. De cette façon tu peux étendre ton code et éventuellement rajouter de nouvelles actions simplement.

            -
            Edité par nohar 18 juin 2013 à 0:01:23

            • Partager sur Facebook
            • Partager sur Twitter
            Zeste de Savoir, le site qui en a dans le citron !
            Anonyme
              21 juin 2013 à 19:01:03

              Bonjour nohar et merci pour toutes ces infos.

              J'avais en effet vu action_handler dans un exemple et j'ai modifié le code, j'ai aussi remplacé optsparse par argsparse car la librairie n'est plus maintenue.

              Pour expliquer un peu le but de tout ca, c'est un projet de stage que j'ai confié a mes 2 stagiaires, ma société faisant du jeu video internet, je les fais travailler sur un serveur créant un service websocket. Le serveur sera diffusé sous licence GPLv3 comme mon framework PHP, il a pour but aussi de remplacer mon serveur APE (Ajax Push Engine - http://ape-project.org) dont les auteurs n'entretiennent plus la docs, le site etc... Et n'y comprenant rien a leur code (C++), j'ai décidé de faire ce serveur qui fonctionne sur le même principe de commandes et communication en JSON.

              Nous avons pas mal avancé, et avons intégré un systèmes de Threads et mis le fonctionnement de WebSockify en run_once, car il fonctionnait en multiprocessing, c'est ce qui nous empêchait d'accéder a la UsersList (1 instance par process, donc 1 seule utilisateur dans la users list). Et une préférence pour les Threads au multiprocessing aussi nous a poussé a faire ca.

              """Le soucis qu'on a actuellement, c'est qu'on accède toujours pas a la class principale depuis le thread, ni a userslist, et que j'avoue ne pas très bien comprendre comment fonctionne le système de queueing pour y accéder.
              Un peu d'éclaircissement ne serait pas de refus :) """ Probleme résolu

              EDIT : nouveau probleme, il ne me lance pas de nouveau thread tant que le premier n'est pas fini

              Voici les fichiers zippé ca sera plus simple : http://projets.pics-link.com/projet.zip

              Contient le serveur pour le lancer : ./main.py
              Le client c'est juste un HTML a lancer dans un navigateur. 

              -
              Edité par Anonyme 21 juin 2013 à 23:55:05

              • Partager sur Facebook
              • Partager sur Twitter
              Anonyme
                23 juin 2013 à 12:27:00

                Bonjour,

                Petite question Nohar, peut-on bind le même port avec du multithread, ou doit on passer exlusivement par du multiprocess ?

                Et désolé si j'ai des lacunes, je m'y suis mis en debut de semaine. On m'avait dit que ce langage pouvait ếtre appris a un enfant de 5ans tellement il est simple dans sa logique, j'y croyais pas, mais en fait, il est vraiment simple.

                -
                Edité par Anonyme 23 juin 2013 à 12:36:20

                • Partager sur Facebook
                • Partager sur Twitter
                  23 juin 2013 à 16:18:05

                  Petite question Nohar, peut-on bind le même port avec du multithread, ou doit on passer exlusivement par du multiprocess ?

                  Multithread ou multiprocessing, ça ne change rien. Je suppose que tu veux binder plusieurs sockets au même port TCP : c'est impossible.

                  Par contre une fois que tu as bindé ta socket TCP à son port et que tu as accept()-é une connexion, cela crée une nouvelle socket, que tu peux passer au thread que tu veux. La socket que tu crées côté serveur est une "master socket", elle doit rester unique parce que c'est elle qui permet aux clients TCP de se connecter (c'est son seul et unique rôle), mais elle crée une nouvelle socket indépendante (cette fois que tu peux lire ou écrire) chaque fois qu'un client se connecte.

                  -
                  Edité par nohar 23 juin 2013 à 16:20:37

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Zeste de Savoir, le site qui en a dans le citron !
                  Anonyme
                    23 juin 2013 à 22:50:26

                    C'est ce qui me semblait :/

                    Donc si je resume :

                    1 tu bind un port, port communiqué aux clients. Ce listenning est maitre de tous et permet d'accueillir les nouvelles connexion.
                    2 Si le master socket recoit une connexion, il recrée un nouveau thread qui lui récupère la connexion mais communique sur un id de connexion différent.
                    3 Il y a donc jamais de nouveau bind de créer mais un changement de destinataire du paquet ?

                    C'est bien ca ?

                    Ici je suis sur du websocket, c'est du socket mais avec un protocol HTML/WS encapsulé, mais ca change rien a la mécanique des bind de port

                    -
                    Edité par Anonyme 23 juin 2013 à 22:52:48

                    • Partager sur Facebook
                    • Partager sur Twitter
                      24 juin 2013 à 1:03:15

                      Voilà t'as tout compris, sauf que le thread en question, si tu tiens à ce que ton programme soit multithread, c'est à toi de le créer.

                      C'est pas forcément obligatoire (tu peux souvent dispatcher le traitement toi même avec le module select), mais c'est tout à fait jouable comme ça.

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Zeste de Savoir, le site qui en a dans le citron !
                      Anonyme
                        24 juin 2013 à 14:22:21

                        Merci Nohar, je vais donc modifier en conséquence ;)

                        Nohar, je vais avoir besoin encore d'un coup de main, il me fait bien le multi thread et tout.
                        A priori mes objets type UsersList et ThreadsList sont bien accessible depuis tous les autres objets même les threads.
                        J'ai rechangé la structure, revu le fonctionnement.

                        Donc il bind le port et ensuite, je lance le ou les threads. Mais j'ai l'impression qui ne partage pas la ressource socket au thread.
                        Je recois toujours un message d'erreur me disant que le socket n'est pas pret.

                        Y'a pas mal d'erreur je ne cesse d'en corriger

                        EDIT: bon j'ai entrepris de tout réécrire proprement. Socket c'est bon, je rajoute le protocol WS la

                        -
                        Edité par Anonyme 25 juin 2013 à 11:21:29

                        • Partager sur Facebook
                        • Partager sur Twitter
                        Anonyme
                          27 juin 2013 à 11:15:26

                          Bonjour,

                          nous avons avancé, il a été évident qu'il serait plus simple de garder le fonctionnement normal de Websockify et du multiprocessing.
                          J'ai donc réécrit le code plus simplement.

                          J'ai enfin accès a ma UsersList en shared memory via manager, mais on a un dernier soucis, j'arrive a initier dans usersList une nouvelle entrée, lui disant qu'elle comprendra une list accessible via l'entrée "cqueue".

                          }{u'CONN': {u'NICKNAME': u'Djey'}} <= reception d'un donnée envoyé par le client

                          CONN <= Command

                          {'031462d190aefce4333914f7e5791cbc': {'cqueue': []}, 'eff3d572b65162899eefa28610bc656d': {'cqueue': []}} <= UsersList

                          J'ai beau rajouter une nouvelle propriété a mon users comme son nickname, en faisant self.usersList[pub_id]['nickname'] = machin
                          je ne l'ai pas dans la usersList.
                          Et pas de message d'erreur. 

                          Impossible d'ecrire dans la list "cqueue" via append ou de rajouter un élément dans la usersList après.
                          Est-ce qu'il y aurait une histoire de lock() etc... a gérer ? 

                          Fichiers toujours dispo sur http://projets.pics-link.com/projet.zip

                          -
                          Edité par Anonyme 27 juin 2013 à 11:17:44

                          • Partager sur Facebook
                          • Partager sur Twitter
                          Anonyme
                            3 juillet 2013 à 9:57:29

                            up ? :/ personne ?
                            • Partager sur Facebook
                            • Partager sur Twitter
                              3 juillet 2013 à 18:43:57

                              Hmmm pardon. En fait c'est un peu démotivant d'aller télécharger ton code pour le débugger...

                              Est-ce qu'il te serait possible de reproduire ton problème avec un code minimaliste ?

                              D'un côté ça serait plus clair pour te fournir une réponse rapide (typiquement en ce moment j'ai pas le temps d'aller plus loin qu'un exemple dans un post), et de l'autre ça te permet aussi à toi d'abstraire ton problème, donc éventuellement de mettre le doigt sur la solution.

                              -
                              Edité par nohar 3 juillet 2013 à 18:50:24

                              • Partager sur Facebook
                              • Partager sur Twitter
                              Zeste de Savoir, le site qui en a dans le citron !
                                3 juillet 2013 à 23:00:34

                                Bonjour,

                                J'ai passé un moment sur ton projet pour comprendre ce qui n'allait pas.

                                En fait, l'explication est dans la doc : http://docs.python.org/2/library/multiprocessing.html#multiprocessing.managers.SyncManager.list

                                « Modifications to mutable values or items in dict and list proxies will not be propagated through the manager, because the proxy has no way of knowing when its values or items are modified. To modify such an item, you can re-assign the modified object to the container proxy. »

                                Tu ne peux donc pas faire :

                                self.usersList[pub_id]['cqueue'].append(message)

                                Par contre, ceci marche :

                                l = self.usersList[pub_id]
                                l['cqueue'].append(message)
                                self.usersList[pub_id] = l

                                Bonne soirée !

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Anonyme
                                  4 juillet 2013 à 2:39:45

                                  Merci !!!

                                  En effet, j'ai lu en diagonale, sans tout comprendre.
                                  Je vais tester ca de suite ;)

                                  Merci encore pour le temps passé dessus.

                                  EDIT: ca marche niquel ;)

                                  -
                                  Edité par Anonyme 4 juillet 2013 à 3:44:13

                                  • Partager sur Facebook
                                  • Partager sur Twitter

                                  Serveur multiprocess et websocket

                                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                  • Editeur
                                  • Markdown