Programmation réseau

1  Généralité sur les sockets

1.1  Présentation

1.2  Caractéristiques des sockets

1.3  Les familles de sockets

1.4  Les modes de connexion

1.5  Les adressages 1/2

1.6  Les adressages 2/2

2  Primitives communes

2.1  Création d’une socket

2.2  Contrôle d’une socket

3  Primitives pour le mode connecté

3.1  Schéma de principe

3.2  Primitives pour le serveur

3.3  Primitive pour le client

3.4  Exemple de serveur

3.5  Exemple de client

4  Primitives pour le mode non connecté

4.1  Schéma de principe

4.2  Primitives de communication

4.3  Exemple de serveur 1/2

int main(int argc,char *argv[])
{
int s;     /* Descripteur de la SOCKET */
struct sockaddr_in6 adresseServeur; /* Structure adresse du serveur */
socklen_t taille=sizeof adresseServeur;
int statut;

/* Lecture des arguments de la commande */
analyseArguments(argc,argv);

/* Creation de la SOCKET d'ecoute du serveur */
s=socket(PF_INET6,SOCK_DGRAM,0);
if(s<0) {perror("socket"); exit(-1);}

/* Preparation de la structure adresse du serveur */
adresseServeur.sin6_family=AF_INET6;
adresseServeur.sin6_addr=in6addr_any;
adresseServeur.sin6_port=htons(SERVEUR_PORT);
adresseServeur.sin6_flowinfo=0;
adresseServeur.sin6_scope_id=0;

/* Installation du serveur a la bonne adresse */
statut=bind(s,(struct sockaddr *)&adresseServeur,taille);
if(statut<0) {perror("bind"); exit(-1);}   

/* Attente de la connexion d'un client puis dialogue avec ce client */
while(1){
    struct sockaddr_in6 adresseClient; /* Structure adresse du client */
    socklen_t taille=sizeof adresseClient; /* Taille de cette adresse */
    char tampon1[MAX_TAMPON];
    char tampon2[MAX_TAMPON];
    int nboctets;

    nboctets=recvfrom(s,tampon1,MAX_TAMPON-1,0,
                      (struct sockaddr *)&adresseClient,
                      &taille);
    tampon1[nboctets]='\0';
    sprintf(tampon2,"%s%s",PREFIXE,tampon1);
    sendto(s,tampon2,strlen(tampon2),0,
           (struct sockaddr *)&adresseClient,taille);
    }
}

4.4  Exemple de serveur 2/2

int initialisationSocketUDP(char *service){
struct addrinfo precisions,*resultat,*origine;
int statut;
int s;

/* Construction de la structure adresse */
memset(&precisions,0,sizeof precisions);
precisions.ai_family=AF_UNSPEC;
precisions.ai_socktype=SOCK_DGRAM;
precisions.ai_flags=AI_PASSIVE;
statut=getaddrinfo(NULL,service,&precisions,&origine);
if(statut<0){ perror("initialisationSocketUDP.getaddrinfo"); exit(EXIT_FAILURE); }
struct addrinfo *p;
for(p=origine,resultat=origine;p!=NULL;p=p->ai_next)
  if(p->ai_family==AF_INET6){ resultat=p; break; }

/* Creation d'une socket */
s=socket(resultat->ai_family,resultat->ai_socktype,resultat->ai_protocol);
if(s<0){ perror("initialisationSocketUDP.socket"); exit(EXIT_FAILURE); }

/* Options utiles */
int vrai=1;
if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&vrai,sizeof(vrai))<0){
  perror("initialisationServeurUDPgenerique.setsockopt (REUSEADDR)");
  exit(EXIT_FAILURE);
  }

/* Specification de l'adresse de la socket */
statut=bind(s,resultat->ai_addr,resultat->ai_addrlen);
if(statut<0){
  freeaddrinfo(origine); shutdown(s,SHUT_RDWR);
  perror("initialisationServeurUDP.bind");
  return -1;
  }

