Introduction au socket 1.2

Transcription

Introduction au socket 1.2
Langage C : Introduction aux Sockets
Environnement Windows 32 bits
Ce tutorial a pour but de vous expliquer le fonctionnement des sockets sous Windows, leur utilisation
est légèrement différente sous les systèmes UNIX et dérivés (Linux, BSD, MAC OS) néanmoins le
principe de fonctionnement est le même et seuls quelques lignes sont à revoir pour un code porté.
1 – Qu’est-ce qu’un socket ?
C’est certainement la question que vous vous posez depuis le début de ce document, je vais vous
l’expliquer maintenant.
Un socket est ce qui est utilisé en programmation pour envoyer et/ou recevoir des données sur le
réseau. Cela sert donc de fil conducteur à votre programme pour communiquer avec un programme
distant.
2 – Comment programme-t-on avec les Sockets ?
Je n’expliquerais ici que le côté client des sockets.
Initialisation de Winsock.
Pour commencer il va falloir inclure deux directives préprocesseur supplémentaires celle-ci sont les
suivantes :
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
Cela indique qu’on va utiliser la bibliothèque winsock2.h (on est sous Windows ne l’oublions pas)
couplée à la bibliothèque ws2_32.lib. Vous n’avez pas à vous préoccuper de ces lignes faites
simplement un copier/coller de celles-ci dans votre programme.
Sachez tout de même pour votre culture personnelle qu’il existe winsock.h dans ce cas on utilisera
alors la lib wsock32.lib mais ceci est sans grande importance.
Pour finir d’initialiser Winsock il vous reste deux lignes à implémenter dans votre fonction :
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
Ces lignes-ci, vont pour la première déclarer une variable WSADATA nommée WSAData, le type
WSADATA est un type définit dans le fichier winsock2.h. La seconde ligne sert à démarrer la
bibliothèque Winsock. A la fonction WSAStartup(), on passe deux arguments, le premier
MAKEWORD(2,0) indique que vous aller utiliser la version 2 de Winsock vous pouvez remplacer (2,0)
par (1,0) si vous utiliser la version 1 de winsock (suivant votre include au début). Le deuxième
argument n’est autre que l’adresse de votre variable de type WSADATA crée à la ligne du dessus.
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.
Note : vous remarquez qu’en début de programme utilisant les sockets on appelle la fonction
WSAStartup() sachez qu’il est recommandé en fin de programme de libérer votre WSA en appelant
la fonction WSACleanup() mais uniquement lorsque vous en aurez fini avec les sockets sinon il vous
faudra reprendre à la ligne WSADATA WSAData; pour réutiliser un socket plus loin.
Arrivé à ce point nous avons terminé l’initialisation de Winsock rien de tel qu’un petit récapitulatif
donc.
Voici ce que vous devez avoir à ce moment là :
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
}
Notez simplement que dans mon cas cela se situe dans la fonction main de mon programme mais
vous pouvez faire de même dans n’importe quelle fonction.
Initialisation de votre socket
Nous y voilà donc, nous venons d’initialiser la bibliothèque, il nous faut à présent définir notre socket.
Pour cela il va falloir déclarer la chose suivante :
SOCKET mon_socket;
SOCKADDR_IN sin;
sin.sin_addr.s_addr
sin.sin_family
sin.sin_port
= inet_addr("127.0.0.1");
= AF_INET;
= htons(80);
Ouh là ! ca en fait des choses ! Rien de bien compliquer vous allez voir. La première ligne crée
mon_socket du type SOCKET, ca ça va ce n’est pas trop dur. Ensuite on crée une structure sin de type
SOCKADDR_IN. Ceci va contenir tous les paramètres de votre socket :
sin.sin_addr.s_addr : correspond à l’adresse sur laquelle votre socket va se connecter notez qu’on
utilise ici la fonction inet_addr() qui converti l’adresse IP en xxx.xxx.xxx.xxx en une variable de type
long. Pensez à remplacer 127.0.0.1 par l’IP du serveur sur lequel vous voulez vous connecter
j’indiquerais plus loin comment on fait pour faire une requête DNS.
sin.sin_family : ce paramètre indique le type de votre socket. Le plus souvent on utilisera AF_INET
pour des sockets Internet. Pour votre culture là encore il existe plein de type de socket comme les
X.25 qui ne sont plus utilisés aujourd’hui.
sin.sin_port : la on spécifie simplement le port de connexion du socket. Pour cela ne pas oublier que
le numéro du port doit passer par la fonction htons(). je vais simplement vous expliquer pourquoi, à
l’intérieur de votre machine les nombre sont transférés octets par octets en commençant par celui de
poids le plus faible sur un réseau c’est le contraire voilà donc pourquoi on utilise la fonction htons().
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.
Votre socket est maintenant paramétré il suffit de le créer. Je ne comprends plus là on a fait notre
socket plus haut il faut recommencer ? Non rassurer-vous je vais vous montrer ce qu’on fait à
présent. Pour commencer voici les lignes de codes :
mon_socket = socket(AF_INET,SOCK_STREAM,0);
bind(mon_socket, (SOCKADDR *)&sin, sizeof(sin));
La fonction socket() crée le socket à proprement dit. On lui passe trois arguments AF_INET (ou le type
que vous avez mit dans sin_family), SOCK_STREAM on dit que ce sera un socket de type STREAM
sachez qu’il existe des SOCK_DGRAM qui correspondent aux paquets UDP vous en aurez donc déduit
qu’avec les SOCK_STREAM on fait des paquets TCP. Enfin le dernier paramètre ne vous en souciez pas
mettez simplement 0.
La deuxième fonction assigne votre socket au port défini dans votre structure SOCKADDR_IN, vous lui
passez trois arguments le nom de votre socket, l’adresse de votre structure SOCKADDR_IN « castée »
en SOCKADDR d’où le (SOCKADDR *) et le dernier argument la taille de votre structure. Il n’est pas
nécessaire d’appeler bind() si notre programme est client cela dit je la mets quand même ça ne coûte
rien.
Nous y sommes donc votre socket étant crée il vous faut maintenant le connecter. Pour cela la
fonction connect() est adaptée.
connect(mon_socket, (SOCKADDR *)&sin, sizeof(sin));
Cette fonction est très simple à utiliser, vous lui passez en argument votre socket, l’adresse de votre
structure SOCKADDR_IN « castée » en SOCKADDR et la taille de votre structure.
Il semblerait qu’à partir de maintenant les préliminaires soient terminés et que vous puissiez envoyer
ou recevoir des données via votre socket. Ce n’est pas magnifique çà ? Avant de vous apprendre
comment on envoie et on reçoit des données via un socket je me suis dit un petit qu’un résumé
commenté ne sera pas de trop.
#include <winsock2.h>
//On demande d’inclure Winsock2
#pragma comment(lib, "ws2_32.lib") //On dit qu’on va avoir besoin de la lib
int _tmain(int argc, _TCHAR* argv[])
{
// On crée nos variables
SOCKET mon_socket;
SOCKADDR_IN sin;
//On initialise Winsock
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
//On crée une varable WSAData
//On initialise Winsock
//On défini nos paramètres
sin.sin_addr.s_addr
= inet_addr("127.0.0.1"); //Ne pas oublier inet_addr
sin.sin_family
= AF_INET;
sin.sin_port
= htons(80); //Le port de notre socket avec htons()
//On crée le socket !
mon_socket = socket(AF_INET,SOCK_STREAM,0);
bind(mon_socket, (SOCKADDR *)&sin, sizeof(sin));
//On connecte notre socket
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.
connect(mon_socket, (SOCKADDR *)&sin, sizeof(sin));
//on n’oublie pas cette fonction, enfin ce n’est pas obligatoire
WSACleanup();
}
N’oubliez pas en C vous devez définir toutes vos variables avant tout appel de fonctions sinon je
compilateur vous fera n’importe quoi ! Ceci est un inconvénient du C par rapport au C++.
Les commandes essentielles
Après avoir crée votre socket il peut être intéressant de lui faire passer des données non ?
Voici pour démarrer la commande pour envoyer des données :
send(mon_socket, paquet, strlen(paquet), 0);
Cette commande envoie donc sur mon_socket, la variable paquet. On précise dans les arguments le
socket, la chaîne à envoyer, la longueur de la chaîne que l’on envoie - un petit strlen(paquet) - et le
dernier argument là encore mettez simplement 0.
Note : Pour utiliser strlen() pensez à inclure string.h à votre projet !
#include <string.h>
Et pour recevoir :
recv(mon_socket, tampon, sizeof(tampon), 0);
Cette commande permet donc de lire sur votre socket. Cette fonction prend en argument le nom du
socket à employer, le tampon de réception, la taille de votre tampon (sizeof(tampon) et on en parle
plus) et le dernier paramètre pour changer on met 0 .
Qu’est ce que le tampon ?
En programmation socket vous êtes amené à stocker des chaînes lors de l’exécution de votre
programme. Dans le monde informatique le mot utilisé pour cela est tampon, buffer en anglais. Dans
notre cas tampon ne sera qu’un tableau de char d’une taille assez grande. Si vous ne voulez pas trop
vous souciez de ça mettez un tampon d’1Ko, ça marchera bien dans la plupart des cas.
A la fin on ferme notre socket :
closesocket(mon_socket) ;
Une fois votre échange avec le serveur terminé vous devez fermer votre socket avec la fonction la
plus simple qui soit certainement closesocket() qui prend en argument votre socket.
Ouf on en a terminé vous savez à peu près tout ce qu’il y a à savoir pour vous débrouiller avec les
sockets.
Pour conclure ce tutoriel je vous donne un petit exemple de code utilisant les sockets :
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
//On demande d’inclure Winsock2
#pragma comment(lib, "ws2_32.lib") //On dit qu’on va avoir besoin de la lib
int _tmain(int argc, _TCHAR* argv[])
{
// On crée nos variables
SOCKET mon_socket;
SOCKADDR_IN sin;
char tampon[1024] = {0};
//On initialise Winsock
WSADATA WSAData;
WSAStartup(MAKEWORD(2,0), &WSAData);
//On crée une varable WSAData
//On initialise Winsock
//On défini nos paramètres
sin.sin_addr.s_addr
= inet_addr("212.27.48.4");
sin.sin_family
= AF_INET;
sin.sin_port
= htons(25);
//On crée le socket !
mon_socket = socket(AF_INET,SOCK_STREAM,0);
bind(mon_socket, (SOCKADDR *)&sin, sizeof(sin));
//On connecte notre socket
connect(mon_socket, (SOCKADDR *)&sin, sizeof(sin));
recv(mon_socket, tampon, sizeof(tampon),0);
printf("%s\n", tampon);
closesocket(mon_socket);
//on n’oublie pas cette fonction, enfin ce n’est pas obligatoire
WSACleanup();
}
Ce programme se connecte sur un serveur SMTP et affiche le message de connexion à
l’écran. Notez qu’aucune vérification n’est faite ce programme n’est donc qu’une
présentation grossière de ce qui pourrait être un client SMTP.
Requête DNS
Si tu me demande smtp.free.fr je te réponds 212.27.48.4….
Jusque là je vous ai expliqué comment vous connecter avec une adresse IP. Il est tout de même
préférable lorsqu’on veut que dans notre programme l’utilisateur soit amené à paramétrer la
connexion qu’il puisse rentrer un nom de serveur et non pas une IP. Je vais donc vous donner la ou
plutôt les fonctions permettant de résoudre un nom en une IP et de façon transparente pour
l’utilisateur. Ce paragraphe expose des notions assez avancés en ce qui concerne la syntaxe du code.
Voici le prototype de la fonction utilisée :
struct hostent *gethostbyname(const char *name);
On a donc une fonction gethostbyname() qui prend en argument une chaîne comportant le nom du
serveur. C’est donc en appelant la fonction gethostbyname(« www.google.fr ») que vous obtiendrez
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.
euh… un pointeur vers une structure hostent aie là ca se complique. Voici le prototype de la structure
hostent :
struct hostent
{
char
*h_name;
char
**h_aliases;
int
h_addrtype;
int
h_length;
char
**h_addr_list;
};
#define h_addr h_addr_list[0]
Qu’y-t-il là dedans ?
h_name : n’est autre que le nom que vous avez entré.
h_aliases : sont des noms alternatifs de l’hôte.
h_addrtype : correspond au type d’adresse (AF_INET là encore).
h_length : la longueur de l’adresse en octet.
h_addr_list : un tableau comportant toutes les IPs retournées pour un même hôte.
h_addr : un pointeur vers la première adresse dans h_addr_list.
Pour la suite je ne vais retenir que h_addr et h_name si vous souhaitez vous pouvez toujours faire
des tests avec les autres membres de cette structure. Petite précision la fonction renvoie un pointeur
NULL si la résolution échoue très facile donc de faire un contrôle d’erreur.
Que diriez-vous d’un code exemple pour vous montrer comment faire pour utiliser ces fonctions ?
struct hostent *host ;
host = gethostbyname( “www.google.fr”) ;
if (host != NULL)
{
printf(“%s\n”, host->h_name);
printf(“%s\n”, inet_ntoa(*((struct in_addr *)host->h_addr)));
}
else
exit(1);
Ici on déclare donc en premier lieu un pointeur vers structure hostent nommée host. On affecte
ensuite à notre pointeur la sortie de la fonction qui rappelez-vous renvoie un pointeur. Je me suis
permis à la ligne suivante de vous inclure le test si jamais la résolution à échouée. Si notre pointeur
ne vaut pas la valeur NULL, on affiche à l’écran le nom d’hôte et l’adresse IP du serveur.
On voit donc qu’il n’y a rien de bien compliqué pour le nom d’hôte, on prend dans notre structure
host la valeur pointée par le pointeur h_name.
Pour ce qui est de l’adresse IP en revanche c’est très différent, celle-ci est stockée sous la forme d’un
char* dans la structure et de plus non formatée xxx.xxx.xxx.xxx. Afin de l’afficher sous ce format on
utilise la fonction inet_ntoa() qui elle n’accepte que les struct in_addr. On doit donc avant
d’appeler cette fonction « caster » notre h_addr en (struct in_addr*) ce qui nous donne donc
((struct in_addr *)host->h_addr) et on dé-référence le pointeur avec l’étoile d’où *((struct
in_addr *)host->h_addr) . Ce format est accepté par inet_ntoa() qui nous affiche l’adresse IP
compréhensible par l’être humain.
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.
Si vous n’avez pas tout compris ce n’est pas grave mais sachez appliquer ceci, c’est le plus important !
Dernière question : Et si je veux mettre cette IP sur mon socket ?
Alors là il n’y a pas plus simple !
Souvenez-vous :
sin.sin_addr.s_addr
= inet_addr("212.27.48.4");
On met l’IP sous forme compréhensible par l’homme, il nous suffit donc de remplacer l’IP écrite en dur
par le résultat de notre fonction soit :
sin.sin_addr.s_addr = inet_addr(inet_ntoa(*((struct in_addr *)host->h_addr)));
Au passage on enlève les guillemets...
Voilà comme toute bonne chose ce tutoriel a une fin. J'espère qu'il vous aura plut N’hésitez
pas à me faire part de vos remarques concernant ce document sur mon blog :
http://camille.caskroot.com/blog.
5 – Remerciement :
Brian "Beej" Hall: http://www.ecst.csuchico.edu/~beej/guide/net/
The Walrus: http://c.developpez.com/WalrusSock/
©2007 – Camille Sauvaget – http://camille.caskroot.com
Vous êtes priés de ne pas copier et/ou diffuser ce document sans ma permission.

Documents pareils