# Les sockets en C

description : Utilisation des sockets en langage C
categories : Coding; Networking;
tags : C; Linux;

Utilisation des sockets en langage C

Dans cet article, nous allons voir comment utiliser les sockets en langage C.

Qu'est ce qu'une socket ?

Une socket, c'est un endpoint, soit un point de terminaison permettant la communication entre deux processus.

Une socket, c'est donc un point de terminaison permettant d'envoyer ou de recevoir des données entre un client et un serveur.

Le serveur étant le poste informatique qui fournit le service, comme par exemple le service http qui se trouve par convention sur le port 80. Et le client étant le poste qui va consommer ou utiliser ce service.

Pour pouvoir communiquer entre eux, une socket est créée sur le serveur, celle ci est configurée de façon à écouter sur un port, donc à attendre les connexions sur ce port.

Sur le client, la socket est créée et utilisée pour se connecter au port du service. La connexion se fait en configurant la socket avec l'IP du serveur ainsi que le port du service sur lequel se connecter.

Ensuite l'échange, le dialogue ou la communication entre le client et le serveur sur le réseau se fait par paquets au moyen d'une suite de protocole comme TCP/IP et UDP/IP.

Qu'est ce qu'un protocole ?

Dans l'informatique, un protocole réseau est un ensemble de règles standard permettant de communiquer.

Internet repose principalement sur les protocoles TCP/IP et UDP/IP.

Qu'est ce que le protocole IP

Le protocole IP pour Internet Protocol est le protocole de routage. Il est en charge de l'acheminement des paquets.

Le routage c'est le moyen avec l'aide d'algorithme de trouver pour la source la meilleure route pour accéder à la destination.

Pour plus d'information sur le protocole IP, vous pouvez consulter la RFC 791 : https://www.rfc-editor.org/rfc/rfc791.html

Qu'est ce que le protocole TCP

Le protocole TCP pour Transmission Control Protocol fournit tout en s'appuyant sur le protocole IP un service de remise de paquets de façon contrôlé, donc fiable.

Il se charge entre autre de l'établissement et de la libération d'une connexion, du transfert des données, de la corrections d'erreurs et du contrôle de ces données.

La RFC 973 concernant le protocole TCP se trouve ici : https://www.rfc-editor.org/rfc/rfc793.html

Qu'est ce que le protocole UDP

Le protocole UDP qui signifie User Datagram Protocol et qui s'appuie également sur le protocole IP permet la remise de paquets de façon non contrôlé et donc non fiable.

Par exemple, il ne garantit, ni la remise, ni l'ordre des paquets délivrés et il ne procède pas à de contrôle sur les données échangées.

Vous pouvez consulter la RFC UDP, la RFC 768 ici : https://www.rfc-editor.org/rfc/rfc768.html

Qu'est ce que IPv4 et IPv6

IPv4 et IPv6 sont deux versions différentes du protocole IP.

Sur la version 4 du protocole, nous utilisons des adresses IP codées sur 32 bits. Sur la version 6, nous utilisons des adresses IP codées sur 128 bits.

Une adresse IPv4 est composée de quatre octets / quatre bytes (4 * 8 = 32). Les octets sont séparés par des points et chacun d'eux a sa valeur comprise entre 0 et 255. Exemple d'adresse IPv4 : 216.58.214.163 qui correspond à l'une des adresses IPv4 de google.fr.

Une adresse IPv6 est composée de huits groupes de 4 chiffres hexadécimaux de 16 bits (8 * 16 = 128). Les groupes sont séparés par des deux-points. Exemple d'adresse IPv6 : 2a00:1450:4007:818::2003 qui correspond à l'une des adresses IPv6 de google.fr.

Pour les adresses IPv6, les zeros inutilent peuvent être omis. Exemple : l'adresse IPv6 2001:0db8:0000:0000:0000:ff00:0042:8329 est égale à l'adresse IPv6 2001:db8::ff00:42:8329.

Les structures de données

La première structure à connaître se nomme struct addrinfo https://www.man7.org/linux/man-pages/man3/getaddrinfo.3.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct addrinfo {
    int              ai_flags;
    int              ai_family;     // AF_INET, AF_INET6 or AF_UNSPEC
    int              ai_socktype;   // SOCK_STREAM or SOCK_DGRAM
    int              ai_protocol;   // 0 (auto), or IPPROTO_TCP, IPPROTO_UDP
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

La deuxième concerne uniquement l'IPv4 et elle se nomme struct sockaddr_in https://www.man7.org/linux/man-pages/man7/ip.7.html

