from __future__ import unicode_literals # -*- coding: utf-8 -*- """serveurs chat et chat vocal (TCP et UDP)""" from socket import socket, AF_INET, SOCK_DGRAM, SOCK_STREAM from threading import Thread from os import system from time import time, sleep import logging #### intialisation des variables #### hote = '' port = 6660 connectes = {} #id, adresse pseudos = {} #id, pseudo taille_paquet = 1024 tampon = [] #parametres deboguage #logging.basicConfig(level=logging.DEBUG, # format='(%(threadName)-10s) %(message)s', # ) #gestion des erreurs et log logging.basicConfig(level=logging.DEBUG) logServeur = logging.getLogger(__name__) handlerServeur = logging.FileHandler('serveur.log') handlerServeur.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s - %(message)s')) #%(name)s - nom du module logServeur.addHandler(handlerServeur) logServeur.debug(" ---------------------- \n") #classes utilitaires: class OutilsCo(): """outils utilises pour gerer la connexion""" def __init__(self): """initialisation""" self.idUtilises = ["00", "mj"] def genererID(self): """génère un id de 2 caractères non utilisé""" caracteres = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "<", ">", "+", "=", "-", "*", "@"] fin = False for c1 in caracteres: for c2 in caracteres: txt = "{}{}".format(c1,c2) if not txt in self.idUtilises: fin = True break if fin: break self.idUtilises.append(str(txt)) logServeur.info("Id {} attribue".format(txt)) return txt def libererID(self, txt): """retire un identifiant de la liste des id utilises""" try: self.idUtilises.remove(txt) except: logServeur.warning("impossible de liberer l'id {}".format(txt) ) #### fils secondaires #### class DebitMetre(Thread): """debitmetre permettant de mesurer le debit d'emission/reception du serveur""" def __init__(self, taille_paquet): """création du serveur vocal""" Thread.__init__(self) self.mesureEnvoi = 0 self.mesureReception = 0 self.debitEmission = 0.00 self.debitReception = 0.00 #si oui, affiche les resultats chaque seconde: self.affichage = False def envoi(self, taille_paquet): """pour signaler l'envoi d'un paquet""" self.mesureEnvoi += taille_paquet def reception(self, taille_paquet): """pour signaler la reception d'un paquet""" self.mesureReception += taille_paquet def run(self): """mesure le volume en octets qui transite chaque seconde""" self.lance = True t0 = time() while self.lance: delta = time() - t0 if delta >= 1: self.debitEmission = self.mesureEnvoi / delta self.mesureEnvoi = 0 self.debitReception = self.mesureReception / delta self.mesureReception = 0 t0 = time() if self.affichage: logServeur.info(self) def __repr__(self): """affiche les derniers debits mesures (en ko/s)""" retour = "** Reception: {} ko/s ** -> Emission: {} ko/s **"\ "".format((self.debitReception * 0.001), (self.debitEmission * 0.001)) return retour def stop(self): """Fermeture du fil debitmetre""" self.lance = False class ServeurVoc(Thread): """serveur UDP dedie au chat vocal""" def __init__(self, port): """création du serveur vocal""" Thread.__init__(self) self.serveur_lance = False self.connectes = [] self.port = port def creer(self): """creation d'une entree serveur UDP""" try: self.socket = socket(AF_INET, SOCK_DGRAM) self.socket.bind(('', self.port)) retour = "Le serveur vocal ecoute a present sur le port {}".format(self.port) self.serveur_lance = True self.start() except IOError, e: if e.errno==10048: retour = "Une instance du serveur est déja lancée" logging.error(retour) else: retour = "Echec de lancement du serveur" logServeur.error(retour) finally: logServeur.info(retour) return retour def nouveauClient(self, adresse): """ajoute un nouveau client a la liste des connectes""" self.connectes.append(adresse) retour = "Nouveau client connecte: {})".format(adresse) logServeur.info(retour) return retour def supprimerClient(self, adresse): """ajoute un nouveau client a la liste des connectes""" self.connectes.remove(adresse) retour = "Client deconnecte: {})".format(adresse) logServeur.info(retour) return retour def run(self): """boucle de fonctionnement du serveur""" self.socket.setblocking(0) while self.serveur_lance: data = "" #on recoit les donnees envoyees try: data, adresse = self.socket.recvfrom(1024) try: dM.reception(len(data)) except: pass except IOError, e: if e.errno == 10035: #pas de donnee recue pass elif e.errno == 10022: logServeur.debug("erreur de reception {}".format(adresse)) sleep(0.02) elif e.errno == 10054: self.connectes = [] logServeur.warning("reinitialisation de la liste des connectes") else: logServeur.error("erreur {}".format(e.errno)) #self.supprimerClient() if len(data) > 0: if not adresse in self.connectes: #l'adresse n'est pas dans la liste: on l'ajoute aux connectes self.nouveauClient(adresse) for dest in self.connectes: if dest != adresse: #le client ne recoit pas ses propres donnees self.socket.sendto(data , dest) try: dM.envoi(len(data)) except: pass #tmp = "{} -> {}".format(len(data), adresse) def stop(self): """Fermeture du serveur""" self.serveur_lance = False self.socket.close() retour = "Serveur vocal ferme" logServeur.info("Fermeture du serveur vocal") return retour class FilClient(Thread): """fil de connexion client au serveur TCP""" def __init__(self, outils, cnn): """creation du fil""" Thread.__init__(self) self.cnn = cnn self.pseudo = "inconnu" self.idClient = "00" self.outils = outils self.connecte = False def creer(self): """initialise la connexion avec le client""" self.connecte = True #on genere un id pour lui self.idClient = self.outils.genererID() #on ajoute son adresse reseau a la liste des connectes connectes[self.idClient] = self.cnn logServeur.info("Client {} connecte - Id {}".format(self.getName(), self.idClient)) #on demarre le fil de reception self.start() def run(self): """echange avec le client""" self.cnn.setblocking(0) while self.connecte: #reception du message client try: msgClient = self.cnn.recv(1024) try: dM.reception(len(msgClient)) except: pass tampon.append((self.getName(), msgClient)) except IOError, e: if e.errno == 10035: #aucun message recu pass else: if self.connecte: logServeur.warning("serveur : erreur reception") self.stop() def stop(self): """ferme le fil client""" self.connecte = False #fermeture de la connexion avec le client: #on supprime le client de la liste des connectes sleep(0.01) try: del connectes[self.idClient] except: logServeur.warning("Impossible de retirer {} de la liste des connectes".format(self.idClient)) #on libere son identifiant self.outils.libererID(self.idClient) self.cnn.close() logServeur.info("Client {} deconnecte ({})".format(self.pseudo, self.getName())) # Le fil se termine ici class Serveur(Thread): """serveur TCP pour chat, echange de fichiers ou d'objets...""" def __init__(self, port): """creation du serveur""" self.serveur_lance = False Thread.__init__(self) self.port = port self.outils = OutilsCo() self.filsOuverts = {} def traitement(self, nomClient, msg): """traite le message selon son type et son/ses destinataire(s)""" client = self.filsOuverts[nomClient] if len(msg) >= 6: nature = msg[0:2] emet = msg[2:4] dest = msg[4:6] try: contenu = msg[6:] except: contenu = "" if dest == "sa": #message pour le serveur, on ne le retransmet pas if nature == "ci": #le client informe de sa connexion et demande un identifiant client.pseudo = contenu pseudos[client.idClient] = client.pseudo logServeur.info("connexion: {} -> {} ({})".format(client.getName(), client.pseudo, client.idClient)) client.cnn.sendall("cisa"+client.idClient) for cnn in connectes.values(): if cnn != client.cnn: msg = "ccsatc{}{}".format(client.idClient, client.pseudo) cnn.sendall(msg) sleep(0.001) try: dM.envoi(len(msg)) except: pass if nature == "cd": #client informe de sa deconnexion for cnn in connectes.values(): if cnn != client.cnn: msg = "cdsaac{}".format(emet) cnn.sendall(msg) try: dM.envoi(len(msg)) except: pass client.stop() elif nature == "f0": #client demande un identifiant pour un envoi de fichier msg = "f0sa{}{}".format(client.idClient, self.outils.genererID()) client.cnn.sendall(msg) elif nature == "f1": #client signale au serveur qu'il peut liberer l'identifiant du fichier self.outils.libererID(contenu) elif dest == "tc": #message pour tous les clients, emetteur compris for cnn in connectes.values(): cnn.sendall(msg) try: dM.envoi(len(msg)+1) except: pass elif dest == "ac": #message pour tous les clients excepte l'emetteur for cnn in connectes.values(): if cnn != client.cnn: cnn.sendall(msg) try: dM.envoi(len(msg)+1) except: pass elif dest == "sv": #message pour le serveur vocal logServeur.debug("pour serveur vocal: {}".format(contenu)) #else: elif dest in connectes: #message pour un client unique (mj ou autre) connectes[dest].sendall(msg) else: logServeur.warning("Erreur : format du message illisible -> {}".format(msg)) def creer(self): """demarre le serveur""" """ try:""" self.cnn = socket(AF_INET, SOCK_STREAM) self.cnn.bind((hote, self.port)) self.cnn.listen(5) retour = "Le serveur ecoute a present sur le port {}".format(port) self.serveur_lance = True self.start() """except: retour = "Impossible de demarrer le serveur""" """finally: logServeur.info(retour) """ return self.serveur_lance, retour def run(self): """boucle de fonctionnement""" self.cnn.setblocking(0) while self.serveur_lance: try: #tant que le serveur est lance, il reste disponible a de nouvelles connexions cnnClient, adresse = self.cnn.accept() #on cree un nouveau fil pour le nouveau client client = FilClient(self.outils, cnnClient) #on ajoute ce fil à la liste pour pouvoir le fermer self.filsOuverts[client.getName()] = client client.creer() except IOError, e: if e.errno == 10035: #si pas de nouvelles connexions, on traite les messages en attente: #le serveur lit les 3 premiers car indiquant la longueur du message ('drapeaux' non compris) #puis les retire du message avant le traitement try: nomClient, txt = tampon[0] msgClient = txt if len(msgClient) >= 9: while len(msgClient) > 0: lg = int(msgClient[0:3]) + 6 self.traitement(nomClient, msgClient[3:lg+3]) msgClient = msgClient[lg+3:] tampon.remove((nomClient, txt)) txt = msgClient = "" except IndexError: #la liste tampon est vide pass else: logServeur.error("serveur : erreur attente clients") break def stop(self): """ferme le serveur""" self.serveur_lance = False #for client in connectes.keys(): for client in self.filsOuverts.values(): if client.connecte: client.cnn.sendall("sdsatc") client.stop() sleep(0.1) self.cnn.close() logging.info("Fermeture du serveur TCP") #### fil principal #### if __name__ == "__main__": # si lancement direct: #debitmetre pour mesurer l'emission #dM = DebitMetre(taille_paquet) #dM.affichage = False s = Serveur(port) s.creer() sVoc = ServeurVoc(port) sVoc.creer() #dM.start() system("pause") s.stop() sVoc.stop() #dM.stop() logServeur.info("- Fin -") sleep(0.1)