You are on page 1of 8

Notes TP Programmation Réseau

Adresses de socket

#include <sys/socket.h>
#include <sys/types.h>

struct sockaddr{
unsigned int sa_family;
char sa_data[14];
};

sa_family donne le type de l'adressage:
● AF_INUX: protocoles internes à UNIX,
● AF_INET: protocole internet,
● AF_NS: procole Xerox,
● ...
d'une manière générique, toujours de la forme AF_XXX.

sa_data peut contenir jusqu'à 14 octets d'inforamtions selon le protocole.
Pour le type AF_INET, qui sert ici, on a 2 octets de numéro de port, 4 octets d'adresse IP (cas IPV4)ou 6 octets (cas 
IPV6). Les autres seront mis a zéro.

Adresses de socket internet

#include <sys/socket.h>
#include <sys/types.h>

struct sockaddr_in{
int sin_family;
unsigned int sin_port; //avec convention Big Endian
struct in_addr sin_addr;
unsigned char s[8]; // 8 octets inutiles
};

sin_family =AF_INET,

sin_port contient le numero du port, avec la convention Big endian, toujours utilisée sur les réseaux, mais pas 
forcement en local sur les machines... 
Pour etre sûr que le port et bien dans la forme souhaiter, utliser la fonction htons()qui convertit de l'hote (h) vers 
(to) le reseau (n) les int court (s) ou long (l).

Toujours penser à mettre à zéro les octets qui terminent l'adresse internet, pour éviter les ennuis. En général, on remet 
tout à zéro après la déclaration et avant le remplissage. Pour cela, on utilise la fonction memset(). 

#include <string.h>

memset(&mysockaddr_in,0,sizeof(mysockaddr_in));

Cette structure d'adresse peut être castée sans problème en une structure sockaddr simple si necessaire :(struct
sockaddr *)&mysockaddr_in.
Adresse IPV4, Notation pointée et stockage des entiers...

struct in_addr{
unsigned long int s_addr;
};

s_addr identifie le réseau, les sous­reseaux et la machine.

Correspondance entre numero IP x.y.z.t et s_addr donnée par:
  s_addr= x+y*256+z*256^2+t*256^3.

Passage adresse IP entier ­> notation pointée:

char* inet_ntoa(struct in_addr myin_addr);

Passage adresse IP notation pointée ­> entier:

int inet_aton(const char* IPpointee,struct in_addr *myin_addr);

Cette fonction remplit la structure spécifiée en deuxième argument avec l'entier qui correspont à l'IP en notation pointée 
passée en premier argument.
 Elle renvoie 0 si le premier argument n'a pas la forme d'une IP pointée (peut servir de test pour un if... else...).

Informations sur les machines

#include <netdb.h> // Pour AF_INET


#include <sys/socket.h>
struct hostent{
char * h_name; // nom officiel de la machine
char ** h_aliases // liste des alias, terminée par NULL
int h_addrtype; // type d'adresse: AF_XXX
int h_length; // taille de l'adresse, selon type.
char ** h_addr_list; // liste des adresses si plusieurs
interfaces réseaux
char * h_addr; // IP principale = h_addr_list[0]
};

ATTENTION: Dans le cas d'une adresse AF_INET, le paramètre h_addr est stocké sous la forme de 4 caractère 
ASCII qui correspondent aux entiers de l'IP pointée:  si la machine a pour IP x.y.z.t, alors 
h_addr=ASCII(x)ASCII(y)ASCII(z)ASCII(t).
Passage de l'IP pointée à son codage ASCII:

char * IPpointee;
struct in_addr stockageIP;
char * codageASCII;
if(inet_aton(IPpointee,&stockageIP)!=0){
codageASCII=(char*)(&stockageIP.s_addr);
}

Si Ippointee est une adresse valide, on la stocke sous la forme d'un entier dans une structure in_addr et on caste 
ensuite cet entier en char*. 
Requêtes pour les informations de la machine
 toutes les requêtes ci­dessous nécessitent d'inclure les mêmes fichiers que pour définir la structure hostent.

