Le but de cet article n'est pas de préciser comment coder du réseau en C, mais de comprendre ce qu'on fait. Pour rappel, en substance, TCP permet d'envoyer des données et de les recevoir dans l'ordre et sans perte. IP sert à donner des adresses à travers des réseaux.

Les grandes étapes

Alors, coder un client réseau via les sockets en C repose sur quatre phases :

  • socket : créer un outil d'entrée sortie dans lequel on peut lire et écrire. On ne fait que l'enregistrer, on ne le relie pas au réseau
  • bind : on relie l'outil d'entrée sortie à un réseau, et donc à une interface réseau. Par exemple, les machines d'entreprise peuvent disposer de deux cartes réseau, une ouverte vers l'extérieur, l'autre non. Si vous voulez avoir une chance de vous connecter à l'internet, il faut trouver la bonne carte. Et il se trouve que c'est vrai en général : il faut préciser quelle interface réseau sera sollicitée.
  • connecter la socket (donc en gros ce sur quoi les read, write et close sont définis) à sa destination. Par exemple, si vous voulez lire une page web sur ce site, il vous faut préciser à la socket que l'adresse de la cible est bien celle du site.
  • lire écrire et fermer la socket. La partie la plus facile. On libère les ressources.

Pour ce qui est du code, on a cet article. Le but de celui ci est de rentrer un peu plus dans les détails pour les opérations de base.

Socket

Le but de l'opération est d'ouvrir un file descriptor et de l'enregistrer auprès du système. Dit comme ça, c'est difficile à appréhender, mais le principe est simple : un file descriptor est un lien vers une "interface" (au sens objet) sur laquelle on peut lire de la donnée, écrire de la donnée, et qui est fermée par un close après usage. Donc, après socket, on a une telle "interface". Si on l'appelle ainsi :


socket(AF_INET6,SOCK_STREAM,0)

On peut se demander en quoi on aurait besoin de savoir si on est en IPV6 et en TCP. Et bien, une socket va envoyer des données sur le réseau, et il se trouve qu'on n'envoie pas le texte brut ainsi, on le décore avec des informations de protocole. La socket va donc écrire les en têtes des protocoles concernés au fur et à mesure qu'on descend dans les couches réseau. Et ça, croyez le bien, c'est très appréciable.

Bind

Alors, tout cela est bel et bon, mais vous allez avoir un petit souci à lire et écrire dans le file descriptor. Sans aucune précision de votre part, celui ci n'est pas relié à votre carte réseau. Il faut donc le faire, avec un bind. C'est en fait un peu plus compliqué que ça : une interface réseau (vis a vis de TCP/IP) est une structure physique ou logicielle qui peut envoyer des données sur un réseau. Chaque interface a une adresse réseau donné par celui-ci. On peut les lister avec ifconfig à la ligne de commande, et getifaddrs avec le code. Chaque interface (par exemple lo) dispose d'adresses. Il y a par exemple eth0 qui est souvent l'interface qui relie l'ordinateur au câble réseau qui vous relie à la box de votre fournisseur d'accès. Il s'agit de trouver la bonne adresse, ou la bonne interface, qui puisse se connecter sur un réseau extérieur. Le lien entre adresse et interface est simple : la structure ifaddrs qui représente une iterface contient un champ qui est l'adresse de celle ci. 

Connect

Alors, une connexion réseau sur TCP/IP sur une adresse extérieure se base sur une communication où il faut fixer la source (avec le bind) et la destination (avec le connect). Donc, il faut une adresse. Et pour avoir une adresse, donc une struct sockaddr. Et donc, il est nécessaire de convertir un nom symbolique, comme par exemple www.google.fr en une adresse IP que la machine comprend. Si la fonction gethostbyname est très simple, elle ne doit plus être utilisée puisqu'elle est devenue obsolète. On prend à la place getaddrinfo :

/**
 * Gethostname is very useful but deprecated. We decorate getaddrinfo which usage is complex
 * to find, given an host and a port, a list of socket addresses that would allow the connection. 
 * @param hostname the hostname to find. For instance, www.google.fr
 * @param port the port number 
 * @param afinet_version AF_INET, AF_INET6 are the two possible options
 * @return NULL for no matching, or NULL parameters. Otherwise, an addrinfo which is a sort of linked list
 */
static struct addrinfo * nets_findAddresses(const char * hostname, int port, int afinet_version) {
    if (!hostname || port < 0 || (afinet_version != AF_INET && afinet_version != AF_INET6)) return NULL;
    // first, set service to the port number 
    char serviceBuffer[15];
    for (int i = 0; i < 15; i++) serviceBuffer[i] = '\0';
    sprintf(serviceBuffer, "%d", port);
    int size = strlen(serviceBuffer);
    char service[sizeof (char) * (size + 1)];
    for (int i = 0; i <= size; i++) service[i] = '\0';
    strncpy(service, serviceBuffer, size);
    // remember that gethostbyname is deprecated, so use getaddrinfo
    struct addrinfo hints;
    hints.ai_addr = NULL;
    hints.ai_addrlen = 0;
    hints.ai_canonname = NULL;
    hints.ai_family = afinet_version; 
    hints.ai_flags = AI_NUMERICSERV; // service contains a port number 
    hints.ai_next = NULL;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_socktype = SOCK_STREAM;
    struct addrinfo * result = NULL;
    int addrresult = getaddrinfo(hostname, service, &hints, &result);
    if (0 != addrresult) {
        fprintf(stderr, "%s", gai_strerror(addrresult));
        if (result != NULL) freeaddrinfo(result);
        result = NULL;
    }
    return result;
}

Close

L'opération de fermeture de flux est générique, c'est à dire qu'elle s'applique à l'interface d'entrée sortie, que ce soit un fichier ou une socket.

 

 

Comments are closed.

Set your Twitter account name in your settings to use the TwitterBar Section.