1
2
3
4
5
6
7
8
9
struct sockaddr_in {
        short int          sin_family;  // Famille d'adresse, AF_INET
        unsigned short int sin_port;    // Numéro du port
        struct in_addr     sin_addr;    // Adresse internet IPv4
};

struct in_addr {
        uint32_t s_addr; // Adresse IPv4 en 32-bit unsigned int
};

La troisième, c'est l'inverse, elle concerne uniquement le protocole IPv6 et elle se nomme struct sockaddr_in6 https://www.man7.org/linux/man-pages/man7/ipv6.7.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct sockaddr_in6 {
        u_int16_t       sin6_family;   // Famille d'adresse, AF_INET6
        u_int16_t       sin6_port;     // Numéro du port
        u_int32_t       sin6_flowinfo; // IPv6 flow information
        struct in6_addr sin6_addr;     // Adresse internet IPv6
        u_int32_t       sin6_scope_id; // Scope ID
};
    
struct in6_addr {
        unsigned char   s6_addr[16];   // Adresse IPv6
};

Ces deux structures : struct sockaddr_in6 et struct sockaddr_in peuvent être converties par cast en struct sockaddr. À l'inverse, une structure struct sockaddr peut être convertie par cast en struct sockaddr_in ou struct sockaddr_in6.

1
2
3
4
struct sockaddr {
    unsigned short  sa_family;      // Famille d'adresse
    char            sa_data[14];    // Padding
};

La dernière structure est utilisée lorsque l'on ne connait pas en avance la version du protocole utilisée par une socket. Elle peut contenir les informations d'adresse IPv4 et d'adresse IPv6, c'est la structure struct sockaddr_storage.

1
2
3
4
5
6
7
8
struct sockaddr_storage {
    sa_family_t  ss_family; // address family

    // all this is padding, implementation specific, ignore it:
    char    __ss_pad1[_SS_PAD1SIZE];
    int64_t __ss_align;
    char    __ss_pad2[_SS_PAD2SIZE];
};

Comment créer une socket

Voici la fonction permettant de créer une socket : socket()

int socket(int domain, int type, int protocol);

Pour de l'IPv4 et le protocole TCP, nous utiliserons :

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

Pour l'IPv4 et le protocole UDP, ce sera :

int sock = socket(AF_INET, SOCK_DGRAM, 0);

Pour de l'IPv6, il suffi de changer AF_INET en AF_INET6.

Comment convertir un nom de domaine en IP

Pour convertir un nom de domaine en IP et pouvoir s'y connecter, il faut utiliser la fonction getaddrinfo()

1
2
int getaddrinfo(const char *nodename, const char *servname,
                const struct addrinfo *hints, struct addrinfo **res);

