| The Walrus : Les sockets
C/C++ démystifiés. |
| --
Introduction |
Beaucoup de monde parlent des sockets en C/C++ comme
étant quelque chose de vraiment difficile. Je dois avouer que le manque
de documentation sur l'internet cause cette réaction. J'ai beaucoup
cherché sur le sujet et mes résultats ont étés
plutôt déçevants. J'ai trouvé quelques documents que
je me permet de citer, comme référence ou bien pour
complémenter celle-ci. J'ai aussi fait le choix de l'écrire en
français parce qu'il n'y a aucune documentation sur les sockets en
français. Enfin, très peu.
Cette documentation n'est pas
complète. Il manque beaucoup de théorie sur la
réseautique, les couches TCP/IP, les *host byte order*, les socket DGRAM
et beaucoup d'autre choses. Avec ceci, vous devriez être en capable de
faire des sockets en utilisant le language C/C++, mais pas de les maitriser.
|
| -- Qu'est-ce qu'un
socket? |
Pour communiquer entre deux applications ou
ordinateurs, il vous faut un téléphone, un socket. Un socket est
attaché à un port ( une porte au sens imagé ). Vous ouvrez
votre porte et attendez qu'un colis sois acheminé à celle-ci. Une
fois le colis reçu, vous pouvez soit envoyer un autre colis ou bien
fermer la porte, le port. Vous devez initialiser le socket, faire un lien avec
le port, attendre pour un paquet, et fermer le socket. Pour avoir plus
d'informations sur les protocoles de transfert TCP/IP ou bien sur les socket en
général, voir la section Références.
|
| -- Utilisation du
socket |
Créer un socket en C/C++ est relativement
simple, contrairement à ce que la majorité du monde pensent. La
création d'un socket est faite par une chaîne de commandes. Pour
commencer, il faut initialiser WSAStartup(). Un groupe de variables seront
aussi nécéssaire pour faire cela. Bon, assez de bavardage et
passons au code lui-même.
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
}
winsock2.h, la librairie que vous vous
servirez avec les sockets. Certaines personnes choisissent la librairie de la
version 1, winsock.h. Dépendant de vos besoins. Si vous choisissez la
version 1, prenez bien soin de choisir la librairie "wsock32.lib" au lieu de
"ws2_32.lib".
WSADATA WSAData, l'initialisation d'une variable
WSADATA. La variable va être utilisée pour le démarrage de
WSAStartup(). Elle n'aura pas d'autre utilisation, généralement.
WSAStartup(MAKEWORD(2,0), &WSAData), ce qui dis à votre ordinateur
que vous allez utiliser des sockets. Il y a deux paramètres à se
rappeller, MAKEWORD(2,0) la version de winsock que vous désirez utiliser
Ça peut varier, dépendant des #include que vous aurez choisis. Si
vous avez choisi d'utiliser winsock 1 (winsock.h), remplacez le par
MAKEWORD(1,0). Le deuxieme paramètre à se rappeller,
&WSAData, vraiment simple. Vous y mettez la variable de type WSADATA que
vous avez définis plus haut. À la fin de votre programme, il est
préférable de nettoyer votre WSA, en faisant WSACleanup();
Attention, ne pas faire de WSACleanup() tant que vous n'aurez pas
terminé avec les sockets, sinon vous devrez initialiser WSAStartup() une
deuxième fois.
Votre winsock est maintenant initialisé.
Maintenant,il suffit de créer le socket lui-même.
SOCKET sock;
SOCKADDR_IN sin;
sin.sin_addr.s_addr = inet_addr("127.0.0.1");
sin.sin_family = AF_INET;
sin.sin_port = htons(4148);
sock = socket(AF_INET,SOCK_STREAM,0);
bind(sock, (SOCKADDR *)&sin, sizeof(sin));
Et voilà. Votre socket est
initialisé. Notez par contre que celui-ci est initialisé pour
vous connecter sur vous-même au port 4148. L'utilité est donc
nulle, sauf si vous avez un serveur à ce port-ci. Voyez la section
"Exemples de codes" pour avoir des exemples plus pertinents.
SOCKET
sock, initialisez une variable du type SOCKET qui sera utilisée pour
définir le socket. SOCKADDR_IN sin, le struct du SOCKADDR contient les
informations techniques du socket. Par exemple, .sin_addr.s_addr, qui
définis l'addresse du server. Si vous codez un serveur, vous n'avez pas
à définir d'addresse, vous utiliseriez donc:
sin.sin_addr.s_addr = htonl(INADDR_ANY);
.sin_family, la *famille* du socket, le type
si on veut. Pour l'internet, les programmeurs utilisent
généralement AF_INET. Le dernier paramètre du struct
à définir, .sin_port. Le port sur lequel vous voulez vous
connecter ou bien écouter. htons(4148) pourrait être
remplacé par htons(23) si vous voulez le port telnet, etc. Pour
d'avantage d'explications sur les fonctions htons, htonl, noths, nothl, je vous
suggère de voir 'The beej guide' référencié dans la
section "Références".
socket(AF_INET, SOCK_STREAM, 0),
la création du socket en tant que tel. Le 1er paramètre est la
famille du socket, comme vous l'avez configuré au par avant dans la
structure du SOCKADDR_IN. AF_INET dans ce cas-ci. La 2ième option,
SOCK_STREAM, c'est le type de socket. Il existe aussi SOCK_DGRAM, dont je
parlerai plus loin dans le texte. Les SOCK_STREAM ouvrent une conn entre les 2
ordinateurs directe et pourra ensuite envoyer les paquets que vous
désirez, tandis que le SOCK_DGRAM envoie un paquet directement à
la destination sans faire d'accept() ou de connect().
bind(sock,
(SOCKADDR *)&sin, sizeof(sin));, la commande qui va attacher votre socket
directement au port et à l'adresse que vous avez défini dans le
struct SOCKADDR_IN. Trois paramètres à retenir ici, sock, le
socket que vous avez initialisé plut tôt. La structure
SOCKADDR_IN, la deuxième chose à retenir, la structure que vous
aurez définis plus haut. Et finalement, la taille, sizeof(sin).
Maintenant votre chemin se divise en deux choix, le serveur ou le client. Si
vous voulez faire un serveur, Vous devrez faire une boucle qui accept() les
connections. Dans ce cas, c'est un petit peu plus compliqué.
listen(sock, 0);
int val = 0;
while(1)
{
val = accept(sock, (SOCKADDR *)&csin, sizeof(csin))
if(val != INVALID_SOCKET)
{
// Fonctions à éxécuter sur le socket.
}
}
Ok, procédons étape par
étape. listen(sock, 0), listen va écouter le port sur le socket.
La 1ere valeur, sock, est le socket sur lequel le listen() écoutera. La
2eme valeur, le BACKLOG. Le nombre maximum de connections qui seront
écoutées en même temps. La variable int val sera
utilisée pour prendre la valeur de retour du accept(). accept(sock,
(SOCKADDR *)&sin, sizeof(csin)), la fonction qui va nous permettre
d'accepter une connection. Une autre fonction très simple, 1ere valeur:
le socket. la 2eme valeur est votre SOCKADDR_IN que vous avez crée pour
prendre les informations du client connecté sur votre serveur. Et
finalement, la dernière valeur, sizeof(sin), la grosseur totale.
J'ai rajouté un if() pour vérifier si le socket est
accepté, si quelqu'un est connecté, et si il l'est, il va
effectuer les fonctions() que vous aurez mis au préallable dans le if(),
par exemple un send().
Maintenant, passons au deuxième choix,
le client. Rien de plus simple. Il n'y à qu'a faire un connect().
connect(sock, (SOCKADDR *)&sin, sizeof(sin))
connect, relativement semblable à
accept(). Ces fonctions sont presque égales puisque la seule
différence, le &sin. Vous mettez *VOTRE* SOCKADDR_IN au lieu de
celui du client dans le accept().
Bravo, maintenant vous êtes
connectés. Mais qu'est-ce que je vais faire a mon socket, maintenant que
je suis connecté?! Nous y arrivons.
|
| -- Commandes reliées au
sockets. |
Les commandes reliées au socketing les plus
utilisées sont send(), recv(), sendto(), recvfrom(), closesocket(),
shutdown(), getpeername(), gethostname().
send() Send, la commande
pour envoyer une string au client ou au serveur. Tellement simple
d'utilisation. send(socket, message, grosseur, 0); Le 1er paramètre
étant le socket, le 2eme étant le message à envoyer. Notez
qu'un simple \n en socketing est \r\n, si vous ne faites pas de \r\n, le
serveur/client ne le considérera pas comme un retour de chariot. La
grosseur, sizeof(message). Le dernier paramètre ne vous sera
probablement jamais utile, vous marquerez donc 0. Exemple: send(sock,
"Hello world!\r\n", 14, 0);
recv() Recv est une autre commande
très simple. En fait elle est presque identique au send(). recv(socket,
buffer, grosseur, 0); Les paramètres sont identiques au send() sauf
qu'au lieu d'envoyer une string, vous le stockez dans une variable, le buffer.
Exemple: recv(sock, buff, sizeof(buff), 0);
sendto() Avant de
faire du SOCK_DGRAM, je suggère fortement de commencer par le
SOCK_STREAM. C'est plus simple pour commencer, et de toutes façons c'est
ce qui est le plus utilisé. L'art d'envoyer un paquet à un IP
particulier, sans avoir à se connect()er. Initialisez votre WSAStartup()
et vous êtes prêts à l'utiliser. Il est TRÈS
important de noter que lors de la création du socket(), il faut
préciser SOCK_DGRAM et non pas SOCK_STREAM. sendto(socket, message,
longueur, 0, sin, sizeof(sin));. Le premier paramètre, le socket
lui-même. Ensuite, vous tapez votre message, la string à
être envoyée, qui peut aussi bien être un buffer
stocké dans une variable, suivi de la longueur du message. Vous
continuerez avec un nouveau SOCKADDR_IN qui aura les informations de la
destination que vous aurez programmé à l'avance. Terminez cela
avec un sizeof(sin). Remplacez la variable sin par celle du SOCKADDR_IN, bien
sur.
recvfrom() La fonction recvfrom est presque identique
à sendto(). Vous avez besoin d'utiliser SOCK_DGRAM lors de la
création du socket(), comme pour le sendto(). recvfrom(socket, message,
longueur, 0, sin, sizeof(sin)); Le sin sera celui du client qui vous aura
envoyé un paquet. Notez que vous devrez configurer le sin comme si vous
feriez un serveur régulier. Voir plus haut pour plus d'informations.
closesocket() L'art de fermer un socket d'une façon facile
et propre. Pour résumer cette fonction en une ligne,
closesocket(socket).
shutdown() C'est exactement comme un close(),
mais vous avez beaucoup plus de contrôle avec cette fonction. Par
exemple, vous pouvez bloquer le flux reçu, envoyé ou les deux.
Deux paramètres à retenir, le 1er, le socket. Et le
deuxième, un int 0 a 3.
0 = Les recv ne seront plus acceptés
1 = Les send ne seront plus acceptés
2 = Ni les send, ni les recv ne seront acceptés, comme un close().
getpeername() Cette fonction,
relativement utile, sert à savoir qui est connecté sur vous, des
infos sur le client. Vous devez créer un SOCKADDR_IN pour stocker les
informations, par contre. L'utilisation de cette fonction va comme-ci:
getpeername(socket, sin, sizeof(sin)); Le sin étant le nouveau
SOCKADDR_IN crée pour les besoins de la cause.
gethostname() Même principe que getpeername() en plus simple.
gethostname(*hostname, sizeof(hostname)); *hostname étant un array
stockant le *hostname* de votre propre machine.
|
| -- Exemples de
codes. |
Voici un client simple pour se connecter à irc.
Notez que les recv() et les send() pour s'enregistrer et pouvoir l'utiliser
comme un client ne sont pas incorporés.
#include
#pragma comment(lib, "ws2_32.lib")
void main()
{
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
SOCKET sock;
SOCKADDR_IN sin;
char *buffer = new char[255];
/* Tout est configuré pour se connecter sur IRC, haarlem, Undernet. */
sock = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_addr.s_addr = inet_addr("62.250.14.6");
sin.sin_family = AF_INET;
sin.sin_port = htons(6667);
connect(sock, (SOCKADDR *)&sin, sizeof(sin));
recv(sock, buffer, sizeof(buffer), 0);
closesocket(sock);
WSACleanup();
}
Maintenant, voici un serveur simple qui envoie
un Hello world! a quiconque se connecte.
#include
#pragma comment(lib, "ws2_32.lib")
void main()
{
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
SOCKET sock;
SOCKET csock;
SOCKADDR_IN sin;
SOCKADDR_IN csin;
sock = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_family = AF_INET;
sin.sin_port = htons(23);
bind(sock, (SOCKADDR *)&sin, sizeof(sin));
listen(sock, 0);
while(1)
{
int sinsize = sizeof(csin);
if((csock = accept(sock, (SOCKADDR *)&csin, &sinsize)) != INVALID_SOCKET)
{
send(csock, "Hello world!\r\n", 14, 0);
}
}
}
Ces code sources sont vraiment basiques, mais
ils pourront toujours vous aider pour voir la structure d'un code C/C++
utilisant les socket. Avec ces instruments et quelques autre documents sur les
sockets, vous devriez être capable de maitriser les principes de bases et
beaucoup plus. Je vous suggère fortement de jeter un oeil sur la section
référence pour plus d'informations sur le sujet en
général.
|
| --
Références |
Voici une liste de références dont je me
suis servi pour apprendre les sockets et pour vous les expliquer. Certains sont
complèts, d'autre non. Ils sont tous de bonne qualité, par
contre. Notez qu'ils sont en anglais, malheureusement ils sont en anglais.
"Beej's guide to network programming"
- http://www.ecst.csuchico.edu/~beej/guide/net/
Un des guides les plus interessant sur l'internet. Relativement complet.
Toutefois, le tutoriel touche à l'environnement UNIX inclusivement. Il
est bien de noter que les sockets en UNIX ne sont pas plus compliqués
qu'en Windows, je dirais même plus simple. Quelques changements de
librairies, enlevez les WSA* et vous avez des socket UNIX.
"BSD Sockets: A Quick And Dirty Primer"
- http://world.std.com/~jimf/papers/sockets/sockets.html
Encore là, nous avons un joli tutoriel, qui explique simplement comment
utiliser les sockets, pour BSD. Mais encore très utile pour Windows, si
vous faite les changements appropriés.
"RFC1180: A tutorial to TCP/IP"
- http://www.faqs.org/rfc/rfc1180.txt
Voici un texte sur le TCP/IP. Ce n'est pas totalement complet, il
manque l'historique du TCP/IP et certaines autres choses, mais en général,
c'est relativement complet. Un tutorial sur le TCP/IP compacté en 30 pages.
Je suggère aussi de regarder faqs.org pour d'autre documentations complètes.
|
| -- Disclaimer |
Cette documentation n'est pas protégée.
Elle n'a aucun *copyright*. Je fais toutefois appel à votre intelligence
et votre éthique pour ce qui concerne les droits d'utilisation du
document. Je suggère fortement de référencier le document
complet et mon nom. C'est une question de respect mutuel et d'intelligence.
Aussi, je tiens à remercier un bon ami à moi, QDerf,
pour l'aide qu'il m'a apporté, même si ça pouvait
parraître moindre.
Si vous avez, pour quelconque raison, besoin
de me rejoindre par courriel, vous pouvez envoyer à
The Walrus. Je ne répondrai pas
aux questions du style "Comment je fais un virus?" ou bien "Ou je pourrais
trouver des textes sur les sockets?". Si vous m'envoyez un courriel, soyez
pertinent. Commentaires et suggestions bienvenues. -
The Walrus
|
|
| Écrit par: The Walrus,
2002 |
|
|