vendredi 30 mai 2014

Réalisation d’un programme émetteur-récepteur en langage C avec des sockets

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.