/* Liberation de la structure d'informations */
freeaddrinfo(origine);

return s;
}

int boucleServeurUDP(int s,int (*traitement)(unsigned char *,int,struct sockaddr *,int)){
while(1){
  struct sockaddr_storage adresse;
  socklen_t taille=sizeof(adresse);
  unsigned char message[MAX_UDP_MESSAGE];
  int nboctets=recvfrom(s,message,MAX_UDP_MESSAGE,0,(struct sockaddr *)&adresse,&taille);
  if(nboctets<0) return -1;
  if(traitement(message,nboctets,(struct sockaddr *)&adresse,taille)<0) break;
  }
return 0;
}

int main(void){
int s=initialisationSocketUDP("4242");
if(s<0){ fprintf(stderr,"Port non utilisable\n"); exit(EXIT_FAILURE); }
boucleServeurUDP(s,votreFonctionUDP);
shutdown(s,SHUT_RDWR);
return 0;
}

4.5  Exemple de client 1/2

int main(int argc,char *argv[])
{
int s;    /* Descripteur de SOCKET */
struct sockaddr_in6 adresse; /* Adresse de la SOCKET du serveur */
int statut;   /* Stocke le statut des commandes */

/* Analyse des arguments */
analyseArguments(argc,argv);

/* Creation de la SOCKET du client */
s=socket(PF_INET6,SOCK_DGRAM,0);
if(s<0){perror("socket"); exit(-1);}

/* Preparation de la structure adresse du serveur */
adresse.sin6_family=AF_INET6;
if(nomVersAdresse(machine,(void *)&adresse)<0){
  fprintf(stderr,"Impossible de trouver l'adresse IP du serveur !\n");
  exit(-1);
  }
adresse.sin6_port=htons(port);
adresse.sin6_flowinfo=0;
adresse.sin6_scope_id=0;

/* Dialogue avec le serveur */
{ char tampon[MAX_TAMPON];
  int taille;

  fgets(tampon,MAX_TAMPON,stdin);
  sendto(s,tampon,strlen(tampon),0,(struct sockaddr *)&adresse,sizeof adresse);
  taille=recvfrom(s,tampon,MAX_TAMPON,0,NULL,NULL);
  fputs(tampon,stdout);
  }

/* Fermeture de la SOCKET de dialogue */
close(s);
exit(0);
}

4.6  Exemple de client 2/2

void messageUDP(char *hote,char *service,unsigned char *message,int taille){
struct addrinfo precisions,*resultat,*origine;
int statut;
int s;

/* Creation de l'adresse de socket */
memset(&precisions,0,sizeof precisions);
precisions.ai_family=AF_UNSPEC;
precisions.ai_socktype=SOCK_DGRAM;
statut=getaddrinfo(hote,service,&precisions,&origine);
if(statut<0){ perror("messageUDPgenerique.getaddrinfo"); exit(EXIT_FAILURE); }
struct addrinfo *p;
for(p=origine,resultat=origine;p!=NULL;p=p->ai_next)
  if(p->ai_family==AF_INET6){ resultat=p; break; }

/* Creation d'une socket */
s=socket(resultat->ai_family,resultat->ai_socktype,resultat->ai_protocol);
if(s<0){ perror("messageUDPgenerique.socket"); exit(EXIT_FAILURE); }

/* Option sur la socket */
int vrai=1;
if(setsockopt(s,SOL_SOCKET,SO_BROADCAST,&vrai,sizeof(vrai))<0){
  perror("initialisationServeurUDPgenerique.setsockopt (BROADCAST)");
  exit(EXIT_FAILURE);
  }

/* Envoi du message */
int nboctets=sendto(s,message,taille,0,resultat->ai_addr,resultat->ai_addrlen);
if(nboctets<0){ perror("messageUDPgenerique.sento"); exit(EXIT_FAILURE); }

/* Liberation de la structure d'informations */
freeaddrinfo(origine);

/* Fermeture de la socket d'envoi */
shutdown(s,SHUT_RDWR);
}

