Vous êtes sur la page 1sur 10

Les sockets

Par rks`

www.openclassrooms.com

Licence Creative Commons 7 2.0


Dernière mise à jour le 28/05/2009
2/11

Sommaire
Sommaire ........................................................................................................................................... 2
Les sockets ........................................................................................................................................ 3
Présentation ...................................................................................................................................................................... 3
Communiquer : un problème, deux solutions .............................................................................................................................................................. 3
Les sockets, vue détaillée ........................................................................................................................................................................................... 3
Le protocole TCP ............................................................................................................................................................... 4
Les bases .................................................................................................................................................................................................................... 4
Applications ................................................................................................................................................................................................................. 6
Le protocole UDP .............................................................................................................................................................. 8
En pratique .................................................................................................................................................................................................................. 8
Application ................................................................................................................................................................................................................... 9
Pour finir ............................................................................................................................................................................ 9
Partager ..................................................................................................................................................................................................................... 10

www.openclassrooms.com
Sommaire 3/11

Les sockets

Par rks`

Mise à jour : 28/05/2009


Difficulté : Facile

Ce tutoriel a pour but de vous apprendre à vous servir des sockets en Erlang. Par sockets on désigne l'interface permettant la
communication entre un système unique et un réseau utilisant le protocole TCP/IP. Contrairement à d'autres langages de
programmation (tels que le C) les sockets sont inclus dans la bibliothèque standard du langage, vous n'aurez donc rien de plus à
télécharger ou installer pour vous en servir.

Pour suivre ce tutoriel il est impératif que vous connaissiez le langage Erlang, aucune autre connaissance n'est requise.

À la fin de ce tutoriel vous serez capable de créer un serveur de communication semblable à celui de M@teo21 dans son tutoriel
sur l'utilisation des sockets avec la bibliothèque Qt (notez d'ailleurs qu'il utilise une bibliothèque à part, car en C++ non plus les
sockets ne sont pas fournis avec le langage).
Sommaire du tutoriel :

Présentation
Le protocole TCP
Le protocole UDP
Pour finir

Présentation
Communiquer : un problème, deux solutions

Comme vous le savez certainement, Erlang se présente comme un langage de référence pour tout ce qui touche à la
communication entre processus, que ce soit en local ou en réseau. Pour cela deux options s'offrent à vous :

1. distribuer vos programmes, grâce au système de noeuds offert par Erlang ;


2. utiliser les sockets.

La distribution par l'intermédiaire des noeuds est sans aucun doute un système très puissant, et intéressant. Pour en résumer le
principe : vos processus vont être répartis sur les nœuds que vous spécifiez, et peuvent sans problème communiquer entre
différents noeuds. Là où ça devient intéressant c'est que vous pouvez disséminer vos nœuds un peu partout sur Internet, tout
en gardant la même facilité de conversation entre vos processus.

La deuxième possibilité, l'utilisation des sockets, est la plus accessible dans la plupart des langages de programmation actuels.
C'est ce qui a motivé le choix de cette solution plutôt que de la première pour le sujet du tutoriel. En effet grâce aux sockets vous
pourrez faire communiquer vos programmes avec des programmes écrits dans d'autre langages (par exemple en OCaml ou encore
en Python), alors que le système de nœuds est propre à Erlang.

Les sockets, vue détaillée

Dans ce tutoriel nous allons nous intéresser aux protocoles TCP et UDP. UDP permet aux applications de s'envoyer de courts
messages, sans garanties. C'est-à-dire que vous ne pouvez jamais être sûr qu'un message ne se perdra pas, ou n'arrivera pas en
retard. Utiliser TCP, par contre, vous assure que tous les messages seront envoyés et/ou reçus, tant que la connexion est établie.

www.openclassrooms.com
Les sockets 4/11

En Erlang deux modules sont offerts pour utiliser ces protocoles, ce sont gen_udp et gen_tcp.

Pour en savoir plus sur les sockets, référez-vous à ce tutoriel.

Le protocole TCP
Dans cette partie nous allons voir comment utiliser le module gen_tcp, qui va nous permettre de communiquer en suivant le
protocole TCP.

