Premiers pas avec les sockets TCP

Introduction

Les sockets permettent d'établir une connexion TCP/IP entre 2 programmes. Il n'y a pas de restriction sur la localisation des programmes et les 2 programmes peuvent très bien se situer sur la même machine (localhost=127.0.0.1). Ce qu'il est important de comprendre est qu'une connexion TCP/IP est identifiée par les 4 données suivantes :

On parle souvent de client et de serveur. Pour fixer les esprits, dans le cas du protocole HTTP, le client est votre navigateur Web (Internet Explorer, Netscape ...), et le serveur est le serveur Web.

Les valeurs de ports communes sont 80 pour HTTP, 21 pour FTP, 25 pour SMTP (Envoi d'emails), 110 pour POP3 (Réception d'emails).

Les sockets sont aux connexions ce que les handles sont au fichiers. Une fois que la connexion est établie entre le client et le serveur, ceux-ci peuvent échanger des données en utilisant les appels recv() et send().

Dans la suite, on présente l'utilisation des sockets bloquantes ce qui reste l'utilisation la plus simple et la moins performante, mais qui est suffisante pour faire quelques tests.

Client : Connexion à un serveur (Mode bloquant)

Variables nécessaires :

    SOCKET hSocket;
    struct sockaddr addrConnect;
    int nPort = 1234;
    char * pszHost = "127.0.0.1"; // ou = "localhost" (cad on se connecte sur notre machine)

Pour se connecter à un serveur, il faut déja créer une socket :

	hSocket = socket( PF_INET, SOCK_STREAM, 0 );

On peut éventuellement se lier à l'adresse locale. Sinon, cela est fait automatiquement au moment de la connexion (appel connect())

    /*  Liaison avec le port local (ici 0)  */
    addr.sin_family = AF_INET ;
    addr.sin_addr.s_addr = htonl (INADDR_ANY);
    addr.sin_port = htons ((unsigned short)0 ); // 0, cad qu'on laisse le système choisir un port
    if ( bind( hSocket, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR )
    {
        printf( "socket() error (%d)\n", SOCKET_ERRNO );
        return -1;
    }

On peut utiliser la fonction suivante TcpFormatAdress()afin de convertir une adresse de destination en structure struct sockaddr :

static struct sockaddr TcpFormatAdress( char * host, u_short port )
{
    struct sockaddr_in addr;
    struct sockaddr addrRet;
    struct hostent FAR *lphost ;
    u_long IP;


    memset((char*)&addr, 0, sizeof(addr));
    /*	Soit on fournit une adresse IP, soit on fournit un nom	*/
    if ((IP = inet_addr(host)) == (u_long)INADDR_NONE)
    {
        if ((lphost  = gethostbyname(host))==NULL)
        {
            memset( (char * )&addrRet, 0, sizeof(addrRet) );
            return  addrRet;
        }
        addr.sin_family = lphost->h_addrtype;
#ifdef _WIN16 /* A définir dans le projet WIN16	*/
        _fmemcpy (&addr.sin_addr, lphost->h_addr, lphost->h_length);
#else /*	WIN32, UNIX*/
        memcpy (&addr.sin_addr, lphost->h_addr, lphost->h_length);
#endif
    }
    else
    {
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = IP;
    }

    /*  Port destination    */    
    addr.sin_port = htons((u_short)port );    

    memcpy( (char *)&addrRet, (char *)&addr, sizeof(addrRet) );
    return addrRet;
}

Puis on se connecte avec connect() :

    /*	Soit on fournit une adresse IP, soit on fournit un nom	*/
    addrConnect = TcpFormatAdress( pszHost, (u_short)nPort );

    /*  Mode bloquant  */
    if( connect( hSocket, &addrConnect, sizeof(addrConnect) ) == SOCKET_ERROR )
    {
        printf( "connect() error (%d)\n", SOCKET_ERRNO );
        return -1;
    }

On peut maintenant utiliser la socket hSocket pour lire et écrire des données sur le réseau.

Serveur : Attente d'un client (Mode bloquant)

Variables nécessaires :

    SOCKET hSocket, hAccept;
    struct sockaddr_in addr;
    int len = sizeof(addr);    
    int nPort = 1234;

On commence par créer une socket :

    hSocket = socket( PF_INET, SOCK_STREAM, 0 );
    if( hSocket == INVALID_SOCKET )
    {
        printf( "socket() error %d\n", SOCKET_ERRNO );
        exit(1);
    }

Puis on écoute le port :

    addr.sin_family = AF_INET ;
    addr.sin_addr.s_addr = htonl (INADDR_ANY);
    addr.sin_port = htons ((unsigned short)nPort );
    if ( bind( hSocket, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR )
    {
        printf( "bind() error %d\n", SOCKET_ERRNO );
        exit(1);
    }

On indique que cette socket va servir à accepter des connexions

    // 100 <=> 100 connexions simultanées possible (backlog)
    if ( listen( hSocket, 100) == SOCKET_ERROR )
    {
        printf( "listen() error %d\n", SOCKET_ERRNO );
        exit(1);
    }

On attend qu'un client se présente : Tant qu'aucun client ne se présenté, on reste bloqué dans accept() (Mode bloquant)

    hAccept = accept(hSocket, NULL, NULL);

On utilise ensuite la nouvelle socket hAccept pour lire et écrire des données sur le réseau.

Client et serveur : Envoi /réception de données (Mode bloquant)

Pour envoyer des données, on utilise send().

    cb = sprintf( Buffer, "coucou" );
    cb = send( hAccept, Buffer, cb, 0 );
    if( cb == SOCKET_ERROR )
    {
        printf( "send() error %d\n", SOCKET_ERRNO );
        exit(1);
    }

Pour recevoir des données, on utilise recv(). Cette fonction bloque tant que l'on n'a pas reçu de données. Une valeur reçue inférieur ou égale à 0 octets peut être considérée comme une déconnexion.

    cb = recv( hAccept, Buffer, sizeof(Buffer), 0 );
    if( cb <= 0 )
    {
        printf( "recv() error %d\n", SOCKET_ERRNO );
        exit(1);
    }

closesocket()

Lorsqu'on en a fini avec la socket, on doit la fermer avec closesocket().

Code

liens

Valid XHTML 1.0!