int main(int argc,char *argv[]){
if(argc!=3){
  fprintf(stderr,"Syntaxe : %s <serveur> <message>\n",argv[0]);
  exit(EXIT_FAILURE);
  }
char *hote=argv[1];
char *message=argv[2];
char *service="4000";
messageUDP(hote,service,(unsigned char *)message,strlen(message));
return 0;
}

5  Fonctions de bibliothèque

5.1  Obtention des adresses IP (classique)

5.2  Obtention des adresses IP (moderne)

5.3  Trouver l’adresse de socket

5.4  Numéros de ports

5.5  Numéros de protocoles

5.6  Exemples de résolution

6  Le super-serveur Unix

6.1  Définition d’un démon

6.2  Le super-serveur d’Unix

6.3  Schéma de fonctionnement

6.4  Exemples de serveurs

A  Annexes

A.1  Programmation réseau

A.2  Programmation en Bourne Shell

A.2.1  Serveur Web en Bourne Shell

#!/bin/sh
#
# Exemple de serveur HTTP en shell
#

#
# Quelques constantes
#

NC=/usr/bin/nc
PORT=8080
TTY_PREFIXE=/dev/tty
PTY_PREFIXE=/dev/pty
TTY_NUMERO=q3
REPERTOIRE=${HOME}/Scripts/Web

#
# Boucle principale
# (ecoute sur le port, traitement de la requete, envoi du fichier)
#

while true ; do
    #
    # On ecoute sur le cote maitre du terminal virtuel
    #
    ${NC} -l -p ${PORT} 4<>${PTY_PREFIXE}${TTY_NUMERO} >&4 2>&4 <&4 &
    PID=$!
    #
    # On lance un sous-shell sur le cote esclave
    #
    (
    stty -echo igncr
    #
    # Lecture de la requete HTTP
    #
    read requete
    #
    # Lecture des entetes (mises a la poubelle)
    #
    while read line ; do 
 if [ -z "${line}" ] ; then break ; fi
    done
    #
    # Analyse de la requete
    #
    set -- ${requete}
    commande=$1 ; file=$2 ; protocole=$3
    #
    # Reponse a la requete
    #
    if [ "${commande}" != "GET" -o ! -f "${REPERTOIRE}/${file}" ] ; then
 echo "${protocole} 400 erreur"
 echo
 echo "Erreur !!"
    else
 echo "${protocole} 200 OK"
 echo "Content-type: text/html"
 echo
 cat "${REPERTOIRE}/${file}"
    fi
    ) 4<>${TTY_PREFIXE}${TTY_NUMERO} >&4 2>&4 <&4
    #
    # On stoppe l'ecoute
    #
    kill ${PID}
    sleep 2
done

A.2.2  Exemple de page Web

<html>
 <head>
  <title>Serveur web de demonstration</title>
 </head>

  Voici deux p't liens :

  <dl>
    <dt>
      <a href="/fichier1">Premier fichier</a>
    <dt>
      <a href="/fichier2">Second fichier</a>
  </dl>
  <p align=right>
    Serveur Script
  </p>
 </body>
</html>

A.3  Programmation avec la commande nc

A.3.1  Serveur Web minimal en Bourne Shell

#!/bin/bash
WEB_DIR=`dirname $0`/www/
NOT_FOUND=error.html
read cmd page proto
echo $cmd $page $proto >> /tmp/log
while read line ; do
  echo $line >> /tmp/log
  if [ "$line" = "
" ] ; then break ; fi
done
if [ "$cmd" = GET ] ; then
  path=${WEB_DIR}$page
  code=200
  if [ ! -f "$path" ] ; then
    path=${WEB_DIR}/${NOT_FOUND}
    code=404
  fi
  case `basename $path` in
    *.png) type="image/png";;
    *.html) type="text/html";;
  esac
  size=`cat $path | wc -c`
  echo "HTTP/1.0 $code"
  echo "Server: ShellWeb"
  echo "Content-type: $type"
  echo "Content-length: $size"
  echo
  cat $path
