Introduction:
Dans
ce monde où augmente toujours les connectivités par réseaux, beaucoup de
programmeurs se retrouvent à écrire des programmes qui communiquent en temps
réel, à réaliser des applications réseau, à programmer un serveur de chat, ou
d’authentification et dans ces différents contextes cités, l’appel aux sockets
se révèle indispensable.
Qu’est-ce qu'une socket?
Une
socket est un identifiant unique représentant une adresse sur le réseau. Des
processus peuvent s’y connecter pour y envoyer des données ou pour en recevoir.
Les processus devront adopter un protocole de communication afin d’assurer un
échange de données cohérent. L’adresse de la socket est spécifiée par le nom de
l’hôte sur lequel on la crée et le numéro de port. Dans notre projet, nous
allons nous appuyer sur le protocole TCP-IP qui se chargera d’assurer le
transport des données (paquets) entre les processus serveur (récepteur) et
client (émetteur).
But du TP :
L’objectif du TP est la réalisation de deux programmes:
- Un programme émetteur : Client
- Un programme récepteur : Serveur
Ces deux programmes s’échangent les informations avec
le protocole ( Send / Wait). Lors de l’établissement des programmes, il a fallu
faire un choix car il existe deux types de sockets :
· Les sockets de flux ("Stream
Sockets ») elles sont en mode connecté (TCP), le message est reçu d’un seul bloc.
· Les sockets de paquets ("Datagram
Sockets") elles sont en mode non connecté (UDP), comme dans le cas du
courrier, le destinataire reçoit le message petit à petit (la taille du message est indéterminée) et de façon
désordonnée.
On
a choisit d’utiliser les sockets de flux qui utilisent un protocole appelé
TCP/IP l’acronyme TCP veut dire « The Transmission Control Protocol », il
s’assure que les données arrivent séquentiellement et sans erreurs, et
l’acronyme "IP" signifie "Internet Protocol" il traite
uniquement le routage Internet.
Voici
le schéma d’une communication en mode connecté.
Programme
récepteur : Serveur
C’est
un programme qui attend puis accepte les connexions entrantes, et qui propose
un certain service à d’autres programmes.
Initialisation de Winsock
Au
début de chaque programme qui utilise des sockets, il faut appeler la fonction WinScock WSAStartup()
erreur=WSAStartup(MAKEWORD(2,2),&initialisation_win32);
Le premier argument est le numéro de version de la
librairie WinSock utilisé : dans notre cas on utilise la version.
La fonction retourne un entier 0 si l’initialisation
s’est bien passée, sinon on peut obtenir des informations sur l’erreur qui
s’est produite en appelant la fonction WSAGetLastError(), qui retourne le code
d’erreur correspondant à la cause de l’échec, ceci grâce à la ligne
du code dessous :
printf("\nDesole, je ne peux pas initialiser Winsock
du a l'erreur : %d %d",erreur, WSAGetLastError());
Fonction socket () ouverture d’une socket :
Pour recevoir des connexions, il faut d.abord installer une
socket. Pour ce faire, il faut en créer un pour écouter les connections.
La fonction socket () est utilisée pour ça.
id_de_la_socket=socket
(AF_INET, SOCK_STREAM, 0);
Le premier argument est la famille d’adresse utilisée par la
socket dans notre cas, il s’agit du format Internet, spécifié par le nom
AF_INET. Un autre argument qu’il faut remplir est le type de socket, comme nos
données seront transportées comme une chaîne de caractères (TCP) on utilisera
les SOCK_STREAM. Enfin, mettre protocol à "0".
La fonction
bind ()
Après
avoir créé une socket, il faut lui donner une adresse à écouter, La fonction
bind() est utilisée pour ça . Une adresse de socket Internet est spécifiée en
utilisant la structure sockaddr_in, qui contient les champs spécifiant la famille d’adresse, soit l’adresse
IP et le numéro de port pour la socket.
struct
sockaddr
{
unsigned
short sa_family; /* famille d’adresse, AF_xxx
char
sa_data[14]; /* 14 octets d’adresse de protocole
};
struct sockaddr_in
{
short int sin_family; /* Famille d’adresse
unsigned
short int sin_port; /* Numéro de Port
struct in_addr sin_addr; /* Adresse Internet
unsigned
char sin_zero[8]; /* Même taille que struct sockaddr
};
struct in_addr
{
unsigned long s_addr;
};
Un
pointeur vers cette structure est passé en argument à la fonction bind(),qui a
besoin d’une adresse. Comme les sockets prétendent gérer plus qu’une famille d’adresse,
il faut mettre le pointeur vers la structure sockaddr_in dans un pointeur de
structure sockaddr pour éviter les warnings lors de la compilation.
Bind
(id_de_la_socket,(struct sockaddr*) &information_sur_la_source, sizeof(information_sur_la_source)
);
La fonction
listen () :
La
fonction listen() est utilisée pour donner le nombre maximum de requêtes en
attente (généralement jusqu’à 5 maximum) avant de refuser les connexions.
La
fonction listen prend deux arguments : listen(id_de_la_socket, backlog );
- Id_de_la_socket
: un entier, un descripteur de fichier socket issus de l’appel système
socket().
- Backlog
: est le nombre de connections autorisées dans la file entrante.
Comme
les autres appels systèmes, listen() retournera -1 en cas d’une erreur.
La fonction
accept () :
Après
avoir créé une socket pour recevoir des appels, il faut accepter les appels
vers cette socket. La fonction accept() est utilisée pour se faire. Accept()
renvoie une nouvelle socket qui est connectée à celle qui appelle.
id_de_la_nouvelle_socket=accept(id_de_la_socket,(struct
sockaddr*)&information_sur_la_source,&tempo);
La
fonction accept() prend 3 arguments :
- Id_de_la_socket
: descripteur de socket à écouter avec listen().
- un
pointeur vers une struct sockaddr. où on trouve l’information concernant la connexion
entrante (et il est possible de déterminer quel hôte appelle sur quel port).
- tempo
est une variable entière locale qui doit contenir sizeof(struct sockaddr_in) avant
que son adresse soit passée à accept().
Aussi,
accept() retourne -1 si une erreur arrive.
Les
fonctions send () and recv () :
Si une connexion entre deux stream sockets est établie, on peut
alors envoyer des données entre elles, les fonctions send() and recv() sont là
pour ça.
Dans notre programme, le rôle du serveur est de recevoir le
message envoyé par le client, donc c’est la fonction recv () qui nous
intéresse.
L’appel de recv() :
int
recv(int sockfd, void *buf, int len, unsigned int flags);
nombre_de_caractere=recv(id_de_la_nouvelle_socket,buffer,1515,0);
La fonction prend quatre arguments :
- le descripteur de socket sur lequel s’effectue la lecture,
- buffer est le tampon où l’information est lue.
- la longueur maximale du tampon,
- flags peut encore être mis à 0.
recv() retourne le nombre d’octets lu dans le tampon, où -1 s’il y
a une erreur.
Programme émetteur : Client
Un client
est un programme qui demande une connexion, se connecte au serveur, généralement
pour lui demander de faire quelque chose.
Le
programme exactement similaire à celui de la réception du coté serveur, aura
besoin de:
- Initialiser
un Winsock
- Créer
un socket à l’aide de la fonction socket ()
- Attribuer
une adresse à la socket grâce à la fonction connect().
- Envoyer
une chaine de caractère à l’aide de la fonction send()
La fonction connect () :
La
fonction connect () permet de se connecter à une socket qui attend les appels. Elle
appelle un port particulier sur un ordinateur particulier et renvoie une socket
connectée à travers laquelle les données peuvent passer.
int connect(int sockfd, struct sockaddr
*serv_addr, int addrlen);
La
fonction connect() prend 3 arguments :
- Le
descripteur de socket, tel que retourné par l’appel à socket().
- Un
pointeur de la structure struct sockaddr contenant le port de destination et l’adresse
IP.
- addrlen sera sizeof(struct sockaddr).
la
valeur de retour de connect()est un entier, il est égal à 1 en cas d’erreur.
La fonction send () :
L'appel système send() :
int
send(int sockfd, const void *msg, int len, int flags);
la fonction send() prend 3 arguments :
- un descripteur de socket par lequel les données sont envoyées.
- msg est un pointeur vers les données à envoyer.
- len est la longueur des données en octets.
- flags prend la valeur 0.
La fonction shutdown() permet de contrôler le processus de clôture
de la socket, elle permet de couper la communication dans un sens précis, ou
les deux.
Prototype:
int shutdown(int sockfd, int how);
sockfd est le descripteur de fichier socket à fermer, et how est à
choisir parmi:
·
0 -
Réceptions interdites
·
1 -
Envois interdits
·
2 -
Réceptions et Envois interdits (comme close())
shutdown() retourne 0 en cas de succès, et -1 en cas d’erreur.
Pour quitter proprement le wincsock ouvert avec la commande
Wsaratup, on utilise la function: WSACleanup().
Implémentation
Vous trouverez dans le fichier ci-joint l’exécutable ainsi que le
code des programmes réalisés avec le langage C, testé avec succès avec le
compilateur MinGW de l’IDE DevC++ sous Windows XP.