User Tools

Site Tools


rx3:index

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rx3:index [2017/03/24 09:23] – [Serveur UDP] orelrx3:index [2024/03/18 15:06] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== Réseau L2 Info : Socket en Python 3 ====== ====== Réseau L2 Info : Socket en Python 3 ======
  
-http://dept-info.labri.fr/~thibault/enseignements.html#Reseau 
  
-==== Client UDP ====+Un peu de documentation : 
 + 
 +  * https://docs.python.org/3/howto/sockets.html 
 +  * https://docs.python.org/3/library/socket.html 
 +  * https://docs.python.org/3/library/select.html 
 +  * https://docs.python.org/3/library/threading.html 
 +  * https://docs.python.org/3/library/stdtypes.html#str  
 + 
 + 
 +==== Tips en Python ==== 
 + 
 +== String vs Byte-Array == 
 + 
 +Les fonctions de la famille send()/recv() ne manipulent pas des string classiques, mais des byte-array :  
 + 
 +<code python> 
 +string = "coucou"     # string classique        
 +byterray = b"coucou"  # byte array (notez le prefixe b) 
 +sock.send(bytearray) 
 +</code> 
 + 
 +Pour convertir une string en byte-array (et inversement), vous pouvez utiliser les fonctions suivantes : 
 + 
 +<code python> 
 +bytearray = "coucou".encode() 
 +string = b"coucou".decode() 
 +</code> 
 + 
 +Pour convertir une string "bonjour la terre" en tableau de mots [ "bonjour", "la", "terre" ], la fonction split() est votre amie : 
 + 
 +<code python> 
 +sentence = "bonjour la terre" 
 +words = sentence.split(" "  # avec " " comme séparateur   
 +</code> 
 + 
 +A l'inverse, si vous disposez d'un tableau de mots, vous pouvez utiliser join() pour effectuer l'opération inverse : 
 + 
 +<code python> 
 +sentence = " ".join(words) 
 +</code> 
 + 
 +==Un mot sur les classes...== 
 + 
 +Un mot maintenant sur les classes en Python... Plûtot qu'un long discours, voici un exemple de classe : 
 + 
 +<code python> 
 +class Dog: 
 +  
 +    def __init__(self, name, age): 
 +        self.name = name 
 +        self.age = age 
 + 
 +    def addYear(self): 
 +        self.age += 1 
 + 
 +    def print(self): 
 +        print("I am a dog. My name is {} and I am {} years old.".format(self.name,self.age)) 
 +         
 +</code> 
 +         
 +A la manière d'une structure, une classe encapsule des données (name et age). De plus, la classe permet de définir des méthodes (addYear et print), qui sont des fonctions permettant de manipuler les données de la classe (au travers la variable self, qui désigne l'objet courant).  
 + 
 +Un objet est une instance particulière de la classe, que l'on peut créer en appellant le constructeur Dog(). Ce constructeur appelle implcitement la méthode __init__ de la classe. Dans l'exemple ci-dessous, deux objets de type Dog sont créés et affectés aux variables milou et rantanplan.     
 + 
 +<code python> 
 +milou = Dog("milou", 4) 
 +rantanplan = Dog("rantanplan", 5) 
 +milou.print() 
 +rantanplan.addYear() 
 +rantanplan.print() 
 +</code> 
 + 
 +Pour aller plus loin : https://docs.python.org/fr/3.5/tutorial/classes.html 
 + 
 +==Un mot sur le flux TCP !== 
 + 
 +Considérons deux sockets TCP s1 et s2,  correspondant à une connexion déjà établie entre un client et un serveur. La gestion du flux TCP implique un mécanisme de bufferisation à l'envoi et à la réception.  
 + 
 +<code python> 
 +# client             # server 
 +s1.send(b"A"       msg1 = s2.recv(1000) # recv 1000 bytes max 
 +s1.send(b"B"       msg2 = s2.recv(1000) # recv 1000 bytes max 
 +</code> 
 + 
 +On souhaiterait récupérer b"A" dans msg1 et b"B" dans msg2. Hélas, il y a quelques difficultés liés à la notion de flux (ou stream) en TCP. 
 + 
 +Tout d'abord, le mécanisme de bufferisation à l'envoi va faire en sorte, qu'il n'y aura en fait qu'un seul message b"AB" envoyé sur le réseau... Pour éviter ce problème, il faut remplacer l'utilisation de la fonction send() par la fonction sendall() ! 
 + 
 +<code python> 
 +# client             # server 
 +s1.sendall(b"A"    msg1 = s2.recv(1000)  
 +s1.sendall(b"B"    msg2 = s2.recv(1000) 
 +</code> 
 + 
 +Côté réception, le mécanisme de bufferisation pose également des difficultés. En effet, comme les deux envois sont très proches, il est fort probable qu'ils soient bufferisés à la réception avant l'appel du premier recv(). Dans ce cas, msg1 contiendra b"AB".  
 + 
 +Une astuce pour contourner ce problème consiste à acquiter les messages : 
 + 
 +<code python> 
 +# client             # server 
 +s1.sendall(b"A"    msg1 = s2.recv(1000) 
 +ack = s1.recv(1000)  s2.send(b"ACK")  
 +s1.sendall(b"B"    msg2 = s2.recv(1000) 
 +ack = s1.recv(1000)  s2.send(b"ACK"
 +</code> 
 + 
 +La fonction recv() étant bloquante, l'envoi du second message b"B" ne pourra avoir lieu qu'après la réception du premier... Ainsi, on reçoit bien les deux messages séparemment comme attendu... Ouf :-)  
 + 
 +==Recv non bloquant== 
 + 
 +Par défaut, la fonction recv() des sockets est bloquante... Plus précisément, la fonction recv() se met en attente jusqu'à la réception effective de données.  Cela peut poser quelques difficultés dans notre jeu, notamment si l'on considère comment communiquer le déplacement des joueurs. En effet, un recv() bloquant côté serveur risque de bloquer indéfiniment la boucle principale du serveur ! Comment faire ? 
 + 
 +Une première solution consiste à utiliser des sockets non bloquante, mais cela implique de gérer une exception dans le cas où il n'y a rien à recevoir ! 
 + 
 +<code python> 
 +import errno 
 +sock = ... 
 +sock.setBlocking(False) 
 +try: 
 +    msg = sock.recv(1000) 
 +except socket.error as e: 
 +    if e.args[0] == errno.EWOULDBLOCK: 
 +        print("nothing received"
 +    else 
 +        print("error:", e) 
 +</code> 
 +         
 +Une autre façon (plus élégante) consiste à utiliser select() avec un timeout de 0 ! 
 + 
 +<code python> 
 +import select 
 +sock = ... 
 +sock.setBlocking(False) 
 +lsock, _, _ = select.select([sock], [], [], 0) 
 +if lsock: 
 +    msg = sock.recv(1000) 
 +else: 
 +    print("nothing received"
 +</code> 
 + 
 +==== Client DayTime en UDP ====
  
 <code python> <code python>
Line 23: Line 162:
  
  
-==== Client TCP ====+==== Client HTTP/GET (TCP====
  
 <code python> <code python>
 #!/usr/bin/python3 #!/usr/bin/python3
-import sys 
 import socket import socket
- 
-HOST = 'www.labri.fr'      
-PORT = 80                  
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-s.connect((HOSTPORT)) +s.connect(("www.perdu.com"80)) 
-s.sendall(b'GET /\r\n\r\n')+s.sendall(b'GET / HTTP/1.1\r\nHost: www.perdu.com\r\nConnection: close\r\n\r\n')
 data = s.recv(1024) data = s.recv(1024)
 s.close() s.close()
Line 40: Line 175:
 </code> </code>
  
-==== Serveur UDP ====+==== Serveur Echo UDP ====
  
 Voici un echo server en version UDP... Voici un echo server en version UDP...
Line 57: Line 192:
 while True: while True:
   reply, addr = s.recvfrom(1500)   reply, addr = s.recvfrom(1500)
-  # if len(reply) == 0: break 
   print (reply)     print (reply)  
   s.sendto(reply, addr)   s.sendto(reply, addr)
 </code> </code>
  
-client ipv4/udp+On utilise //netstat// pour vérifier que son serveur écoute :
  
-  nc --u localhost 7777+  netstat -ulpn 
 +    Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     
 +    udp        0      0 0.0.0.0:7777            0.0.0.0:                          3314/python3        
  
 +
 +  
 +
 +Voici un client netcat ipv4/udp: 
 +
 +  nc -4 -u localhost 7777
 +   coucou
 +   coucou
 ==== Serveur TCP ==== ==== Serveur TCP ====
  
Line 71: Line 215:
  
 <code python> <code python>
-Echo server program+#!/usr/bin/python3
 import socket import socket
 + 
 HOST = ''                 # Symbolic name meaning all available interfaces HOST = ''                 # Symbolic name meaning all available interfaces
-PORT = 50007              # Arbitrary non-privileged port+PORT = 7777               # Arbitrary non-privileged port
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 s.bind((HOST, PORT)) s.bind((HOST, PORT))
-s.listen(1+s.listen(0
-conn, addr = s.accept() +while True: 
-with conn: +    sclient, addr = s.accept() 
-  print('Connected by', addr) +    print('Connected by', addr) 
-  while True: +    while True: 
-    data = conn.recv(1024+        data = sclient.recv(1500
-      if not data: break +        if data == b'' or data == b'\n' : break 
-        conn.sendall(data)+        print(data) 
 +        sclient.sendall(data
 +    print('Disconnected by', addr)         
 +    sclient.close()
 </code>             </code>            
-==== Documentation ==== 
  
-  * https://docs.python.org/3/library/socket.html +==== Serveur TCP (version multithread) ====
-  * https://docs.python.org/3/library/select.html +
-  * https://docs.python.org/3/library/threading.html +
-  * https://docs.python.org/3/library/stdtypes.html#str+
  
 +<code python>
 +#!/usr/bin/python3
 +import socket
 +import threading
  
 +
 +def handle(addr, sclient):
 +    print('Connected by', addr)
 +    while True:
 +        data = sclient.recv(1500)
 +        if data == b'' or data == b'\n':
 +            break
 +        print(data)
 +        sclient.sendall(data)
 +    print('Disconnected by', addr)
 +    sclient.close()
 +
 +
 +HOST = ''
 +PORT = 7777
 +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 +s.bind((HOST, PORT))
 +s.listen(0)
 +while True:
 +    sclient, addr = s.accept()
 +    t = threading.Thread(None, handle, None, (addr, sclient))
 +    t.start()
 +</code>
 +
 +==== Serveur Echo TCP (version select) ====
 +
 +//La version select permet de gérer de multiples clients simultanément.//
 +
 +<code python>
 +#!/usr/bin/python3
 +import socket
 +import select
 + 
 +HOST = ''
 +PORT = 7777
 +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 +s.bind((HOST, PORT))
 +s.listen()
 +l = []
 +l.append(s)
 +m = {}
 +
 +while True:
 +    l2, _, _ = select.select(l, [], [])
 +    for s2 in l2:
 +        # socket server (new client connection)
 +        if s2 == s:  
 +            sclient, addr = s.accept()
 +            l.append(sclient)
 +            m[sclient] = addr
 +            print('Connected by', addr)
 +        # socket client (new client message)
 +        else:        
 +            while True:
 +                data = s2.recv(1500)
 +                print(data)
 +                if data == b'' or data == b'\n' :
 +                    print('Disconnected by', m[s2])
 +                    l.remove(s2)
 +                    s2.close()
 +                    break
 +                s2.sendall(data)                  
 +s.close()
 +</code>
  
  
rx3/index.1490347416.txt.gz · Last modified: 2024/03/18 15:05 (external edit)