Gestion des erreurs avec les sockets en C

Les sockets sont la base de la programmation d’applications réseau et il n’est pas rare de les manipuler en C. Il est assez facile de faire du code portable entre Windows et les autres systèmes d’exploitation courant (Linux, Mac OS X et autres Unix), en incluant les en-têtes qui vont bien à coup de directives pré-processeur et en pensant à démarrer la bibliothèque Winsock2 sous Windows. Vous pouvez lire ce tutoriel sur les sockets C, c’est avec lui que j’ai appris lors d’un projet en C à l’INSA.

Dans mon présent article, je souhaite mettre en avant un point qui n’est sûrement pas écrit assez gros dans le tutoriel : si les fonctions de manipulation des sockets sont les mêmes, la gestion des erreurs est différente ! En effet, les Unixoïdes positionnent correctement la variable magique errno et on peut donc utiliser perror() et strerror() pour avoir un message lisible décrivant l’erreur. Mais Windows ne positionne pas errno ! Le vilain ! Il faut alors utiliser la fonction WSAGetLastError() pour obtenir un code équivalent à errno et utiliser la fonction FormatMessage() pour obtenir un message lisible.

Voici un code simple (et imparfait, le retour de socket() n’est pas vérifié par exemple). Si l’adresse IP que vous renseignée n’est pas correcte pour votre ordinateur, alors la fonction bind() échouera. On pourra alors constater la différence entre Windows et les autres.

#include <errno.h></pre>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined (_WIN32)
#include <winsock2.h>
typedef int socklen_t;

#elif defined (__linux__)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;

#endif

#define PORT 23242

int main(void)
{
#if defined (_WIN32)
    WSADATA WSAData;
    WSAStartup(MAKEWORD(2,2), &WSAData);
#endif

    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

    SOCKADDR_IN sin;
    sin.sin_addr.s_addr = inet_addr("137.0.0.1");
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);

    errno = 0;

    int sock_err = bind(sock, (SOCKADDR*)&sin, sizeof(sin));

    if(sock_err == -1)
    {
        printf("Errno bind(): %d : %s\n", errno, strerror((errno)));
        perror("Perror bind()");

#if defined (_WIN32)
        printf("WSAGetLastError bind() : %d\n", WSAGetLastError());
        char *wsa_message = NULL;
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                WSAGetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPSTR)&wsa_message,
                0,
                NULL);
        printf("FormatMessage : %s\n", wsa_message);
        LocalFree(wsa_message);
#endif
    }

#if defined (_WIN32)
    WSACleanup();
#endif

    return EXIT_SUCCESS;
}

Résultat sous Windows Seven :

Errno bind(): 0 : No error
Perror bind(): No error
WSAGetLastError bind() : 10049
FormatMessage : L'adresse demandée n'est pas valide dans son contexte.

Résultat sous Mac OS X :

Errno bind(): 49 : Can't assign requested address
Perror bind(): Can't assign requested address

Gardez ce détail en tête pour éviter de vous retrouvez avec un code qui compile et s’exécute de la même manière mais avec perror("Bind()"); qui affiche des messages d’erreur qui n’ont aucun rien à avoir la choucroute comme Bind(): Result too Large ou encore Bind(): No error.

Publicités

Une Réponse

  1. On n’oublie pas les autres pièges de winsock, par exemple l’utilisation de closeSocket (et pas de close) pour fermer le socket (et on pense a faire un shutdown avant, au cas ou). On doit aussi utiliser les codes d’erreurs WSAE* et pas E* du standard POSIX.

    Pour Windows, la plupart des problèmes sont connus et documentés par Microsoft: http://msdn.microsoft.com/en-us/library/windows/desktop/ms740096(v=vs.85).aspx

    Mais on peut rencontrer le même genre d’ennuis sur d’autres systèmes. Par exemple BeOS ne pouvait pas utiliser les sockets dans read(), write(), select(), …

    Ne parlons même pas de stacks pour l’embarqué comme LWIP qui combinent tous les problèmes ! (pas de errno, et pas de read/write/select non plus !)

    C’est plus compliqué qu’il n’y parait de faire quelque chose de portableavec des sockets, finalement. Une astuce est d’utiliser OpenSSL, qui fournit (en plus du bazar pour faire du SSL et de la cryptographie) une API portable pour travailler avec des sockets et/ou des descripteurs de fichiers et/ou autre chose (l’API permet d’étendre de système d’I/O en remplissant une struct de pointeurs de fonctions).

    Bon courage 🙂

    J'aime

    13 août 2013 à 3:52

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s