struct hostent * gethostbyname(const char * nom);

Cette requête renvoie les infos complètes de la machine appellée nom.

struct hostent * gethostbyaddr(const char * myaddr, int lgaddr, int type);

Cette requête renvoie les infos complètes de la machine adressée  par myaddr  où lgaddr represente la longueur de 
l'adresse (lgaddr=sizeof(myaddr)). Le parametre type correspond a type d'adressage utilisé. 

gethostname(char * nom, int lgmaxnom);

Cette requête renvoie le nom de la machine. Le parametre lgmaxnom correspond à la longuer maximale premise pour 
les noms (256 en général).

 Appel système  socket
   

#include <sys/types.h>
#include <sys/socket.h>

int socket(int family, int type,int protocole);

l'entier  family parametre le domaine d'adressage: AF_XXX,
l'entier  type donne le mode de communication:
● SOCK_STEAM: mode connecté au dessus de TCP,
● SOCK_DGRAM: mode déconnecté avec datagrammes au dessu de TCP,
● SOCK_RAW: mode utilisé au dessu de IP.
L'entier protocole donne le protocole. En général il est mis à zéro, valeur par défaut.
Remarque: renvoie un entier que l'on appelle descripteur de fichier de la socket (même rôle que les descripteur de 
fichiers in et out).

 Appel système  bind
   
Cette fonction associe le descripteur de fichier de la socket à une adresse de socket (IP+port) sur laquelle se mettre en 
écoute.

#include <sys/types.h>
#include <sys/socket.h>

int bind(int socketfd, struct sockaddr* myaddr,int lgmyaddr);

 
 Appel système  connect
    (coté client)
   
Les socket étant toujours crées en état déconnecté, après avoir créé une socket et initialisé son adresse avec les 
paramètres d'un serveur, on établit la connection via la commande connect:

#include <sys/types.h>
#include <sys/socket.h>

int connect(int socketfd, struct sockaddr* serv_addr,int lgserv_addr);

renvoie ­1 en cas d'echec de connection.
 Appel système  listen
    (coté serveur)
   
Après avoir crée une socket et initialisé son adresse, on doit mettre le serveur en écoute pour que d'éventuels clients se 
connectent:

#include <sys/types.h>
#include <sys/socket.h>

int listen(int socketfd, int maxlog);

L'entier  spécifie le nombre maximal de personnes que le serveur peut mettre en attente.
 
 Appel système  accept
      (coté serveur)
   
Lorsque le serveur est à l'écoute, et qu'il a suffisamment peu de client pour prendre un client supplémentaire, il peut 
accepter une éventuellement requête de connection:

#include <sys/types.h>
#include <sys/socket.h>

int accept(int socketfd, struct sockaddr* client_addr,int * lgclient_addr);

 enregistre l'adresse du client dans la structure  client_addr  et  la longueur de son adresse dans 
lgclient_addr. Renvoie le flux de la socket socketfd sur le flux de la socket du client.
Si les deux derniers paramètres sont inutiles au serveur, on peut les premplacer par NULL.

ATTENTION: ne pas oublier de caster les structures sockaddr_in en structure sockaddr simple dans l'utilisation 
de bind, connect et accept.

 Emission d'information (Mode connecté  SOCK_STREAM
   ) 

#include <sys/types.h>
#include <sys/socket.h>

int write(int socketfd, char * buffer, int nbbytes);

Envoie dans la socket socketfd le contenu de buffer , l'entier nbbytes est égal à sizeof(buffer) et renvoie 
le nombre de caractères émis dans la socket ou ­1 si échec.

 Reception d'information (Mode connecté  SOCK_STREAM
   ) 

#include <sys/types.h>
#include <sys/socket.h>

int read(int socketfd, char * buffer, int nbbytes);