Les bases

Connexion et déconnexion

La première fonction du module que vous devrez utiliser lorsque vous voudrez vous connecter à un serveur est la fonction
connect, qui va établir la communication avec le correspondant souhaité.

Cette fonction s'utilise ainsi : connect(Host, Port, Options)

Host est une variable qui peut contenir soit une chaîne de caractères, soit un atome, soit une adresse ip. En Erlang les adresses
ip sont représentées sous forme de tuples, je vous invite à consulter la documentation du module inet pour plus d'informations
(erl -man int ).

Port est une variable de type integer, qui contient le numéro du port sur lequel vous voulez ouvrir la connexion. La variable
Options est une liste d'options, qui sont quant à elles sont très nombreuses et diverses. Vous avez par exemple list et binary, qui
servent à déterminer si vous voulez recevoir les données sous formes de listes (donc des chaînes de caractères) ou de binaires.
Une autre option intéressante est le couple {packet, PacketType} , où plusieurs choix s'offrent à vous pour
PacketType :

raw (ou 0) signifie que vous ne souhaitez pas empaqueter les données ;
1, 2 ou encore 4, qui spécifient le nombres d'octets par paquet ;
line, avec cette option le paquet est une ligne terminée par un retour à la ligne (\n) ;
d'autres options qu'il n'est pas nécessaire de détailler ici, si jamais vous voulez plus d'informations référez-vous à la
documentation de la fonction setopts du module inet.

Une fois la tentative de connexion effectuée la fonction va vous renvoyer un couple du type {ok, Socket} si la connexion
a réussi, {error, Raison} sinon.

Quand vous aurez fini de communiquer, il vous faudra fermer la connexion si votre correspondant ne le fait pas pour vous, pour
cela vous avez la fonction close/1 à votre disposition. L'argument attendu est la socket retournée par la fonction connect.

Envoi et réception

Une fois la connexion effectuée, vous pouvez commencer à vous amuser à envoyer des messages et en recevoir.

Pour envoyer un message il faut utiliser la fonction send/2. Elle attend deux arguments, le premier est la socket renvoyée par
connect lors de l'établissement de la connexion. La seconde est un paquet, dont le type est un binaire ou une liste, en fonction
des paramètres de connexion que vous avez choisis.

Pour la réception deux méthodes s'offrent à vous, la première est l'utilisation de la fonction recv/2. En premier paramètre elle
attend la socket, en deuxième la longueur du paquet à recevoir. Cette longueur n'a d'importance que si vous êtes en mode raw. Si
vous mettez cet argument à 0, tous les octets contenus dans le paquet seront réceptionnés, si par contre la valeur est supérieure
à 0, seul un nombre limité d'octets sera reçu, celui fixé.

L'autre méthode possible est d'utiliser la structure receive ... end que vous avez probablement l'habitude d'utiliser
lorsque vous développez des applications concurrentes. Pour distinguer les messages normaux des messages envoyés par le
protocole TCP, il vous faudra filtrer vos messages en suivant le motif {tcp, Socket, Paquet} . L'autre type de message
à repérer est si la connexion a été fermée par votre interlocuteur, vous pouvez vérifier cela grâce au format

www.openclassrooms.com
Les sockets 5/11

{tcp_closed, Socket} .

Exemple

Vous en savez désormais assez pour créer un petit client qui va envoyer une requête HTTP à une adresse donnée et recevoir la
réponse. Voici le code :
Code : Erlang

-module(exemple1_client).
-export([get_url/1]).

get_url(Host) ->
case gen_tcp:connect(Host, 80, [list, {packet, line}]) of
{error, Reason} -> io:format("Erreur: ~w~n", [Reason]);
{ok, Socket} ->
gen_tcp:send(Socket, "GET / HTTP/1.0\r\n\r\n"),
wait_for_answer(Socket, [])
end.

wait_for_answer(Socket, Acc) ->


receive
{tcp, Socket, Packet} ->
wait_for_answer(Socket, [Packet|Acc]);
{tcp_closed, Socket} ->
io:put_chars(lists:reverse(Acc));
_ ->
wait_for_answer(Socket, Acc)
end.

