diff --git a/cours/source/17-sockets/1.png b/cours/source/17-sockets/1.png new file mode 100644 index 0000000..000f483 Binary files /dev/null and b/cours/source/17-sockets/1.png differ diff --git a/cours/source/17-sockets/exemple.rst b/cours/source/17-sockets/exemple.rst new file mode 100644 index 0000000..b6f6776 --- /dev/null +++ b/cours/source/17-sockets/exemple.rst @@ -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. diff --git a/cours/source/17-sockets/index.rst b/cours/source/17-sockets/index.rst new file mode 100644 index 0000000..067187c --- /dev/null +++ b/cours/source/17-sockets/index.rst @@ -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 diff --git a/cours/source/17-sockets/introduction.rst b/cours/source/17-sockets/introduction.rst new file mode 100644 index 0000000..369c376 --- /dev/null +++ b/cours/source/17-sockets/introduction.rst @@ -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é diff --git a/cours/source/17-sockets/protocoles.rst b/cours/source/17-sockets/protocoles.rst new file mode 100644 index 0000000..156613d --- /dev/null +++ b/cours/source/17-sockets/protocoles.rst @@ -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 + + + + +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 ````. + +On peut lire la suite du message:: + + + suite = s.recv(1000).decode() + print(suite) + +.. code-block:: text + + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.

+

More information...

+
+ + + + +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. + diff --git a/cours/source/img/client-serveur.png b/cours/source/img/client-serveur.png new file mode 100644 index 0000000..aa46792 Binary files /dev/null and b/cours/source/img/client-serveur.png differ diff --git a/cours/source/index.rst b/cours/source/index.rst index fb4e6e7..7e3d0fb 100644 --- a/cours/source/index.rst +++ b/cours/source/index.rst @@ -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