Programmation de sockets en C

Programmation de sockets en C

Programmation des sockets est un moyen de connecter deux nœuds sur un réseau pour communiquer entre eux. Un socket (nœud) écoute sur un port particulier sur une adresse IP tandis que l'autre socket tend la main à l'autre pour établir une connexion. Le serveur forme le socket d'écoute tandis que le client contacte le serveur.
La programmation Socket est largement utilisée dans les applications de messagerie instantanée, le streaming binaire et les collaborations documentaires, les plateformes de streaming en ligne, etc.

Exemple

Dans ce programme C, nous échangeons un message bonjour entre le serveur et le client pour démontrer le modèle client/serveur.

serveur.c

C
   #include         #include         #include         #include         #include         #include         #define PORT 8080   int     main  (  int     argc       char     const  *     argv  [])   {      int     server_fd       new_socket  ;      ssize_t     valread  ;      struct     sockaddr_in     address  ;      int     opt     =     1  ;      socklen_t     addrlen     =     sizeof  (  address  );      char     buffer  [  1024  ]     =     {     0     };      char  *     hello     =     'Hello from server'  ;      // Creating socket file descriptor      if     ((  server_fd     =     socket  (  AF_INET       SOCK_STREAM       0  ))      <     0  )     {      perror  (  'socket failed'  );      exit  (  EXIT_FAILURE  );      }      // Forcefully attaching socket to the port 8080      if     (  setsockopt  (  server_fd       SOL_SOCKET        SO_REUSEADDR     |     SO_REUSEPORT       &  opt        sizeof  (  opt  )))     {      perror  (  'setsockopt'  );      exit  (  EXIT_FAILURE  );      }      address  .  sin_family     =     AF_INET  ;      address  .  sin_addr  .  s_addr     =     INADDR_ANY  ;      address  .  sin_port     =     htons  (  PORT  );      // Forcefully attaching socket to the port 8080      if     (  bind  (  server_fd       (  struct     sockaddr  *  )  &  address        sizeof  (  address  ))       <     0  )     {      perror  (  'bind failed'  );      exit  (  EXIT_FAILURE  );      }      if     (  listen  (  server_fd       3  )      <     0  )     {      perror  (  'listen'  );      exit  (  EXIT_FAILURE  );      }      if     ((  new_socket      =     accept  (  server_fd       (  struct     sockaddr  *  )  &  address        &  addrlen  ))       <     0  )     {      perror  (  'accept'  );      exit  (  EXIT_FAILURE  );      }          // subtract 1 for the null      // terminator at the end      valread     =     read  (  new_socket       buffer        1024     -     1  );         printf  (  '%s  n  '       buffer  );      send  (  new_socket       hello       strlen  (  hello  )     0  );      printf  (  'Hello message sent  n  '  );      // closing the connected socket      close  (  new_socket  );          // closing the listening socket      close  (  server_fd  );      return     0  ;   }   

client.c

C
   #include          #include         #include         #include         #include         #define PORT 8080   int     main  (  int     argc       char     const  *     argv  [])   {      int     status       valread       client_fd  ;      struct     sockaddr_in     serv_addr  ;      char  *     hello     =     'Hello from client'  ;      char     buffer  [  1024  ]     =     {     0     };      if     ((  client_fd     =     socket  (  AF_INET       SOCK_STREAM       0  ))      <     0  )     {      printf  (  '  n   Socket creation error   n  '  );      return     -1  ;      }      serv_addr  .  sin_family     =     AF_INET  ;      serv_addr  .  sin_port     =     htons  (  PORT  );      // Convert IPv4 and IPv6 addresses from text to binary      // form      if     (  inet_pton  (  AF_INET       '127.0.0.1'       &  serv_addr  .  sin_addr  )       <=     0  )     {      printf  (      '  n  Invalid address/ Address not supported   n  '  );      return     -1  ;      }      if     ((  status      =     connect  (  client_fd       (  struct     sockaddr  *  )  &  serv_addr        sizeof  (  serv_addr  )))       <     0  )     {      printf  (  '  n  Connection Failed   n  '  );      return     -1  ;      }          // subtract 1 for the null      // terminator at the end      send  (  client_fd       hello       strlen  (  hello  )     0  );      printf  (  'Hello message sent  n  '  );      valread     =     read  (  client_fd       buffer        1024     -     1  );         printf  (  '%s  n  '       buffer  );      // closing the connected socket      close  (  client_fd  );      return     0  ;   }   