Voici ce que ça donne quand je teste de mon côté :


Secret (cliquez pour afficher)
Code : Console

Erlang (BEAM) emulator version 5.6.3 [source] [async-threads:0] [hipe]


[kernel-poll:false]

Eshell V5.6.3 (abort with ^G)


1> c(exemple1_client).
{ok,exemple1_client}
2> exemple1_client:get_url("www.google.fr").
HTTP/1.0 302 Found
Location: http://www.google.fr/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie:
PREF=ID=b1ad4e2e00926034:TM=1222866614:LM=1222866614:S=-zYQ1v-
uiG6a1_zC;
expires=Fri, 01-Oct-2010 13:10:14 GMT; path=/; domain=.google.com
Date: Wed, 01 Oct 2008 13:10:14 GMT
Server: gws
Content-Length: 218
Connection: Close

<HTML><HEAD><meta http-equiv="content-
type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.fr/">here</A>.
</BODY></HTML>
ok
3>

www.openclassrooms.com
Les sockets 6/11

Écoute

Maintenant que vous savez créer un client, intéressons-nous à la création d'un serveur. Contrairement au client, un serveur ne se
charge pas d'effectuer la connexion. Tout ce qu'il a à faire c'est d'attendre que quelqu'un essaye de se connecter, et ensuite
décider s'il accepte la connexion ou non.

Pour la phase d'écoute vous devrez utiliser la fonction listen/2, qui en premier argument attend le port sur lequel écouter. En
deuxième argument elle attend une liste d'options semblable à celle de la fonction connect que nous avons vu précédemment.
Cette fonction renverra soit un couple {error, Raison} , soit {ok, ListenSocket} .

Une fois que listen aura fini de s'exécuter, vous utiliserez ListenSocket en association avec la fonction accept/1, qui va elle-même
vous retourner un couple {ok, Socket} , ou bien entendu {error, Raison} en cas d'erreur.

Quand le temps vous manque

Il arrive parfois qu'on veuille laisser un temps limité à une action pour s'effectuer, comme par exemple : « t'as 30 secondes pour te
connecter, si ça prend plus de temps laisse tomber », ou encore « si tu reçois pas de message dans les deux prochaines minutes
ferme la connexion » (oui, parce que je suis persuadé que vous aussi vous parlez à vos programmes :p ).

Pour faire cela on utilise ce qu'on appelle des timeouts. Parmi toutes les fonctions que l'on a vues précédemment, celles
acceptant un timeout sont : connect, accept, et recv. Pour toutes ces fonctions il suffit d'ajouter un argument lors de l'appel, la
durée (en millisecondes) que vous voulez laisser à la fonction pour s'exécuter.

Si jamais vous utilisez la structure receive ... end , vous allez pouvoir utiliser un autre mot clé, à savoir : « after ». La
structure est la suivante :

Code : Erlang

receive
{tcp, Socket, Request} -> foo;
{tcp_closed, Socket} -> bar
after Timeout ->
baz
end

Où Timeout est un entier.

Applications

Un serveur basique

Nous allons désormais nous servir de ce que vous venez d'apprendre pour créer un serveur basique. Basique dans le sens où il
ne va gérer qu'une seule connexion à la fois.

Pour cet exercice je vais vous demander de créer un serveur qui va servir de lien entre le client et un serveur IRC. Ça peut vous
sembler bizarre, mais l'idée m'est venue quand j'ai voulu me connecter à IRC depuis mon lycée. Il est en effet apparu que tous les
ports du réseau sauf quelques ports particuliers étaient bloqués. J'ai donc eu l'idée de créer un programme qui servira de
passerelle entre deux ports.

L'idée c'est de créer un serveur qui attend une connexion sur un port quelconque (mettons le port 8484), et qui une fois la
connexion effectuée va ouvrir une connexion avec un serveur IRC et va ensuite transférer tous les messages que lui envoie le
client vers le serveur et inversement.

