serveur.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. from __future__ import unicode_literals
  2. # -*- coding: utf-8 -*-
  3. """serveurs chat et chat vocal (TCP et UDP)"""
  4. from socket import socket, AF_INET, SOCK_DGRAM, SOCK_STREAM
  5. from threading import Thread
  6. from os import system
  7. from time import time, sleep
  8. import logging
  9. #### intialisation des variables ####
  10. hote = ''
  11. port = 6660
  12. connectes = {} #id, adresse
  13. pseudos = {} #id, pseudo
  14. taille_paquet = 1024
  15. tampon = []
  16. #parametres deboguage
  17. #logging.basicConfig(level=logging.DEBUG,
  18. # format='(%(threadName)-10s) %(message)s',
  19. # )
  20. #gestion des erreurs et log
  21. logging.basicConfig(level=logging.DEBUG)
  22. logServeur = logging.getLogger(__name__)
  23. handlerServeur = logging.FileHandler('serveur.log')
  24. handlerServeur.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s - %(message)s')) #%(name)s - nom du module
  25. logServeur.addHandler(handlerServeur)
  26. logServeur.debug(" ---------------------- \n")
  27. #classes utilitaires:
  28. class OutilsCo():
  29. """outils utilises pour gerer la connexion"""
  30. def __init__(self):
  31. """initialisation"""
  32. self.idUtilises = ["00", "mj"]
  33. def genererID(self):
  34. """génère un id de 2 caractères non utilisé"""
  35. caracteres = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
  36. "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
  37. "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
  38. "<", ">", "+", "=", "-", "*", "@"]
  39. fin = False
  40. for c1 in caracteres:
  41. for c2 in caracteres:
  42. txt = "{}{}".format(c1,c2)
  43. if not txt in self.idUtilises:
  44. fin = True
  45. break
  46. if fin:
  47. break
  48. self.idUtilises.append(str(txt))
  49. logServeur.info("Id {} attribue".format(txt))
  50. return txt
  51. def libererID(self, txt):
  52. """retire un identifiant de la liste des id utilises"""
  53. try:
  54. self.idUtilises.remove(txt)
  55. except:
  56. logServeur.warning("impossible de liberer l'id {}".format(txt) )
  57. #### fils secondaires ####
  58. class DebitMetre(Thread):
  59. """debitmetre permettant de mesurer le debit d'emission/reception du serveur"""
  60. def __init__(self, taille_paquet):
  61. """création du serveur vocal"""
  62. Thread.__init__(self)
  63. self.mesureEnvoi = 0
  64. self.mesureReception = 0
  65. self.debitEmission = 0.00
  66. self.debitReception = 0.00
  67. #si oui, affiche les resultats chaque seconde:
  68. self.affichage = False
  69. def envoi(self, taille_paquet):
  70. """pour signaler l'envoi d'un paquet"""
  71. self.mesureEnvoi += taille_paquet
  72. def reception(self, taille_paquet):
  73. """pour signaler la reception d'un paquet"""
  74. self.mesureReception += taille_paquet
  75. def run(self):
  76. """mesure le volume en octets qui transite chaque seconde"""
  77. self.lance = True
  78. t0 = time()
  79. while self.lance:
  80. delta = time() - t0
  81. if delta >= 1:
  82. self.debitEmission = self.mesureEnvoi / delta
  83. self.mesureEnvoi = 0
  84. self.debitReception = self.mesureReception / delta
  85. self.mesureReception = 0
  86. t0 = time()
  87. if self.affichage:
  88. logServeur.info(self)
  89. def __repr__(self):
  90. """affiche les derniers debits mesures (en ko/s)"""
  91. retour = "** Reception: {} ko/s ** -> Emission: {} ko/s **"\
  92. "".format((self.debitReception * 0.001), (self.debitEmission * 0.001))
  93. return retour
  94. def stop(self):
  95. """Fermeture du fil debitmetre"""
  96. self.lance = False
  97. class ServeurVoc(Thread):
  98. """serveur UDP dedie au chat vocal"""
  99. def __init__(self, port):
  100. """création du serveur vocal"""
  101. Thread.__init__(self)
  102. self.serveur_lance = False
  103. self.connectes = []
  104. self.port = port
  105. def creer(self):
  106. """creation d'une entree serveur UDP"""
  107. try:
  108. self.socket = socket(AF_INET, SOCK_DGRAM)
  109. self.socket.bind(('', self.port))
  110. retour = "Le serveur vocal ecoute a present sur le port {}".format(self.port)
  111. self.serveur_lance = True
  112. self.start()
  113. except IOError, e:
  114. if e.errno==10048:
  115. retour = "Une instance du serveur est déja lancée"
  116. logging.error(retour)
  117. else:
  118. retour = "Echec de lancement du serveur"
  119. logServeur.error(retour)
  120. finally:
  121. logServeur.info(retour)
  122. return retour
  123. def nouveauClient(self, adresse):
  124. """ajoute un nouveau client a la liste des connectes"""
  125. self.connectes.append(adresse)
  126. retour = "Nouveau client connecte: {})".format(adresse)
  127. logServeur.info(retour)
  128. return retour
  129. def supprimerClient(self, adresse):
  130. """ajoute un nouveau client a la liste des connectes"""
  131. self.connectes.remove(adresse)
  132. retour = "Client deconnecte: {})".format(adresse)
  133. logServeur.info(retour)
  134. return retour
  135. def run(self):
  136. """boucle de fonctionnement du serveur"""
  137. self.socket.setblocking(0)
  138. while self.serveur_lance:
  139. data = ""
  140. #on recoit les donnees envoyees
  141. try:
  142. data, adresse = self.socket.recvfrom(1024)
  143. try:
  144. dM.reception(len(data))
  145. except:
  146. pass
  147. except IOError, e:
  148. if e.errno == 10035:
  149. #pas de donnee recue
  150. pass
  151. elif e.errno == 10022:
  152. logServeur.debug("erreur de reception {}".format(adresse))
  153. sleep(0.02)
  154. elif e.errno == 10054:
  155. self.connectes = []
  156. logServeur.warning("reinitialisation de la liste des connectes")
  157. else:
  158. logServeur.error("erreur {}".format(e.errno))
  159. #self.supprimerClient()
  160. if len(data) > 0:
  161. if not adresse in self.connectes:
  162. #l'adresse n'est pas dans la liste: on l'ajoute aux connectes
  163. self.nouveauClient(adresse)
  164. for dest in self.connectes:
  165. if dest != adresse:
  166. #le client ne recoit pas ses propres donnees
  167. self.socket.sendto(data , dest)
  168. try:
  169. dM.envoi(len(data))
  170. except:
  171. pass
  172. #tmp = "{} -> {}".format(len(data), adresse)
  173. def stop(self):
  174. """Fermeture du serveur"""
  175. self.serveur_lance = False
  176. self.socket.close()
  177. retour = "Serveur vocal ferme"
  178. logServeur.info("Fermeture du serveur vocal")
  179. return retour
  180. class FilClient(Thread):
  181. """fil de connexion client au serveur TCP"""
  182. def __init__(self, outils, cnn):
  183. """creation du fil"""
  184. Thread.__init__(self)
  185. self.cnn = cnn
  186. self.pseudo = "inconnu"
  187. self.idClient = "00"
  188. self.outils = outils
  189. self.connecte = False
  190. def creer(self):
  191. """initialise la connexion avec le client"""
  192. self.connecte = True
  193. #on genere un id pour lui
  194. self.idClient = self.outils.genererID()
  195. #on ajoute son adresse reseau a la liste des connectes
  196. connectes[self.idClient] = self.cnn
  197. logServeur.info("Client {} connecte - Id {}".format(self.getName(), self.idClient))
  198. #on demarre le fil de reception
  199. self.start()
  200. def run(self):
  201. """echange avec le client"""
  202. self.cnn.setblocking(0)
  203. while self.connecte:
  204. #reception du message client
  205. try:
  206. msgClient = self.cnn.recv(1024)
  207. try:
  208. dM.reception(len(msgClient))
  209. except:
  210. pass
  211. tampon.append((self.getName(), msgClient))
  212. except IOError, e:
  213. if e.errno == 10035:
  214. #aucun message recu
  215. pass
  216. else:
  217. if self.connecte:
  218. logServeur.warning("serveur : erreur reception")
  219. self.stop()
  220. def stop(self):
  221. """ferme le fil client"""
  222. self.connecte = False
  223. #fermeture de la connexion avec le client:
  224. #on supprime le client de la liste des connectes
  225. sleep(0.01)
  226. try:
  227. del connectes[self.idClient]
  228. except:
  229. logServeur.warning("Impossible de retirer {} de la liste des connectes".format(self.idClient))
  230. #on libere son identifiant
  231. self.outils.libererID(self.idClient)
  232. self.cnn.close()
  233. logServeur.info("Client {} deconnecte ({})".format(self.pseudo, self.getName()))
  234. # Le fil se termine ici
  235. class Serveur(Thread):
  236. """serveur TCP pour chat, echange de fichiers ou d'objets..."""
  237. def __init__(self, port):
  238. """creation du serveur"""
  239. self.serveur_lance = False
  240. Thread.__init__(self)
  241. self.port = port
  242. self.outils = OutilsCo()
  243. self.filsOuverts = {}
  244. def traitement(self, nomClient, msg):
  245. """traite le message selon son type et son/ses destinataire(s)"""
  246. client = self.filsOuverts[nomClient]
  247. if len(msg) >= 6:
  248. nature = msg[0:2]
  249. emet = msg[2:4]
  250. dest = msg[4:6]
  251. try:
  252. contenu = msg[6:]
  253. except:
  254. contenu = ""
  255. if dest == "sa":
  256. #message pour le serveur, on ne le retransmet pas
  257. if nature == "ci":
  258. #le client informe de sa connexion et demande un identifiant
  259. client.pseudo = contenu
  260. pseudos[client.idClient] = client.pseudo
  261. logServeur.info("connexion: {} -> {} ({})".format(client.getName(), client.pseudo, client.idClient))
  262. client.cnn.sendall("cisa"+client.idClient)
  263. for cnn in connectes.values():
  264. if cnn != client.cnn:
  265. msg = "ccsatc{}{}".format(client.idClient, client.pseudo)
  266. cnn.sendall(msg)
  267. sleep(0.001)
  268. try:
  269. dM.envoi(len(msg))
  270. except:
  271. pass
  272. if nature == "cd":
  273. #client informe de sa deconnexion
  274. for cnn in connectes.values():
  275. if cnn != client.cnn:
  276. msg = "cdsaac{}".format(emet)
  277. cnn.sendall(msg)
  278. try:
  279. dM.envoi(len(msg))
  280. except:
  281. pass
  282. client.stop()
  283. elif nature == "f0":
  284. #client demande un identifiant pour un envoi de fichier
  285. msg = "f0sa{}{}".format(client.idClient, self.outils.genererID())
  286. client.cnn.sendall(msg)
  287. elif nature == "f1":
  288. #client signale au serveur qu'il peut liberer l'identifiant du fichier
  289. self.outils.libererID(contenu)
  290. elif dest == "tc":
  291. #message pour tous les clients, emetteur compris
  292. for cnn in connectes.values():
  293. cnn.sendall(msg)
  294. try:
  295. dM.envoi(len(msg)+1)
  296. except:
  297. pass
  298. elif dest == "ac":
  299. #message pour tous les clients excepte l'emetteur
  300. for cnn in connectes.values():
  301. if cnn != client.cnn:
  302. cnn.sendall(msg)
  303. try:
  304. dM.envoi(len(msg)+1)
  305. except:
  306. pass
  307. elif dest == "sv":
  308. #message pour le serveur vocal
  309. logServeur.debug("pour serveur vocal: {}".format(contenu))
  310. #else:
  311. elif dest in connectes:
  312. #message pour un client unique (mj ou autre)
  313. connectes[dest].sendall(msg)
  314. else:
  315. logServeur.warning("Erreur : format du message illisible -> {}".format(msg))
  316. def creer(self):
  317. """demarre le serveur"""
  318. """ try:"""
  319. self.cnn = socket(AF_INET, SOCK_STREAM)
  320. self.cnn.bind((hote, self.port))
  321. self.cnn.listen(5)
  322. retour = "Le serveur ecoute a present sur le port {}".format(port)
  323. self.serveur_lance = True
  324. self.start()
  325. """except:
  326. retour = "Impossible de demarrer le serveur"""
  327. """finally:
  328. logServeur.info(retour)
  329. """
  330. return self.serveur_lance, retour
  331. def run(self):
  332. """boucle de fonctionnement"""
  333. self.cnn.setblocking(0)
  334. while self.serveur_lance:
  335. try:
  336. #tant que le serveur est lance, il reste disponible a de nouvelles connexions
  337. cnnClient, adresse = self.cnn.accept()
  338. #on cree un nouveau fil pour le nouveau client
  339. client = FilClient(self.outils, cnnClient)
  340. #on ajoute ce fil à la liste pour pouvoir le fermer
  341. self.filsOuverts[client.getName()] = client
  342. client.creer()
  343. except IOError, e:
  344. if e.errno == 10035:
  345. #si pas de nouvelles connexions, on traite les messages en attente:
  346. #le serveur lit les 3 premiers car indiquant la longueur du message ('drapeaux' non compris)
  347. #puis les retire du message avant le traitement
  348. try:
  349. nomClient, txt = tampon[0]
  350. msgClient = txt
  351. if len(msgClient) >= 9:
  352. while len(msgClient) > 0:
  353. lg = int(msgClient[0:3]) + 6
  354. self.traitement(nomClient, msgClient[3:lg+3])
  355. msgClient = msgClient[lg+3:]
  356. tampon.remove((nomClient, txt))
  357. txt = msgClient = ""
  358. except IndexError:
  359. #la liste tampon est vide
  360. pass
  361. else:
  362. logServeur.error("serveur : erreur attente clients")
  363. break
  364. def stop(self):
  365. """ferme le serveur"""
  366. self.serveur_lance = False
  367. #for client in connectes.keys():
  368. for client in self.filsOuverts.values():
  369. if client.connecte:
  370. client.cnn.sendall("sdsatc")
  371. client.stop()
  372. sleep(0.1)
  373. self.cnn.close()
  374. logging.info("Fermeture du serveur TCP")
  375. #### fil principal ####
  376. if __name__ == "__main__":
  377. # si lancement direct:
  378. #debitmetre pour mesurer l'emission
  379. #dM = DebitMetre(taille_paquet)
  380. #dM.affichage = False
  381. s = Serveur(port)
  382. s.creer()
  383. sVoc = ServeurVoc(port)
  384. sVoc.creer()
  385. #dM.start()
  386. system("pause")
  387. s.stop()
  388. sVoc.stop()
  389. #dM.stop()
  390. logServeur.info("- Fin -")
  391. sleep(0.1)