fi

A.3.2  Serveur Web minimal en C

/**** Minimal web server ****/

/** Include files **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>


/** Some constants **/

#define WEB_DIR  "./www"
#define PAGE_NOTFOUND "error.html"
#define MAX_BUFFER 1024

#define CODE_OK  200
#define CODE_NOTFOUND 404

/** Main procedure **/

int main(void){
char buffer[MAX_BUFFER];
char cmd[MAX_BUFFER];
char page[MAX_BUFFER];
char proto[MAX_BUFFER];
char path[MAX_BUFFER];
char type[MAX_BUFFER];

if(fgets(buffer,MAX_BUFFER,stdin)==NULL) exit(-1);
if(sscanf(buffer,"%s %s %s",cmd,page,proto)!=3) exit(-1);
while(fgets(buffer,MAX_BUFFER,stdin)!=NULL){
  if(strcmp(buffer,"\r\n")==0) break;
  }
if(strcasecmp(cmd,"GET")==0){
  int code=CODE_OK;
  struct stat fstat;
  sprintf(path,"%s%s",WEB_DIR,page);
  if(stat(path,&fstat)!=0 || !S_ISREG(fstat.st_mode)){
    sprintf(path,"%s/%s",WEB_DIR,PAGE_NOTFOUND);
    code=CODE_NOTFOUND;
    }
  strcpy(type,"text/html");
  char *end=page+strlen(page);
  if(strcmp(end-4,".png")==0) strcpy(type,"image/png");
  if(strcmp(end-4,".jpg")==0) strcpy(type,"image/jpg");
  if(strcmp(end-4,".gif")==0) strcpy(type,"image/gif");
  fprintf(stdout,"HTTP/1.0 %d\r\n",code);
  fprintf(stdout,"Server: CWeb\r\n");
  fprintf(stdout,"Content-type: %s\r\n",type);
  fprintf(stdout,"Content-length: %d\r\n",fstat.st_size);
  fprintf(stdout,"\r\n");
  fflush(stdout);
  int fd=open(path,O_RDONLY);
  if(fd>=0){
    int bytes;
    while((bytes=read(fd,buffer,MAX_BUFFER))>0) write(1,buffer,bytes);
    close(fd);
    }
  }
}

A.3.3  Exemple d’utilisation avec nc

while true ; do  nc -l -p 80 -c ./web ; done

A.3.4  Exemple de page HTML

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
  <HEAD>
    <TITLE>Serveur d'enseignement de Polytech'Lille</TITLE>
  </HEAD>
  <BODY>
    <TABLE BORDER=0 WIDTH=100% HEIGHT=100%>
      <TR><TD VALIGN=center>
        <CENTER>
          <IMG ALT="" SRC="elephant.png">
        </CENTER>
      </TD></TR>
    </TABLE>
  </BODY>
</HTML>

A.4  Sources pour RPC

A.4.1  Makefile pour le serveur et le client RPC

#
# Generation d'un client et d'un serveur RPC de demonstration
# (simple addition et soustraction d'entiers).
#

# Quelques constantes liees au projet

CLIENT_OBJS=calc_client.o calc_clnt.o calc_xdr.o
SERVER_OBJS=calc_server.o calc_svc.o calc_xdr.o

# Message pour la marche a suivre

all:
 @echo -e "\
Tapez d'abord make generate pour obtenir le fichier d'inclusion .h et\n\
les fichiers C. Modifiez les programmes calc_client.c et calc_server.c\n\
puis lancez la compilation par make compile."

# Generation des fichiers C par rpcgen

generate: calc.x
 rpcgen -a -C $^ 

# Copie des fichiers modifies a la main sur leurs modeles

copy:
 cp calc_client_modified.c calc_client.c
 cp calc_server_modified.c calc_server.c

# Compilation des programmes C

compile: client server

client: ${CLIENT_OBJS} calc.h
 gcc -o client ${CLIENT_OBJS}