Cet exercice est particulièrement intéressant car il va à la fois vous faire créer un serveur, mais aussi un client. Vous mettrez ainsi
en application tout ce que nous venons de voir.

www.openclassrooms.com
Les sockets 7/11

Les informations dont vous avez besoin sont l'adresse du serveur IRC, mettons qu'on veuille se connecter à EpiKnet, ce sera :
irc.epiknet.org. Le port associé étant le port 6667.

Voici le code :

Secret (cliquez pour afficher)


Code : Erlang

-module(serveur).
-export([start/0]).

start() ->
case gen_tcp:listen(8484, [list, {packet, line}]) of
{ok, Listen} ->
{ok, Socket} = gen_tcp:accept(Listen),
gen_tcp:close(Listen),
{ok, Irc} = gen_tcp:connect("irc.epiknet.org", 6667, [list,
{packet, line}]),
loop(Socket, Irc);
{error, Reason} ->
io:format("Erreur: ~s~n", [Reason])
end.

loop(Socket, Irc) ->


receive
{tcp, Socket, Request} ->
gen_tcp:send(Irc, Request),
loop(Socket, Irc);
{tcp, Irc, Request} ->
gen_tcp:send(Socket, Request),
loop(Socket, Irc);
{tcp_closed, Irc} ->
io:format("Connexion à l'hôte perdue.~n"),
gen_tcp:close(Socket);
{tcp_closed, Socket} ->
io:format("Fin de la connexion.~n"),
gen_tcp:close(Irc)
end.

Un serveur parallèle

En testant le code précédent, vous aurez pu remarquer qu'il fonctionne très bien pour la première connexion, mais si vous
souhaitez lancer une deuxième connexion pendant que la première est toujours ouverte, vous allez échouer lamentablement.
Pourquoi cela ? Eh bien car vous avez créé un serveur séquentiel, c'est-à-dire qu'il va effectuer les actions les unes après les
autres. Pour gérer plusieurs connexions en même temps il vous faut développer un serveur parallèle, ainsi nommé parce qu'il
parallélise les actions.

Pour passer d'un serveur séquentiel, comme le précédent, à un serveur parallèle il s'agit de créer un nouveau processus dès que
accept reçoit une nouvelle connexion. Ainsi au lieu de faire :
Code : Erlang

init() ->
{ok, Listen} = gen_tcp:listen(...),
wait_for_connect(Listen).

wait_for_connect(Listen) ->
{ok, Socket} = gen_tcp:accept(Socket),
loop(Socket),
wait_for_connect(Listen).

www.openclassrooms.com
Les sockets 8/11

Il faudra faire :
Code : Erlang

init() ->
{ok, Listen} = gen_tcp:listen(...),
spawn(fun() -> wait_for_connect(Listen) end).

wait_for_connect(Listen) ->
{ok, Socket} = gen_tcp:accept(Socket),
spawn(fun() -> wait_for_connect(Listen) end),
loop(Socket).

Facile non ?

Une fois le serveur parallélisé, il peut potentiellement accepter des milliers de connexions simultanées. Il serait donc
bon de tenir un compteur de connexions, pour fixer une limite maximale de connexions.
Je vous laisse y réfléchir de votre côté. :p

Le protocole UDP
Ce qu'il est important de noter sur UDP, c'est que contrairement au protocole TCP il fonctionne sans connexion. C'est-à-dire que
vous n'avez pas besoin de vous connecter à un hôte pour lui envoyer des messages, vous envoyez des messages à qui vous
voulez, quand vous voulez. C'est aussi pour ça que vous n'avez aucune garantie que vos messages arriveront bien à destination.

Il peut au premier abord sembler inintéressant d'utiliser un tel système de communication, n'apportant aucune garantie. Mais ça
reste néanmoins utilisé dans des applications où l'on a besoin d'envoyer des petits messages très régulièrement, et qu'il importe
peu s'il y en a quelques-uns qui se perdent dans le tas. C'est par exemple le cas des jeux multi-joueurs, à l'image d'Urban Terror,
jeu qui a de nombreux adeptes sur ce site si j'ai bien compris. :) De plus, l'envoi de message est bien plus rapide en UDP qu'en
TCP.