On lit ce qui arrive de la socket socketfd et on le met en mémoire dans buffer en général de longueur maximale 
nbbytes (256).  Renvoie le nombre de caractères émis ou ­1 si échec.
Remarque: Attention aux retours charriots que l'on peut avoir à supprimer, en particulier si on utilise telnet comme 
moyen de connection coté client:

int nblus=read(int socketfd, char * buffer, int nbbytes);


buffer[nblus-2]='\0';
 Mise en parallèle de processus:  pid,
  fork ... processus père et fils... Rodriguez!
   
Pour pouvoir connecter simultanément des clients, on doit pouvoir dédoubler les processus mis en oeuvre pour 1 client 
dans le programme serveur. On fait donc une fourchette fork()... les deux processus, le père et le fils, utilisent le 
même programme pas n'ont pas le même numéro d'identité processus pid. Selon la valeur de  pid, qui vaut 0 pour le 
processus fils, le programme peut avoir des effets différents. Les deux processus peuvent interagir. Cela peut 
eventuellement poser des problème de programme zombie... (cf dessous). Voici la structure d'une dérivation père­fils:

#include <sys/types.h>
pid_t pid;
pid=fork(); // En cas de réussite, les 2 processus suivent leur
cours. Renvoie pid=-1 si erreur.
if(pid==-1){
/* Gestion du message d'erreur*/
};
if(pid==0){
/* Processus fils */
exit(0);
};
else{
/*Processus père*/
};

 Cela permettra de mettre dans le processus fils la gestion d'un client pendant que le processus pere retourne écouter le 
réseau  pour d'éventuelles autres connections. 
Remarque: Un pid_t est un entier simple. 

Gestion des processus fils devenus zombies
Puis qu'un processus fils et son processus père peuvent a priori interagir durant leurs exécutions, même après s'être 
terminé, un processus fils doit attendre que son père soit définitivement terminer pour se finir complètement. Il attend 
donc sagement, façon zombie, après avoir envoyé un signal (SIGCHLD) à son père qui le prévient qu'il a fini de 
mouliner.  Pour traiter ce signal afin de terminer proprement le processus fils (pour récupérer la mémoire...) et d'éviter 
de bloquer le reste, on fabrique une petite fonction (à mettre avant le main) qui définit la façon de se comporter à 
l'entente d'un signal SIGCHLD.

#include <signal.h>
#include <wait.h>

void ZombieKiller(int sig){


while(waitpid(-1,NULL,WNOHANG)>1);
}

int main(int argc, char * argv[]){


...
signal(SIGCHLD,ZombieKiller); //avant d'utiliser le fork(). Determine
comment traiter le signal
...
}

L'appel waitpid est une fonction qui écoute les signaux envoyés par un ou plusieurs processus fils et qui détermine, à 
l'entente de ce signal, quel processus a bougé:

#include <wait.h>

pid_t waitpid(pid_t pid, int * status, int options);

/*Appel simple:*/
waitpid(-1, NULL, WNOHANG);

 s'il réussit, l'appel renvoie l'identifiant du processus fils pid dont l'état a changé, et en cas d'erreur ­1 est renvoyé. 
Lorsque pid=-1,  l'appel est à l'écoute de tous les processus fils.  Si *status n'est pas NULL,  waitpid() stocke 
l'état du fils dans la variable  *status qui indique si le fils s'est terminé et comment. L'entier options détermine la 
réaction du processus fils pid, l'option WNOHANG permet de pas bloquer si aucun fils ne s'est terminé.
Si WNOHANG est utilisé et aucun fils n'a changé d'état, la valeur de retour est 0.

Schéma de programme serveur

/* Ossature d'un programme créant un serveur en écoute constante.*/


/* traitement des clients en simultané, avec gestion des zombies*/
/* Parametre à entrer: numéro du port d'écoute*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h> /*Pour memset() !*/
#include <signal.h> /*Pour gestion des signaux*/
#include <wait.h> /*Pour gestion des signaux*/