Elle s'utilise de cette façon :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    struct addrinfo *result, *ptr;
    char ipstr[INET6_ADDRSTRLEN];

    if (argc != 2)
    {
        fprintf(stderr, "usage : %s <domain>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */

    if (getaddrinfo(argv[1], NULL, &hints, &result) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    printf("Adresses IP pour : %s\n", argv[1]);

    for (ptr = result; ptr != NULL; ptr = ptr->ai_next)
    {
        void *addr;
        char *ipver;

        if (ptr->ai_family == AF_INET)
        { // IPv4
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)ptr->ai_addr;
            addr = &(ipv4->sin_addr);
            ipver = "IPv4";
        }

        else
        { // IPv6
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ptr->ai_addr;
            addr = &(ipv6->sin6_addr);
            ipver = "IPv6";
        }

        // Converti l'IP en string
        inet_ntop(ptr->ai_family, addr, ipstr, INET6_ADDRSTRLEN);

        // Affiche l'IP
        printf("\t %s: %s\n", ipver, ipstr);
    }

    freeaddrinfo(result);

    return 0;
}

Ce qui sur le domaine google.fr, nous donnes :

1
2
3
4
5
$ gcc socket-getaddrinfo.c -o socket-getaddrinfo -Wall
$ ./socket-getaddrinfo google.fr
Adresses IP pour : google.fr
         IPv4: 216.58.201.227
         IPv6: 2a00:1450:4007:810::2003

Comment se connecter, envoyer et recevoir en TCP

Pour réaliser une connexion TCP, c'est la fonction connect()

1
2
int connect(int sockfd, const struct sockaddr *addr,
            socklen_t addrlen);

Pour envoyer des données sur une socket en TCP, c'est la fonction send()

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Puis, pour recevoir des données d'une socket en TCP, c'est recv()

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

Pour démontrer ces trois fonctions : connect(), send() et recv(), je vais écrire du code permettant de se connecter et de s'enregistrer sur un serveur IRC. Le serveur utilisé dans l'exemple est celui de libera.chat

Voici la RFC du protocole IRC : https://www.rfc-editor.org/rfc/rfc1459.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    int rv;
    struct addrinfo *servinfo, *ptr;
    int sock;
    char buffer[1024 + 1];

    if (argc != 4)
    {
        fprintf(stderr, "usage : %s <domain> <port> <nickname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */

    if ((rv = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats, crée la socket et se connect
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed : %s\n", strerror(errno));
            continue;
        }

        if (connect(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "connect() failed : %s\n", strerror(errno));
            close(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to connect\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    printf("info : connected !\n");

    sprintf(buffer, "NICK %s\r\nUSER %s 0 * :%s\r\n", argv[3], argv[3], argv[3]); // Voir la RFC IRC

    if (send(sock, buffer, strlen(buffer), 0) == -1)
    {
        fprintf(stderr, "send() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    for (;;)
    {
        rv = recv(sock, buffer, 1024, 0);

        if (rv == -1)
        {
            fprintf(stderr, "recv() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (rv == 0) break;

        buffer[rv] = 0;

        printf("%s", buffer);
    }

    close(sock);

    return 0;
}

Ce code permet donc de se connecter sur le serveur IRC dont l'adresse est donnée en argument numéro un et le port en argument numéro deux.

Il permet également de s'enregistrer sur ce serveur IRC en tant qu'utilisateur dont le pseudonyme est donné en argument numéro trois.

Après s'être connecté et s'être enregistré sur le serveur IRC, ce code affiche ce que le serveur IRC nous envoit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ gcc socket-connect.c -o socket-connect -Wall
$ ./socket-connect irc.libera.chat 6667 sysb0t
info : connected !
:calcium.libera.chat NOTICE * :*** Checking Ident
:calcium.libera.chat NOTICE * :*** Looking up your hostname...
:calcium.libera.chat NOTICE * :*** Couldn't look up your hostname
:calcium.libera.chat NOTICE * :*** No Ident response
:calcium.libera.chat 001 sysb0t :Welcome to the Libera.Chat Internet Relay Chat Network sysb0t
~~~ bla bla bla ~~~
:calcium.libera.chat 376 sysb0t :End of /MOTD command.
~~~ bla bla bla ~~~
PING :calcium.libera.chat
:sysb0t!~sysb0t@IP.IP.IP.IP QUIT :Ping timeout: 250 seconds
ERROR :Closing Link: IP.IP.IP.IP (Ping timeout: 250 seconds)

Note : IP.IP.IP.IP correspond à mon IPv4.

Je transforme donc le nom de domaine "irc.libera.chat" en IP. Je boucle sur les adresses IP obtenues, et à chaque tour, je crée une socket et tente de me connecter à l'IP sur le port "6667". Une fois connecté, je m'enregistre sur le serveur IRC en tant qu'utilisateur "sysb0t".

Tout ce que je reçois du serveur sur la socket est affiché sur la sortie standard de la console.

Pour fermer proprement la socket j'utilise la fonction close()

Dans l'output affiché dans la console, nous trouvons cette ligne intéressante : PING :calcium.libera.chat. Cette ligne correspond en fait à quelque chose de bien connue en réseau. Lorsqu'un serveur veut être certain de la situation de la socket ou de la connexion avec un client, il PING ce client et attend durant un certain temps de recevoir le PONG qui va avec. Si le serveur ne reçoit pas ce PONG comme dans notre cas, le serveur considère la socket comme non valide et cette socket se retrouve fermée. Pour rester connecté à ce serveur IRC, le client aurait du envoyer "PONG :calcium.libera.chat" en retour du ping.

Comment envoyer et recevoir en UDP

Avec le protocole UDP il n'y a pas de connexion comme avec le protocole TCP. Il n'y a donc pas de fonction connect().

Pour envoyer des données avec le protocole UDP, c'est la fonction sendto()

1
2
3
ssize_t sendto(int socket, const void *message, size_t length,
               int flags, const struct sockaddr *dest_addr,
               socklen_t dest_len);

Pour recevoir des données en UDP, c'est recvfrom()

1
2
3
ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
                 int flags, struct sockaddr *restrict address,
                 socklen_t *restrict address_len);

Pour illustrer ces deux fonctions, je vais écrire deux codes : un serveur qui va attendre un paquet de données et un client qui va envoyer le paquet de données. Dans cet exemple, le paquet de données sera un simple message, une simple chaîne de caractères donnée en argument au client.

Notes : Pour des échanges de messages, pour être certain d'avoir le message au complet. il est préférable d'utiliser le protocole TCP.
Un exemple d'application où il est préférable d'utiliser le protocole UDP sont les applications de streaming audio et/ou vidéo en temps réél. Dans ce genre d'application, il est préférable d'abandonner des paquets plutôt que d'attendre des paquets retardés en raison du délai de la tranmission. Nous parlons de latence

Le serveur qui va recevoir le message :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int port;
    struct sockaddr_in sin;
    int sock;
    struct sockaddr_in csin; /* client */
    socklen_t csin_size;
    int rv;
    char buffer[1024 + 1];

    if (argc != 2)
    {
        fprintf(stderr, "usage : %s <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    errno = 0;
    port = strtol(argv[1], NULL, 10);

    if (errno != 0)
    {
        perror("strtol()");
        exit(EXIT_FAILURE);
    }

    memset(&sin, 0, sizeof(struct sockaddr_in));

    sin.sin_addr.s_addr = htonl(INADDR_ANY); /* my IPv4 address */
    sin.sin_port = htons(port);
    sin.sin_family = AF_INET; /* IPv4 only */

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("socket()");
        exit(EXIT_FAILURE);
    }

    if (bind(sock, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
    {
        perror("bind()");
        exit(EXIT_FAILURE);
    }

    memset(&csin, 0, sizeof(struct sockaddr_in));
    csin_size = sizeof(struct sockaddr_in);

    if ((rv = recvfrom(sock, buffer, 1024, 0, (struct sockaddr *)&csin, &csin_size)) == -1)
    {
        perror("recvfrom()");
        exit(EXIT_FAILURE);
    }

    buffer[rv] = 0;

    printf("received from UDP socket : %s\n", buffer);

    return 0;
}

Le client qui va envoyer le message :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int port;
    struct sockaddr_in sin;
    int sock;

    if (argc != 4)
    {
        fprintf(stderr, "usage : %s <ip> <port> <message>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    errno = 0;
    port = strtol(argv[2], NULL, 10);

    if (errno != 0)
    {
        perror("strtol()");
        exit(EXIT_FAILURE);
    }
    
    memset(&sin, 0, sizeof(struct sockaddr_in));

    sin.sin_addr.s_addr = inet_addr(argv[1]);
    sin.sin_port = htons(port); 
    sin.sin_family = AF_INET; /* IPv4 only */

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("socket()");
        exit(EXIT_FAILURE);
    }

    if (sendto(sock, argv[3], strlen(argv[3]), 0, (struct sockaddr *)&sin, sizeof(struct sockaddr)) == -1)
    {
        perror("sendto()");
        exit(EXIT_FAILURE);
    }

    return 0;
}

Je commence par exécuter le premier code qui va attendre de recevoir le message :

1
2
$ gcc socket-udp-recvfrom.c -o socket-udp-recvfrom -Wall
$ ./socket-udp-recvfrom 8080

Puis, j'exécute le client qui va envoyer le message :

1
2
3
$ gcc socket-udp-sendto.c -o socket-udp-sendto -Wall
$ ./socket-udp-sendto 127.0.0.1 8080 "hello world"      
$ 

L'output du premier code qui recoit le message :

1
2
3
$ ./socket-udp-recvfrom 8080
received from UDP socket : hello world
$ 

Comment accepter des connexions en TCP

Pour recevoir et accepter des connexions sur un port donné en TCP, il faut utiliser les fonctions bind() avec listen() et accept() :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>

#define BACKLOG 1
#define MESSAGE "Hello World !\n"

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    struct addrinfo *servinfo, *ptr;
    int rv;
    int sock, new_sock;
    int yes = 1;
    struct sockaddr_storage their_addr;
    socklen_t addr_size;

    if (argc != 2)
    {
        fprintf(stderr, "usage : %s <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */
    hints.ai_flags = AI_PASSIVE;        /* For wildcard IP address */

    if ((rv = getaddrinfo(NULL, argv[1], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats, crée la socket et se bind
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed : %s\n", strerror(errno));
            continue;
        }

        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
        {
            fprintf(stderr, "setsockopt() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (bind(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "bind() failed : %s\n", strerror(errno));
            close(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to bind\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    if (listen(sock, BACKLOG) == -1)
    {
        fprintf(stderr, "listen() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    printf("info : listening !\n");

    addr_size = sizeof(struct sockaddr_storage);
    
    if ((new_sock = accept(sock, (struct sockaddr *)&their_addr, &addr_size)) == -1)
    {
        fprintf(stderr, "accept() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    if (send(new_sock, MESSAGE, strlen(MESSAGE), 0) == -1)
    {
        fprintf(stderr, "send() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // traitement : envoi et reception

    close(new_sock);
    close(sock);
    
    return 0;
}

Dans ce code, le port sur lequel écouter est donné en argument numéro un :

1
2
3
$ gcc socket-listen.c -o socket-listen -Wall
$ ./socket-listen 8080
info : listening !

Pour m'y connecter je vais utiliser l'outil telnet :

1
2
3
4
5
6
7
$ telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello World !
Connection closed by foreign host.
$ 

Je me connecte avec telnet sur l'IPv4 127.0.0.1 et le port 8080.

Je reçois bien le MESSAGE : "Hello World !\n" envoyé depuis le serveur. Le client telnet se ferme ensuite, car côté serveur, une fois le MESSAGE envoyé, nous fermons les deux sockets et nous quittons.

Nous imaginons donc un quelconque traitement entre le client et le serveur sur new_sock à base de send() et de recv(), puis d'analyse ou de "parsing" sur les données échangées.

Dans cet exemple, sur le serveur, la fonction accept() est dîte "bloquante". Le programme s'arrête donc sur cette fonction jusqu'a ce qu'un client se connecte.

Le serveur accepte donc une seule connexion, dialogue avec le client depuis cette connexion sur "new_sock", puis lorsque ce dialogue prend fin sur "new_sock", le serveur s'arrête. Il n'est donc plus possible de s'y connecter.

Il est parfois nécessaire de continuer à attendre une nouvelle connexion voir d'accepter plusieurs clients simultanéments.

Les sockets et les threads

Avec les sockets, la programmation multithread est une technique utilisée pour permettre de boucler sur la fonction accept() et de traiter en même temps les connexions déjà existantes avec des clients. Dès qu'un client se connecte au serveur, un nouveau thread est donc créé permettant les interactions entre le serveur et ce client.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#define MESSAGE "Hello World !\n"

void * thread_start(void *arg);

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    struct addrinfo *servinfo, *ptr;
    int rv;
    int sock, new_sock;
    int yes = 1;
    struct sockaddr_storage their_addr;
    socklen_t addr_size;
    pthread_t thread_id;

    if (argc != 2)
    {
        fprintf(stderr, "usage : %s <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */
    hints.ai_flags = AI_PASSIVE;        /* For wildcard IP address */

    if ((rv = getaddrinfo(NULL, argv[1], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats pour s'y "bind"
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed : %s\n", strerror(errno));
            continue;
        }

        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
        {
            fprintf(stderr, "setsockopt() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (bind(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "bind() failed : %s\n", strerror(errno));
            close(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to bind\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    if (listen(sock, SOMAXCONN) == -1)
    {
        fprintf(stderr, "listen() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    printf("info : listening !\n");

    for (;;)
    {
        addr_size = sizeof(struct sockaddr_storage);

        if ((new_sock = accept(sock, (struct sockaddr *)&their_addr, &addr_size)) == -1)
        {
            fprintf(stderr, "accept() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (pthread_create(&thread_id, NULL, thread_start, &new_sock) != 0)
        {
            fprintf(stderr, "pthread_create() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    close(sock);

    return 0;
}

void * thread_start(void *arg)
{
    int sock = *((int *)arg);
    int rv;
    char buffer[1024 + 1];

    printf("info : new connection... new thread\n");

    if (send(sock, MESSAGE, strlen(MESSAGE), 0) == -1)
    {
        fprintf(stderr, "send() failed : %s\n", strerror(errno));
        close(sock);
        return NULL;
    }

    for (;;)
    {
        rv = recv(sock, buffer, 1024, 0);

        if (rv == -1)
        {
            fprintf(stderr, "recv() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (rv == 0) break;

        buffer[rv] = 0;

        printf("%s", buffer);
    }
    
    close(sock);

    return NULL;
}

Je compile socket-listen-pthread.c et je run le binaire obtenu pour écouter sur le port 8080. J'utilise encore telnet comme client pour me connecter trois fois sur ce port 8080. À la troisième connexion, j'envoie en écrivant dans le terminal de telnet et en validant avec la touche entrer la chaîne de caractère "w00t \o/". J'utilise les touches "CTRL+C" sur le terminal du serveur pour fermer le serveur et ainsi fermer toutes les sockets :

Le terminal utilisé pour socket-listen-pthread :

1
2
3
4
5
6
7
8
9
$ gcc socket-listen-pthread.c -o socket-listen-pthread -Wall -lpthread
$ ./socket-listen-pthread 8080
info : listening !
info : new connection... new thread
info : new connection... new thread
info : new connection... new thread
w00t \o/
^C
$ 

Le terminal utilisé pour la troisième connexion avec telnet :

1
2
3
4
5
6
7
8
$ telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello World !
w00t \o/
Connection closed by foreign host.
$ 

Plusieurs clients, ici, trois comme exemple peuvent donc être connectés au serveur simultanément. Aussi, le serveur reste en écoute sur le port 8080 jusqu'au "CTRL+C".

Les sockets et la fonction select()

Tout comme la fonction accept() la fonction recv() est dîte "bloquante". Ce qui signifie qu'à l'appel de la fonction recv() dans le programme, si il n'y a pas de données à recevoir de la socket, ce programme se retrouve alors bloqué jusqu'à recevoir des données. Durant ce temps d'attente, le programme se retrouve donc bloqué.

Il est donc parfois nécessaire de savoir en avance s'il y a ou non des données à recevoir. Ceci peut ce faire grâce à la fonction select()

Nous l'utilisons de cette façon :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    int rv;
    struct addrinfo *servinfo, *ptr;
    int sock;
    char buffer[1024 + 1];
    fd_set rfds;
    struct timeval tv;
    int retval;

    if (argc != 4)
    {
        fprintf(stderr, "usage : %s <domain> <port> <nickname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */

    if ((rv = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats, crée la socket et se connect
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed : %s\n", strerror(errno));
            continue;
        }

        if (connect(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "connect() failed : %s\n", strerror(errno));
            close(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to connect\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    printf("info : connected !\n");

    sprintf(buffer, "NICK %s\r\nUSER %s 0 * :%s\r\n", argv[3], argv[3], argv[3]);

    if (send(sock, buffer, strlen(buffer), 0) == -1)
    {
        fprintf(stderr, "send() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    for (;;)
    {
        FD_ZERO(&rfds);
        FD_SET(sock, &rfds);

        tv.tv_sec = 8;
        tv.tv_usec = 0;

        retval = select(sock + 1, &rfds, NULL, NULL, &tv);

        if (retval < 0)
        {
            fprintf(stderr, "select() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (retval > 0)
        {
            rv = recv(sock, buffer, 1024, 0);

            if (rv == 0) break;

            if (rv == -1)
            {
                fprintf(stderr, "recv() failed : %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }

            buffer[rv] = 0;

            printf("%s", buffer);
        }

        else
        {
            printf("info : aucune donnée dans la socket pour le moment...\n");
        }
    }

    close(sock);

    return 0;
}

Compilation et exécution de ce code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ gcc socket-select.c -o socket-select -Wall
$ ./socket-select irc.libera.chat 6667 sysb0t
info : connected !
:tantalum.libera.chat NOTICE * :*** Checking Ident
:tantalum.libera.chat NOTICE * :*** Looking up your hostname...
:tantalum.libera.chat NOTICE * :*** Couldn't look up your hostname
:tantalum.libera.chat NOTICE * :*** No Ident response
:tantalum.libera.chat 001 sysb0t :Welcome to the Libera.Chat Internet Relay Chat Network sysb0t
~~~ bla bla bla ~~~
:tantalum.libera.chat 376 sysb0t :End of /MOTD command.
:sysb0t MODE sysb0t :+iw
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
~~~ bla bla bla ~~~
PING :tantalum.libera.chat
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
~~~ bla bla bla ~~~
:sysb0t!~sysb0t@IP.IP.IP.IP QUIT :Ping timeout: 256 seconds
ERROR :Closing Link: IP.IP.IP.IP (Ping timeout: 256 seconds)
$ 

Note : IP.IP.IP.IP correspond à mon IPv4.

Les sockets et la fonction poll()

La fonction poll() permet de faire différemment la même chose que la fonction select() :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <poll.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    int rv;
    struct addrinfo *servinfo, *ptr;
    int sock;
    char buffer[1024 + 1];
    struct pollfd pfds[1];

    if (argc != 4)
    {
        fprintf(stderr, "usage : %s <domain> <port> <nickname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */

    if ((rv = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats, crée la socket et se connect
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed : %s\n", strerror(errno));
            continue;
        }

        if (connect(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "connect() failed : %s\n", strerror(errno));
            close(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to connect\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    printf("info : connected !\n");

    sprintf(buffer, "NICK %s\r\nUSER %s 0 * :%s\r\n", argv[3], argv[3], argv[3]);

    if (send(sock, buffer, strlen(buffer), 0) == -1)
    {
        fprintf(stderr, "send() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    pfds[0].fd = sock;
    pfds[0].events = POLLIN;

    for (;;)
    {
        rv = poll(pfds, 1, 8000);

        if (rv == -1)
        {
            fprintf(stderr, "poll() failed : %s\n", strerror(errno));
            exit(EXIT_FAILURE);
        }

        if (rv > 0)
        {
            rv = recv(sock, buffer, 1024, 0);

            if (rv == 0) break;

            if (rv == -1)
            {
                fprintf(stderr, "recv() failed : %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }

            buffer[rv] = 0;

            printf("%s", buffer);
        }

        else
        {
            printf("info : aucune donnée dans la socket pour le moment...\n");
        }
    }

    close(sock);

    return 0;
}

Nous obtenons donc la même chose :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ gcc socket-poll.c -o socket-poll -Wall
$ ./socket-poll irc.libera.chat 6667 sysb0t
info : connected !
:erbium.libera.chat NOTICE * :*** Checking Ident
:erbium.libera.chat NOTICE * :*** Looking up your hostname...
:erbium.libera.chat NOTICE * :*** Couldn't look up your hostname
:erbium.libera.chat NOTICE * :*** No Ident response
:erbium.libera.chat 001 sysb0t :Welcome to the Libera.Chat Internet Relay Chat Network sysb0t
~~~ bla bla bla ~~~
:erbium.libera.chat 376 sysb0t :End of /MOTD command.
:sysb0t MODE sysb0t :+iw
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
~~~ bla bla bla ~~~
PING :erbium.libera.chat
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
~~~ bla bla bla ~~~
:sysb0t!~sysb0t@IP.IP.IP.IP QUIT :Ping timeout: 240 seconds
ERROR :Closing Link: IP.IP.IP.IP (Ping timeout: 240 seconds)

Note : IP.IP.IP.IP correspond à mon IPv4.

Les sockets non bloquantes

Comme nous l'avons vu, les fonctions recv() et accept() sont des fonctions bloquantes. Ceci est le comportement par défaut de ces fonctions.

La fonction fcntl() permet entre autre de modifier ce comportement pour rendre ces fonctions non-bloquantes.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    int rv;
    struct addrinfo *servinfo, *ptr;
    int sock;
    char buffer[1024 + 1];

    if (argc != 4)
    {
        fprintf(stderr, "usage : %s <domain> <port> <nickname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */

    if ((rv = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats, crée la socket et se connect
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed : %s\n", strerror(errno));
            continue;
        }

        if (connect(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "connect() failed : %s\n", strerror(errno));
            close(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to connect\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    printf("info : connected !\n");

    if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
    {
        fprintf(stderr, "fcntl() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    sprintf(buffer, "NICK %s\r\nUSER %s 0 * :%s\r\n", argv[3], argv[3], argv[3]);

    if (send(sock, buffer, strlen(buffer), 0) == -1)
    {
        fprintf(stderr, "send() failed : %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    for (;;)
    {
        rv = recv(sock, buffer, 1024, 0);

        if (rv == -1)
        {
            if (errno == EAGAIN)
            {
                printf("info : aucune donnée dans la socket pour le moment...\n");
                sleep(4);
            }

            else
            {
                fprintf(stderr, "recv() failed : %s\n", strerror(errno));
                exit(EXIT_FAILURE);
            }
        }

        else if (rv == 0) break;

        else
        {
            buffer[rv] = 0;
            printf("%s", buffer);
        }
    }

    close(sock);

    return 0;
}

Je compile et je run :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ gcc socket-nonblock.c -o socket-nonblock -Wall
$ ./socket-nonblock irc.libera.chat 6667 sysb0t
info : connected !
info : aucune donnée dans la socket pour le moment...
:erbium.libera.chat NOTICE * :*** Checking Ident
:erbium.libera.chat NOTICE * :*** Looking up your hostname...
:erbium.libera.chat NOTICE * :*** Couldn't look up your hostname
info : aucune donnée dans la socket pour le moment...
:erbium.libera.chat NOTICE * :*** No Ident response
:erbium.libera.chat 001 sysb0t :Welcome to the Libera.Chat Internet Relay Chat Network sysb0t
~~~ bla bla bla ~~~
:erbium.libera.chat 376 sysb0t :End of /MOTD command.
:sysb0t MODE sysb0t :+iw
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
~~~ bla bla bla ~~~
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
PING :erbium.libera.chat
info : aucune donnée dans la socket pour le moment...
info : aucune donnée dans la socket pour le moment...
:sysb0t!~sysb0t@IP.IP.IP.IP QUIT :Ping timeout: 240 seconds
ERROR :Closing Link: IP.IP.IP.IP (Ping timeout: 240 seconds)
$ 

Note : IP.IP.IP.IP correspond à mon IPv4.

Les sockets sur Microsoft Windows

Jusqu'à présent, le code a été écrit pour être utilisé sur GNU/Linux. Pour pouvoir être utilisé également sur Microsoft Windows il faut effectuer quelques changements dans le code. Il faut surtout utiliser des macros prédéfinies : Pre-defined Compiler Macros

Voici le code fonctionnant sur GNU/Linux comme sur Windows :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#define closesocket(s) close(s)
#endif

int main(int argc, char *argv[])
{
    struct addrinfo hints;
    int rv;
    struct addrinfo *servinfo, *ptr;
    int sock;
    char buffer[1024 + 1];

    if (argc != 4)
    {
        fprintf(stderr, "usage : %s <domain> <port> <nickname>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

#ifdef _WIN32
    WSADATA wsaData;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        fprintf(stderr, "WSAStartup() failed\n");
        exit(EXIT_FAILURE);
    }
#endif

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;        /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;    /* TCP */

    if ((rv = getaddrinfo(argv[1], argv[2], &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo() failed\n");
        exit(EXIT_FAILURE);
    }

    // boucle sur les résultats, crée la socket et se connect
    for (ptr = servinfo; ptr != NULL; ptr = ptr->ai_next)
    {
        if ((sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1)
        {
            fprintf(stderr, "socket() failed\n");
            continue;
        }

        if (connect(sock, ptr->ai_addr, ptr->ai_addrlen) == -1)
        {
            fprintf(stderr, "connect() failed\n");
            closesocket(sock);
            continue;
        }

        break;
    }

    if (ptr == NULL)
    {
        fprintf(stderr, "info : unable to connect\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(servinfo);

    printf("info : connected !\n");

    sprintf(buffer, "NICK %s\r\nUSER %s 0 * :%s\r\n", argv[3], argv[3], argv[3]);

    if (send(sock, buffer, strlen(buffer), 0) == -1)
    {
        fprintf(stderr, "send() failed\n");
        exit(EXIT_FAILURE);
    }

    for (;;)
    {
        rv = recv(sock, buffer, 1024, 0);

        if (rv == -1)
        {
            fprintf(stderr, "recv() failed\n");
            exit(EXIT_FAILURE);
        }

        if (rv == 0) break;

        buffer[rv] = 0;

        printf("%s", buffer);
    }

    closesocket(sock);

#if _WIN32
    WSACleanup();
#endif

    return 0;
}

Ce code compile sur GNU/Linux mais aussi sur Microsoft Windows avec Visual Studio ou avec mingw-w64

Exemple dans PowerShell après compilation avec Visual Studio :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
PS C:\Users\sysc4ll\source\repos\socket-win-linux\x64\Release> .\socket-win-linux.exe irc.libera.chat 6667 sysb0t
info : connected !
:tantalum.libera.chat NOTICE * :*** Checking Ident
:tantalum.libera.chat NOTICE * :*** Looking up your hostname...
:tantalum.libera.chat NOTICE * :*** Couldn't look up your hostname
:tantalum.libera.chat NOTICE * :*** No Ident response
:tantalum.libera.chat 001 sysb0t :Welcome to the Libera.Chat Internet Relay Chat Network sysb0t
~~~ bla bla bla ~~~
:tantalum.libera.chat 376 sysb0t :End of /MOTD command.
:sysb0t MODE sysb0t :+iw
PING :tantalum.libera.chat
:sysb0t!~sysb0t@IP.IP.IP.IP QUIT :Ping timeout: 256 seconds
ERROR :Closing Link: IP.IP.IP.IP (Ping timeout: 256 seconds)
PS C:\Users\sysc4ll\source\repos\socket-win-linux\x64\Release>

Les sources du tuto

Les src du tuto se trouve sur cette URL : https://gitlab.com/sysc4ll/tuto-c-sockets