Vous pourrez constater que créer une application utilisant UDP est bien plus facile que d'en créer une avec TCP. En effet, nous
n'avons désormais plus à nous occuper de maintenir la connexion ! :)

En pratique

Tout d'abord, avant de pouvoir recevoir ou envoyer un message, il vous faut ouvrir le port sur lequel se feront vos
communications, pour cela vous disposez des fonctions suivantes :
Code : Erlang

gen_udp:open(Port) -> {ok, Socket} | {error, Raison}


gen_udp:open(Port, Options) -> {ok, Socket} | {error, Raison}

Toutes ces variables vous sont désormais connues, inutile donc de s'y attarder.

Ensuite, la fonction pour envoyer un message se complexifie un peu. Il faudra en effet préciser l'adresse à laquelle vous voulez
envoyer le message, et le port sur lequel l'envoyer. Voyez plutôt :
Code : Erlang

gen_udp:send(Socket, Address, Port, Packet) -> ok | {error, Reason}

Et enfin pour recevoir, vous bénéficiez des même fonctions que précédemment, seulement la valeur renvoyée sera différente, ce
sera en effet un couple de la forme {ok, Tuple} , où Tuple est un triplet de la forme {Address, Port, Packet} . Le

www.openclassrooms.com
Les sockets 9/11

motif pour la structure receive ... end est modifié en conséquence, exemple :
Code : Erlang

receive
{udp, Socket, Adress, Port, Packet} -> foo
end

Notez qu'il vous faudra ensuite fermer le port sur lequel s'effectuent les communications, et ce à l'aide de la fonction close/1.
L'argument à lui passer est la socket retournée par open.

Application

Plutôt que de vous faire créer un logiciel communiquant avec Urban Terror, j'ai décidé qu'il serait plus rapide et facile de créer un
client DAYTIME. L'intérêt de cet exemple c'est qu'il n'y a nul besoin de connaître un protocole, puisque quel que soit le message
envoyé le serveur enverra toujours la même réponse. :D

Si vous n'avez pas lu la RFC, il vous faut simplement savoir que les communications s'effectuent sur le port 13. Vous aurez aussi
probablement envie de tester votre client, pour cela référez-vous à la liste des serveurs daytime. Voilà, vous avez tout ce qu'il
faut ! :)

Au final vous avez :


Code : Erlang

-module(daytime).
-export([ask/1]).

ask(Server) ->
{ok, Socket} = gen_udp:open(8888),
gen_udp:send(Socket, Server, 13, "hello\r\n"),
receive_date(Socket),
gen_udp:close(Socket).

receive_date(Socket) ->
receive
{udp, Socket, _, _, Date} ->
io:put_chars(Date);
_ -> receive_date(Socket)
end.

Pour finir
Voilà, vous savez désormais utiliser les sockets en Erlang !
Et plus particulièrement :

ouvrir une connexion TCP avec gen_tcp:connect/3-4 ;


envoyer un message en TCP avec gen_tcp:send/2-3 ;
recevoir un message en TCP soit avec gen_tcp:recv\2-3 soit avec la structure receive ... end et le motif
adapté : {tcp, _, _} ;
écouter un port dans l'attente d'une connexion, à l'aide des fonctions gen_tcp:listen/2 et gen_tcp:accept/1
;
fermer une connexion avec gen_tcp:close/1 ;
utiliser des timeouts.

Mais aussi :

ouvrir un port en prévision d'une communication UDP avec gen_udp:open/1-2 ;


envoyer un message à l'aide de gen_udp:send/4 ;
recevoir des messages avec gen_udp:recv/2-3 ou receive {udp, _, _, _, _,} -> ... end ;
fermer le port ouvert précédemment avec gen_udp:close/1 .

www.openclassrooms.com
Les sockets 10/11

Voilà, c'est tout ! Si jamais vous avez des questions n'hésitez pas à passer sur les forums !

Un tutoriel signé PHM

Partager

Ce tutoriel a été corrigé par les zCorrecteurs.

www.openclassrooms.com

Vous aimerez peut-être aussi