Compilation

 gcc client.c -o clientgcc server.c -o server  


Sortir

 Client:Hello message sentHello from serverServer:Hello from clientHello message sent  

Composants de la programmation Socket

1. Prises

Prises sont l'un des composants principaux utilisés par le programme pour accéder au réseau afin de communiquer avec d'autres processus/nœuds sur le réseau. Il s'agit simplement d'une combinaison d'une adresse IP et d'un numéro de port qui fait office de point final pour la communication.
Exemple : 192.168.1.1:8080 où les deux parties séparées par les deux points représentent le Adresse IP (192.168.1.1) et le numéro de port (8080).

Types de prises :

  • Prise TCP (prise de flux) : Fournit une communication fiable basée sur une connexion (c.-à-d. Protocole TCP ).
  • Prise UDP (prise de datagramme) : Fournit une communication sans connexion plus rapide mais peu fiable (c.-à-d. Protocole UDP ).

2. Modèle client-serveur

Le modèle client-serveur fait référence à l'architecture utilisée dans la programmation socket où un client et un serveur interagissent entre eux pour échanger des informations ou des services. Cette architecture permet au client d'envoyer des demandes de service et au serveur de traiter et d'envoyer une réponse à ces demandes de service.

Diagramme d'état pour le modèle serveur et client

Programmation de sockets en CDiagramme d'état pour le modèle serveur et client de Socket

La programmation socket en C est un moyen puissant de gérer la communication réseau.

Création d'un processus côté serveur

Le serveur est créé en suivant les étapes suivantes :

1. Création de sockets

Cette étape implique la création du socket à l’aide de la fonction socket().

Paramètres :

  • chaussette : descripteur de socket un entier (comme un descripteur de fichier)
  • domaine: un entier spécifie le domaine de communication. Nous utilisons AF_LOCAL tel que défini dans le standard POSIX pour la communication entre les processus sur le même hôte. Pour communiquer entre les processus sur différents hôtes connectés par IPV4, nous utilisons AF_INET et AF_I NET 6 pour les processus connectés par IPV6.
  • taper: type de communication
    SOCK_STREAM : TCP (orienté connexion fiable)
    SOCK_DGRAM : UDP (sans connexion peu fiable)
  • protocole: Valeur de protocole pour Internet Protocol (IP) qui est 0. Il s'agit du même numéro qui apparaît dans le champ de protocole dans l'en-tête IP d'un paquet. (man protocols pour plus de détails)
C
   sockfd     =     socket  (  domain       type       protocol  )   

2. Définir l'option de prise

Cela aide à manipuler les options du socket référencé par le descripteur de fichier sockfd. Ceci est complètement facultatif mais cela facilite la réutilisation de l'adresse et du port. Empêche les erreurs telles que : adresse déjà utilisée.

C
   setsockopt  (  sockfd       level       optname       optval       socklen_t     optlen  );   

3. Lier

Après la création du socket, la fonction bind() lie le socket à l'adresse et au numéro de port spécifiés dans addr (structure de données personnalisée). Dans l'exemple de code, nous lions le serveur à l'hôte local, nous utilisons donc INADDR_ANY pour spécifier l'adresse IP.

C++
   bind  (  sockfd       sockaddr     *  addr       socklen_t     addrlen  );   

Paramètres :

  • chaussette : descripteur de fichier socket créé à l'aide de la fonction socket().
  • adresse : pointeur vers une structure sockaddr qui contient l'adresse IP et le numéro de port pour lier le socket.
  • addrlen : longueur de la structure addr.

4. Écoutez

Dans cette étape, le serveur utilise la fonction Listen() qui met le socket du serveur dans un mode passif où il attend que le client s'approche du serveur pour établir une connexion. Le backlog définit la longueur maximale à laquelle la file d'attente des connexions en attente pour sockfd peut croître. Si une demande de connexion arrive alors que la file d'attente est pleine, le client peut recevoir une erreur avec une indication d'ECONNREFUSED.

C
   listen  (  sockfd       backlog  );   