/* Taille maximale des noms:*/


#define MAXNAME 256

/*Petite fonction qui traite les signaux de fins des processus fils...*/
void ZombieKiller(int sig){
while(waitpid(-1,NULL,WNOHANG)>0);
}

int main(int argc, char * argv[]){

/*Récupération du numéro de port pris en argument*/


int numport=atoi(argv[1]);

/*Determination du nom et IP de la machine ou le programme est lancé*/


char nom[MAXNAME];
struct hostent *infosmachine;
struct in_addr nummachine;
unsigned long int *temp;
gethostname(nom,MAXNAME); //Récupère le nom de la machine
infosmachine = gethostbyname(nom); //Récupère les infos de la machine

/*Ecriture des propriétés de la machine: Nom+IP*/


temp=(unsigned long int *)infosmachine->h_addr;
nummachine.s_addr=*temp;

printf("Le programme est lancé sur la machine appellée \"%s\", dont l'IP est
%s\n\n",infosmachine->h_name,inet_ntoa(nummachine));

/*Déclaration d'une socket*/


int sd=socket(AF_INET,SOCK_STREAM,0);

/*Déclaration de l'adresse de la socket*/


struct sockaddr_in sa;
memset(&sa,0,sizeof(sa));
sa.sin_family=AF_INET;
sa.sin_port=htons(numport); //pour mettre dans le bon sens...
sa.sin_addr=nummachine;
/*Assignation de l'adresse à la socket*/
bind(sd, (struct sockaddr *)&sa,sizeof(sa));

/*Nombre de client max en file d'attente pour la socket*/


listen(sd,10);

/*Et c'est parti pour affronter les clients....*/


signal(SIGCHLD,ZombieKiller); //Gestion des signaux de fins de processus.

int scomm; // on cree une socket client pour liberer la socket principale .
pid_t pid;
struct sockaddr_in ca;
int lgca=sizeof(ca);
memset(&ca,0,lgca);

while(1){
scomm=accept(sd,(struct sockaddr *)&ca,&lgca);
//ouverture d'un "canal" client avec recup de ses infos.

pid=fork(); //disjoinction Père-Fils

if(pid==-1){ //ERREUR DE DISJONCTION


fprintf(stdout,"Erreur dans la disjonction PERE/FILS");
}
if(pid==0){ //PROCESSUS FILS

/*Instructions Fils */

close(scomm); // ferme la socket de communication


exit(0); //termine le processus
}

else{ //PROCESSUS PERE

/* Instruction Père*/

close(scomm); // le processus père retourne en attente


}
}
close(sd); //ferme la socket principale
}
ANNEXE: Petites commandes aidant au diagnostique d'une erreur...

ping x.y.t.z
pour savoir si d'un point de vue physique, la machine x.y.z.t et la sienne  sont reliées...

route -n
donne la liste des routages d'IP.

ifconfig
donne la liste des interfaces réseau (et l'IP de la machine, en particulier)

sudo /etc/init.d/networking restart 


relance le réseau...

netstat -antp
donne la table des processus lancés sur la machine avec les numéros de ports sur lesquels ces processus écoutent.

Sudo tcpdump -i any -n host nommachine


donne en temps reel les echanges entre notre machine et la machine nommachine. L'option -n laisse les adresse et 
les ports en chiffres, -i any ecoute sur toutes les interfaces réseau. Au lieu d'écouter les échanges entre sa machine et 
une autre précisée, on peut enlever simplement  host nommachine  pour ecouter tous les échanges, mais aussi le 
remplacer par:
● net nomdomaine, pour écouter tout ce qui vient d'un domaine particulier,
● port numport, pour écouter ce qui transite par le port  numport.
● portrange numportmin-numportMAX, pour ecouter ce qui transite par les ports compris entre 
numportmin et numportMAX.

You might also like