| @@ -0,0 +1,128 @@ | |||
| Exemple | |||
| ======= | |||
| Pour ce premier exemple, le client et le serveur vont tourner sur la même machine: la vôtre! | |||
| Le processus du serveur tournera dans un premier terminal, et le processus du client dans un autre. | |||
| Pour les différencier, on peut utiliser la variable `sys.ps1`. | |||
| Étape 1 | |||
| ------- | |||
| Ouvrez deux terminaux, lancez dans chacun d'eux la commande `python3` sans arguments, puis tapez:: | |||
| >>> import sys | |||
| >>> sys.ps1 = "(serveur) >>> " | |||
| dans le premier, et:: | |||
| >>> import sys | |||
| >>> sys.ps1 = "(client) >>> " | |||
| dans le second. | |||
| Vous devriez obtenir le résultat suivant:: | |||
| (serveur) >>> | |||
| :: | |||
| (client) >>> | |||
| Dans chacun d'eux, définissez les variables ``IP`` et ``PORT``:: | |||
| (serveur) >>> IP = "127.0.0.1" | |||
| (serveur) >>> PORT = 3000 | |||
| :: | |||
| (client) >>> IP = "127.0.0.1" | |||
| (client) >>> PORT = 3000 | |||
| L'adresse IP ``127.0.0.1`` est spéciale et désigne votre propre machine. | |||
| Le PORT 3000 est un port arbitraire | |||
| Étape 2 | |||
| ------- | |||
| Dans le REPL du serveur, créez une socket du type 'AF_INET' et 'SOCK_STREAM', puis appelez | |||
| ``bind()`` avec le tuple (IP, PORT) [#f1]_ :: | |||
| (serveur) >>> IP = "127.0.0.1" | |||
| (serveur) >>> PORT = 3000 | |||
| (serveur) >>> import socket | |||
| (serveur) >>> s_serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||
| (serveur) >>> s_serveur.bind((IP, PORT)) | |||
| Ensuite, appelez ``s_serveur.listen(0)``: cela permet à votre serveur d'accepter des connections:: | |||
| (serveur) >>> s_serveur.listen(0) | |||
| Enfin, appelez ``s_serveur.accept`: cette méthode retourne un tuple qu'on note souvent ``con, addr``:: | |||
| (serveur) >>> con, addr = s_serveur.accept() | |||
| Cette fois ci, vous devriez constater que le processus du serveur est *bloqué*: l'invite de commande ne s'affiche | |||
| pas - en effet, le serveur est en attente d'une connexion par le client | |||
| Étape 3 | |||
| ------- | |||
| De la même façon que pour le serveur, créez une socket du même type côté | |||
| client:: | |||
| (client) >>> IP = "127.0.0.1" | |||
| (client) >>> PORT = 3000 | |||
| (client) >>> import socket | |||
| (client) >>> s_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||
| Ensuite, *connectez* la socket client à la socket serveur:: | |||
| (client) >>> s_client.connect((IP, PORT)) | |||
| Comme par magie, vous devriez voir le processus **dans l'autre terminal** reprendre son exécution: | |||
| les deux processus Python sont donc bien en train de *communiquer* | |||
| Étape 4 | |||
| ------- | |||
| Vous pouvez maintenant utiliser les méthodes ``send()`` et ``recv``, respectivement avec l'objet | |||
| ``con`` côté serveur, et ``s_client`` côté client pour envoyer et recevoir des messages entre | |||
| les deux processus. | |||
| Notez que ``send`` prend des ``bytes`` en arguments et renvoie le nombre d'octets envoyés, | |||
| et que ``recv()`` prend un nombre d'octets à lire. | |||
| On peut envoyer des message du client vers le serveur:: | |||
| (client) >>> s_client.send(b"Bonjour") | |||
| 7 | |||
| :: | |||
| (serveur) >>> con.recv(7) | |||
| b'Bonjour' | |||
| Et du serveur vers le client:: | |||
| (serveur) >>> con.send(b"Comment va ?") | |||
| 12 | |||
| :: | |||
| (client) >>> s_client.recv(12) | |||
| b'Comment va ?' | |||
| .. rubric:: notes | |||
| .. [#f1] Il existe des sockets de plusieurs type et avec des comportements différents. AF_INET et SOCK_STREAM sont | |||
| les plus courants. | |||
| @@ -0,0 +1,14 @@ | |||
| Chapitre 17 - Sockets | |||
| ===================== | |||
| Ce chapitre n'est pas à proprement parler un cours sur le langage | |||
| Python, mais contient des éléments qui nous servirons à bâtir | |||
| un projet à long terme : fabriquer un site Web ! | |||
| .. toctree:: | |||
| :maxdepth: 1 | |||
| introduction | |||
| exemple | |||
| protocoles | |||
| @@ -0,0 +1,28 @@ | |||
| Introduction | |||
| ============= | |||
| Clients et serveurs | |||
| ------------------- | |||
| Dans le chapitre 15, nous avons parlé des données binaires et évoqué le | |||
| fait que cela permettait à nos programmes Python d'intéragir avec | |||
| l'extérieur, notamment via le système de fichiers. | |||
| Il existe dans la librairie standard une autre façon pour Python de | |||
| communiquer via *le réseau* : il s'agit du module ``socket``. | |||
| Une communication sur le réseau Internet implique: | |||
| * Un *client* qui va faire des *requêtes* | |||
| * Un *serveur* qui va renvoyer des *réponses* au client | |||
| * Une *adresse* du serveur utilisée par le client | |||
| Dans notre cas, cette adresse est un *tuple*, composé de deux éléments: | |||
| une *adresse IP* et un *port*. | |||
| .. image:: /img/client-serveur.png | |||
| En général: | |||
| * Le *client* et le *serveur* sont sur des machines différentes | |||
| * Il n'y a qu'un seul processus qui est capable d'écouter sur un port donné | |||
| @@ -0,0 +1,145 @@ | |||
| Protocoles | |||
| ========== | |||
| Le problème | |||
| ------------ | |||
| Notez que ``send()`` et ``recv()`` sont très basiques: | |||
| * On peut appeler ``send()`` plusieurs fois d'affilée | |||
| * On peut aussi appeler ``recv()`` alors que le client n'a encore rien envoyé, | |||
| le serveur va juste bloquer en attendant le prochain message du client. | |||
| * Si le client envoie 3 octets et que le serveur utilise ``recv(10)``, il n'y | |||
| a pas d'erreur | |||
| * Si le client envoie 10 octets et que le serveur utilise ``recv(3)``, il n'y | |||
| a pas non plus d'erreur, et il faut appeler ``recv()`` une deuxième fois | |||
| (avec 7 par exemple) pour récupérer le message en entier | |||
| Protocoles | |||
| ----------- | |||
| Ainsi, si on veut que deux machines s'échangent des messages via Internet, il faut | |||
| convenir d'un **protocole** - qui parle en premier, quel genre de message peut-il | |||
| envoyer? Il faut aussi convenir d'une façon de communiquer la *taille* des réponses. | |||
| Le protocole HTTP | |||
| ----------------- | |||
| Le protocole HTTP est relativement simple. On peut le vérifier en essayant | |||
| de se connecter à l'adresse ``(93.184.216.34, 80)``, et envoyant les messages | |||
| suivants - à l'heure où j'écris ces lignes, l'IP ci-dessus correspond au site | |||
| ``http://example.com`` :: | |||
| import socket | |||
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |||
| s.connect(("93.184.216.34", 80)) | |||
| s.send(b"GET / HTTP/1.1\r\n") | |||
| s.send(b"Host: example.com\r\n") | |||
| s.send(b"\r\n") | |||
| message = s.recv(351).decode() | |||
| print(message) | |||
| Résultat: | |||
| .. code-block:: text | |||
| HTTP/1.1 200 OK | |||
| Age: 514391 | |||
| Cache-Control: max-age=604800 | |||
| Content-Type: text/html; charset=UTF-8 | |||
| Date: Sun, 01 Mar 2020 15:57:23 GMT | |||
| Etag: "3147526947+ident" | |||
| Expires: Sun, 08 Mar 2020 15:57:23 GMT | |||
| Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT | |||
| Server: ECS (bsa/EB18) | |||
| Vary: Accept-Encoding | |||
| X-Cache: HIT | |||
| Content-Length: 1256 | |||
| <!doctype html> | |||
| Notez qu'on a appelé `.decode()` sur le message reçu du serveur - on dit que HTTP | |||
| est un protocole de *texte*. | |||
| Notez que la réponse du serveur rappelle le nom du protocole ``HTTP/1.1`` et répond avec | |||
| de nombreuses lignes contenant une *clé* et une *valeur* séparé par des deux-points | |||
| On appelle cela des *en-têtes* (ou *headers*). L'un d'eux contient la taille totale du | |||
| message: ``Content-Length: 1256``. | |||
| Après le bloc de headers, on voit le *corps* de la réponse: quelque chose qui commence | |||
| par ``<!doctype html>``. | |||
| On peut lire la suite du message:: | |||
| suite = s.recv(1000).decode() | |||
| print(suite) | |||
| .. code-block:: text | |||
| <html> | |||
| <head> | |||
| <title>Example Domain</title> | |||
| <meta charset="utf-8" /> | |||
| <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||
| <style type="text/css"> | |||
| body { | |||
| background-color: #f0f0f2; | |||
| margin: 0; | |||
| padding: 0; | |||
| font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; | |||
| } | |||
| div { | |||
| width: 600px; | |||
| margin: 5em auto; | |||
| padding: 2em; | |||
| background-color: #fdfdff; | |||
| border-radius: 0.5em; | |||
| box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02); | |||
| } | |||
| a:link, a:visited { | |||
| color: #38488f; | |||
| text-decoration: none; | |||
| } | |||
| @media (max-width: 700px) { | |||
| div { | |||
| margin: 0 auto; | |||
| width: auto; | |||
| } | |||
| } | |||
| </style> | |||
| </head> | |||
| <body> | |||
| <div> | |||
| <h1>Example Domain</h1> | |||
| <p>This domain is for use in illustrative examples in documents. You may use this | |||
| domain in literature without prior coordination or asking for permission.</p> | |||
| <p><a href="https://www.iana.org/domains/example">More information...</a></p> | |||
| </div> | |||
| </body> | |||
| </html> | |||
| Si maintenant vous allez sur ``https://example.com`` avec un navigateur Web, et cliquez sur | |||
| "Afficher le code source de la page", vous devriez voir exactement le contenu ci-dessus. | |||
| Cela prouve que: | |||
| * Il y a un serveur qui écoute sur l'adresse IP de example.com, sur le port 80 | |||
| * Ce serveur comprend le protocole HTTP | |||
| * Un navigateur ne fait rien d'autre que: | |||
| * envoyer des requêtes HTTP vers un serveur | |||
| * interpréter le texte retourné et l'afficher | |||
| * On peut facilement coder à la fois des *clients* et des *serveur* HTTP en Python, juste avec le module socket. | |||
| @@ -33,3 +33,4 @@ remarques. | |||
| 14-bibliothèques-01/index | |||
| 15-fichiers-et-données-binaires/index | |||
| 16-interpréteur-interactif/index | |||
| 17-sockets/index | |||