Paramètres :

  • chaussette : descripteur de fichier socket créé à l'aide de la fonction socket().
  • arriéré : nombre représentant la taille de la file d'attente contenant les connexions en attente pendant que le serveur attend d'accepter une connexion.

5. Acceptez

Dans cette étape, le serveur extrait la première demande de connexion de la file d'attente des connexions en attente pour le socket d'écoute. sockfd crée un nouveau socket connecté en utilisant le accepter() fonction et renvoie un nouveau descripteur de fichier faisant référence à ce socket. À ce stade, la connexion est établie entre le client et le serveur et ils sont prêts à transférer des données.

C
   new_socket  =     accept  (  sockfd       sockaddr     *  addr       socklen_t     *  addrlen  );   

Paramètres :

  • chaussette : descripteur de fichier socket renvoyé par socket() et bind().
  • adresse : pointeur vers une structure sockaddr qui contiendra l'adresse IP et le numéro de port du client.
  • addrlen : pointeur vers une variable qui spécifie la longueur de la structure d'adresse.

6. Envoyer/Recevoir

Dans cette étape, le serveur peut envoyer ou recevoir des données du client.

Envoyer(): pour envoyer des données au client

C
   send  (  sockfd       *  buf       len       flags  );   

Paramètres :

  • chaussette : descripteur de fichier socket renvoyé par la fonction socket().
  • bouf : pointeur vers le buffer contenant les données à envoyer.
  • seulement : nombre d'octets de données à envoyer.
  • drapeaux : entier spécifiant diverses options sur la façon dont les données sont envoyées, généralement 0 est utilisé pour le comportement par défaut.

Recevoir() : pour recevoir les données du client.

C
   recv  (     sockfd       *  buf       len       flags  );   

Paramètres :

  • chaussette : descripteur de fichier socket renvoyé par la fonction socket().
  • bouf : pointeur vers le buffer contenant les données à stocker.
  • seulement : nombre d'octets de données à envoyer.
  • drapeaux : entier spécifiant diverses options sur la façon dont les données sont envoyées, généralement 0 est utilisé pour le comportement par défaut.

6. Fermer

Une fois l'échange d'informations terminé, le serveur ferme le socket à l'aide de la fonction close() et libère les ressources système.

C
   close  (  fd  );   

Paramètres :

  • fd : descripteur de fichier du socket.

Création d'un processus côté client

Suivez les étapes ci-dessous pour créer un processus côté client :

1. Connexion de prise

Cette étape implique la création du socket qui se fait de la même manière que celle du serveur.

2. Connectez-vous

L'appel système connect() connecte le socket référencé par le descripteur de fichier sockfd à l'adresse spécifiée par addr. L'adresse et le port du serveur sont spécifiés dans l'adr.

C++
   connect  (  sockfd       sockaddr     *  addr       socklen_t     addrlen  );   

Paramètres

  • chaussette : descripteur de fichier socket renvoyé par la fonction socket().
  • adresse : pointeur vers la structure sockaddr contenant l'adresse IP et le numéro de port du serveur.
  • addrlen : taille de l'adr.

3. Envoyer/Recevoir

Au cours de cette étape, le client peut envoyer ou recevoir des données du serveur, ce qui s'effectue à l'aide des fonctions send() et recieve() de la même manière que le serveur envoie/reçoit des données du client.

4. Fermer

Une fois l'échange d'informations terminé, le client doit également fermer le socket créé et libérer les ressources système à l'aide de la fonction close() de la même manière que le serveur.

Problèmes courants et leurs correctifs dans la programmation Socket

  • Échecs de connexion : Pour éviter les échecs de connexion, nous devons nous assurer que le client essaie de se connecter au bon Adresse IP et port .
  • Erreurs de liaison de port : Ces erreurs se produisent lorsqu'un port est déjà utilisé par une autre application. Dans ce scénario, la liaison à ce port échouera. Essayez d'utiliser un autre port ou fermez l'application précédente à l'aide du port.
  • Prises bloquantes : Par défaut, les sockets bloquent. Cela signifie que les appels comme accept() ou recv() attendront indéfiniment s'il n'y a pas de connexion client ou de données. Vous pouvez définir le socket en mode non bloquant si nécessaire.
Créer un quiz