2014:04:08:TP:C:Socket

De wiki-prog
Aller à : navigation, rechercher


Introduction

Voici la base de code d'un serveur TCP:

int main(int argc, char *argv[])
{
  int fd_accept;
  socklen_t rlen;
  int port = 4242;
  struct sockaddr_in local;
  struct sockaddr_in remote;
 
  /* Initialisation */
  memset(&local, 0, sizeof(local));
  local.sin_family = AF_INET;
  local.sin_addr.s_addr = htonl(INADDR_ANY);
  local.sin_port = htons(port);
 
  /* On cree la socket */
  fd_accept = socket(AF_INET, SOCK_STREAM, 0);
 
  /* On associe la socket */
  bind(fd_accept, (struct sockaddr*)&local, sizeof(local));
 
  /* On passe en mode ecoute */
  listen(fd_accept, 5);
 
  /* La boucle principale */
  for(;;)
    {
      int fd;
      /* Initialisation de la structure remote */
      memset(&remote, 0, sizeof(remote));
      remote.sin_family = AF_INET;
      rlen = sizeof(remote);
 
      /* On attend les connexions */
      fd = accept(fd_accept, (struct sockaddr*)&remote, &rlen);
      /* remote contient des informations sur la connexion */
 
      /* Ici on traite la connexion */
 
    } 
}

Exercices

  • Compléter le code précédant avec les en-têtes à inclure (pensez à utiliser man … )

On va maintenant implémenter un petit serveur dont le but est d'envoyer un fichier (toujours le même) vers le client. On se fixe un petit protocole simple, décrit par l'échange suivant (après établissement de la connexion):

client: GET\r\n\r\n
server: SIZE taille\r\n\r\n
client: OK\r\n\r\n
server: ... envoie du fichier ...

Les séquences \r\n\r\n correspondent à un double retour chariot dans la norme telnet (différent du line-feed Unix \n seul.)

La taille du fichier est la taille en octets (donc la taille de la transmission après reception du OK.)

Le fichier est envoyé tel quel par le serveur.

La fin de la connexion est à la responsabilité du client (comme dans tout bon protocole TCP), le serveur doit attendre en lecture sur la connexion la fin de fichier pour fermer la socket.

  • Implémenter le parsing des messages du client à partir d'un file descriptor en lecture (comme ça vous pourrez tester sur l'entrée standard.)
/* read_get(fd) lit le message GET et renvoie 1 si tout va bien et 0 sinon */
int read_get(int fd);
/* read_ok(fd) lit le message OK et renvoie 1 si tout va bien et 0 sinon */
int read_okt(int fd);

Note: les messages du client sont de taille fixe, donc il vous suffit de lire sur la socket jusqu'à avoir le bon nombre d'octet (7 pour le premier message et 6 pour le second.)

  • Implémenter le protocol à partir d'une paire de file-descriptors
int protocol(char *file, int fdin, int fdout);

On utilise une paire de fd pour pouvoir tester avec l'entrée et la sortie standard avant de passer au réseau, lorsque l'on utilisera le réseau, on donnera le même fd pour les deux paramètres. Si l'une des lectures (GET et OK) échoue vous devez sortir immédiatement avec 0, sinon vous renverez 1 à la fin.

Penser à la commande stat(2) pour récupérer la taille du fichier.

  • Intéger tout ça dans le code du serveur en utilisant fork(2)

Si la fonction protocol échoue (retour 0) vous devez fermer la socket immédiatement, sinon il vous faudra attendre (avec read(2)) la fermeture côté client.

Bonus

Vous pouvez améliorer votre serveur:

  • En utilisant sendfile(2)
  • En utilisant select(2) avec un timeout pour limiter l'attente de la fin de connexion

Et si vous n'avez vraiment rien à faire remplacer la gestion des connexions multiples par un usage intelligent de select(2).