server: ${SERVER_OBJS} calc.h
 gcc -o server ${SERVER_OBJS}

# Regle de nettoyage

clean: 
 rm -f client server *.o calc.h calc_svc.c calc_clnt.c calc_xdr.c
 rm -f calc_client.c calc_server.c

A.4.2  Définition des RPC

/********************************************************************/
/** Fichier de definition de RPC d'addition et de soustraction     **/
/********************************************************************/

/** Quelques constantes **/

#define PROGRAM_NUMBER 19992000
#define VERSION_NUMBER 1

/** Structures des parametres des procedures **/

struct operandes {
 int x;
 int y;
};

/** Definition des RPC **/

program CALC_PROG {
   version CALC_VERSION {
     int ADD(operandes) = 1;
     int SUB(operandes) = 2;
   } = VERSION_NUMBER;
} = PROGRAM_NUMBER;

A.4.3  Fichier C du serveur

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "calc.h"

int * 
add_1_svc(operandes *argp, struct svc_req *rqstp)
{

 static int  result;

/* AJOUT DE CODE : implantation de l'addition */
 result=argp->x+argp->y;
/* FIN D'AJOUT DE CODE */

 return(&result);
}

int * 
sub_1_svc(operandes *argp, struct svc_req *rqstp)
{

 static int  result;

/* AJOUT DE CODE : implantation de la soustraction */
 result=argp->x-argp->y;
/* FIN D'AJOUT DE CODE */

 return(&result);
}

A.4.4  Fichier C du client

/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include "calc.h"


void
/** MODIFICATION DE CODE : ajout des operandes en parametre **/
calc_prog_1( char* host, int x, int y )
/** FIN DE MODIFICATION DE CODE **/
{
 CLIENT *clnt;
 int  *result_1;
 operandes  add_1_arg;
 int  *result_2;
 operandes  sub_1_arg;
 clnt = clnt_create(host, CALC_PROG, CALC_VERSION, "udp");
 if (clnt == NULL) {
  clnt_pcreateerror(host);
  exit(1);
 }
/** AJOUT DE CODE : initialisation du parametre de la RPC **/
 add_1_arg.x = x ; add_1_arg.y = y ; 
/** FIN D'AJOUT DE CODE **/
 result_1 = add_1(&add_1_arg, clnt);
 if (result_1 == NULL) {
  clnt_perror(clnt, "call failed:");
 }
/** AJOUT DE CODE : affichage du resultat de la RPC **/
 printf("%d + %d = %d\n",add_1_arg.x,add_1_arg.y,*result_1);
/** FIN D'AJOUT DE CODE **/
/** AJOUT DE CODE : initialisation du parametre de la RPC **/
 sub_1_arg.x = x ; sub_1_arg.y = y ; 
/** FIN D'AJOUT DE CODE **/
 result_2 = sub_1(&sub_1_arg, clnt);
 if (result_2 == NULL) {
  clnt_perror(clnt, "call failed:");
 }
/** AJOUT DE CODE : affichage du resultat de la RPC **/
 printf("%d - %d = %d\n",sub_1_arg.x,sub_1_arg.y,*result_2);
/** FIN D'AJOUT DE CODE **/
 clnt_destroy( clnt );
}


main( int argc, char* argv[] )
{
 char *host;
/** AJOUT DE CODE : variables pour les operandes **/
 int x,y;
/** FIN D'AJOUT DE CODE **/

/** MODIFICATION DE CODE : nouvelle syntaxe **/
 if(argc < 4) {
  printf("usage: %s server_host op1 op2\n", argv[0]);
/** FIN DE MODIFICATION DE CODE **/
  exit(1);
 }
 host = argv[1];
/** MODIFICATION DE CODE : recuperation et utilisation des operandes **/
 x = atoi(argv[2]);
 y = atoi(argv[3]);
 calc_prog_1( host, x, y );
/** FIN DE MODIFICATION DE CODE **/
}

Ce document a été traduit de LATEX par HEVEA