| @@ -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 | 14-bibliothèques-01/index | ||||
| 15-fichiers-et-données-binaires/index | 15-fichiers-et-données-binaires/index | ||||
| 16-interpréteur-interactif/index | 16-interpréteur-interactif/index | ||||
| 17-sockets/index | |||||