Le réseau : un chat simple.

Un exemple de découverte des fonctions réseau.

socket, bind, listen, connect, accept

Voir version :

Pas de dépendances

Télécharger :

#include<stdio.h>    
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")

void purger()
{
    int c;
    while ((c = getchar()) != '\0' && c != '\n');
}

int askport()
{
    int port;    
    printf("Port : ");
    scanf("%i",&port);
    purger();
    return port;
}

void askIP(char ip[15])
{
    printf("Ip : ");
    scanf("%s",ip);    
    purger();
}

int dialogue(SOCKET sock,int tour)
{
    int err = 0;
    char buffer[500];
    while(err>=0)
    {
        memset(buffer,0,sizeof(buffer));
        if (tour==1)
        {
            printf("A vous > ");
            scanf("%500[^\n]",buffer);
            purger();
            err = send(sock,buffer,sizeof(buffer),0);
            tour = 0;
        }
        else
        {
            printf("Attente : ");
            err = recv(sock,buffer,sizeof(buffer),0);
            printf("%s\n",buffer);
            tour = 1;
        }
    }
    return 0;
}

int serveur()
{
    SOCKET sock_attente;    
    SOCKET sock_dialogue;
    SOCKADDR_IN csin;
    int len;
    char buffer[50];
    int err=0;
    int port = askport();
    csin.sin_family=AF_INET;
    csin.sin_addr.s_addr=htonl(INADDR_ANY);
    csin.sin_port=htons(port);
    sock_attente = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (sock_attente == INVALID_SOCKET)
        return -1;
    if (bind(sock_attente,(SOCKADDR*)&csin,sizeof(csin)) == SOCKET_ERROR)
        return -2;
    printf("Listen...\n");
    if (listen(sock_attente, 1) == SOCKET_ERROR)
        return -3;
    len = sizeof(csin);
    printf("Attente client...\n");
    sock_dialogue = accept(sock_attente, (SOCKADDR *)&csin, &len);
    if (sock_dialogue==INVALID_SOCKET)
        return -4;
    printf("Connection ok\n");
    dialogue(sock_dialogue,0);
    return 0;
}

int client()
{
    char ip[15];
    int port;
    SOCKET sock;    
    SOCKADDR_IN sin;
    askIP(ip);
    port = askport();
    sin.sin_family=AF_INET;
    sin.sin_addr.s_addr=inet_addr(ip);
    sin.sin_port=htons(port);
    sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (sock == INVALID_SOCKET)
        return -1;
    printf("Connection...\n");
    if(connect(sock,(SOCKADDR*)&sin,sizeof(sin)) == SOCKET_ERROR)
        return -2;
    printf("Connection ok\n");
    dialogue(sock,1);
    return 0;
}

int main()
{
    int choix;
    int ret;
#ifdef WIN32
    WSADATA wsa; 
    WSAStartup(MAKEWORD(2,0),&wsa);    
#endif
    printf("Serveur = 0 ; Client = 1 : ");
    scanf("%d",&choix);
    if (choix==0)
        ret = serveur();
    else
        ret = client();
    printf("Retour : %d\n",ret);
#ifdef WIN32
    WSACleanup();
#endif
    return 0;
}



Commentaires


	Bienvenue sur cet exemple de réseau.

	Ce code est fait pour Windows : il ne compilera pas en état sous Linux.
	Cependant, il existe l'équivalent sous Linux, avec le header socket.h au lieu de winsock2.h et sans les fonctions WSA dans le main que j'ai protégé.
	
	Compilation : Vous devez inclure winsock2.h et lier la librairie ws2_32.lib (sous Windows)

	Il s'agit d'un mini-char simpliste et très limité, car c'est à chacun son tour de parler, et quand c'est à l'un de parler, l'autre ne peut pas.
	Avant toute chose, Lançons le programme pour voir. On va le lancer deux fois.

	Tout d'abord, lancez le programme, et choisissez serveur.
	On vous demande un port, entrez par exemple 7000.
	Il se peut que le pare feu (firewall) gueule, et demande si on est d'accord poue laisser passer une connection. Dites lui que c'est bon.

	Le programme, si tout se passe bien, va s'arrêter sur "Attente client..."
	
	Laissez le ouvert, et lancez une seconde fois le programme.
	Cette fois, choisissez client. Entrez l'adresse IP 127.0.0.1 (adresse qui permet de tester le programme sur la même machine), et port 7000

	Vous verrez que la console du serveur réagira. Vous serez connecté : ce sera marqué.
	Vous pourrez alors discuter entre les deux consoles à tour de rôle (oui, ce char ne permet qu'à tour de rôle)
	Constatez que ce que vous écrivez d'un coté apparait de l'autre coté.

	-------------------------
	Quelques explications théoriques :


	* qu'est ce qu'un port ? Et bien imaginez que votre PC est un standard téléphonique, quand on lui 
	parle par le réseau, il faut lui dire a quelle "oreille" on lui parle. Sauf qu'un PC a des milliers
	d'oreilles, de ports donc !
	C'est pour cela qu'on peut se connecter à plusieurs à la fois sur le meme serveur, chacun communique
	avec un port différent et le PC écoute tout le monde en meme temps...
	Ne vous détrompez pas, meme quand vous allez sur un site, vous parlez au PC sur le port 80
	Mais alors pourquoi est ce que plusieurs personnes peuvent venir si tout le monde parle sur le meme port ?
	Ben c'est comme une résidence de vacances : vous arrivez a l'accueil (port 80) et on vous donne tout de suite
	une chambre (un autre port, libre...)
	Donc quand vous vous connectez sur un serveur, le serveur vous donne tout de suite un autre numéro de port !
	et ainsi, le port 80 est tout de suite libre pour un autre mec qui arrive !

	Pour le client, vous entrez l'IP du serveur (si vous testez sur la meme machine et que vous lancez les 2 programmes,
	alors 2 choix :
	- vous donnez a votre programme l'ip de votre machine
	- vous donnez l'ip 127.0.0.1 qui veut littéralement dire "Moi meme"

	Evidemment, si vous voulez tester ce programme sur deux ordis distants, le client a besoin de connaître l'IP du serveur.

	Le client demande bien sur, sur quel port se connecter, mettez bien sur le meme port ! (exemple 7000)


	-------------------------
	-------------------------
	-------------------------

	Passons au code :
	Dans le main, on a :

		WSADATA wsa; 
		WSAStartup(MAKEWORD(2,0),&wsa);	

	Ces 2 lignes sont pour déclarer a Windows que vous allez vous servir du réseau. En gros, c'est une demande
	de permission, c'est indispensable ! Je n'expliquerai pas + ces lignes...
	
	On demande si on veut lancer le programme en tant que serveur ou en tant que client, puis on va dans la bonne fonction.
	
	-----------------
	Pour le serveur :
	-----------------

	On demande le numéro de port ou écouter ! C'est a dire que ce sera le port "reception d'hotel" de votre serveur
	On ne mettra pas le 80 pour ne par partir en conflit avec les autres applications en cours !
	On mettra 7000 par exemple...


		csin.sin_family=AF_INET;
		csin.sin_addr.s_addr=htonl(INADDR_ANY);
		csin.sin_port=htons(port);

	On remplit la structure csin, qui va servir a déterminer les parametre du canal de communication
	que nous allons ouvrir.
	family = AF_INET : internet fonctionne avec cela, mettre toujours cela ici....

		s_addr = htonl(INADDR_ANY);

	On met toujours htonl (Host to Network Long), ça sert a NORMALISER les informations !
	N'oubliez pas que vous pouvez communiquer avec un Mac, qui marche d'une autre façon, et dont
	le codage est différent (niveau Endian)...
	En gros : ça ne marche pas pareil, donc il faut NORMALISER. Cette fonction permet de normaliser les long pour le réseau

	INADDR_ANY veut dire "on va écouter n'importe qui qui viendra ! Je suis ouvert a tout gars qui vient..." <-- role d'un serveur

		sin_port=htons(port);

	on normalise le numéro de port (Host to Network Short)

	Cette structure contient donc maintenant "j'écoute n'importe qui sur le port 7000 en norme Internet"


		sock_attente = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	--> cree la SOCKET, c'est a dire le canal de communication ! On va le mettre en norme Internet (AF_INET), 
	on va envoyer des paquets de données par flux SOCK_STREAM (ce que fait le net), et on va utiliser le protocole TCP pour cela
	(changer ces parametres est fait par les personnes qui masterisent le réseau... Si ce n'est pas le cas pour vous, recopiez ces 
	parametres par défaut...)

		bind(sock_attente,(SOCKADDR*)&csin,sizeof(csin));

	--> On associe notre structure de caractéristique csin a la socket grace a Bind
	Notre canal de communcation est tout bien configuré !

		listen(sock_attente, 1);

	--> ça c'est le nombre de personnes qui peuvent attendre sur le port 7000
	Si on reprend l'exemple de l'hotel, c'est la file d'attente a la réception : ici je met 1, 
	ça veut dire que si un 2e gars essaie de se connecter EN MEME TEMPS, le serveur l'envoie chier
	par contre, une fois, on verra apres, que le "client a sa chambre", alors la file de réception 
	est vide, et on peut de nouveau écouter qq un d'autres ! Et donc avoir plusieurs clients sur le meme
	serveur ! 

		sock_dialogue = accept(sock_attente, (SOCKADDR *)&csin, &len);

	--> ici, le serveur accepte le client... il lui assigne une autre socket (et donc un autre port !)
	pour reprendre l'exemple, le recpetionniste donne la chambre au client !
	La fonction est bloquante : c'est à dire qu'on ne va pas sortir tant que personne ne s'est pas connecté : 
	le programme de serveur va attendre ici qu'un client se connecte...

	Une fois cela fait : on a le client en ligne, et on communique avec le lui par la socket 
	(par le canal de communication) "sock_dialogue"

	La fonction dialogue va leur permettre de dialoguer. Le paramètre tour définit "à qui le tour de parler ?"
	Je vous laisse donc deviner que send et recv permettent d'envoyer des données sur le canal, ou
	d'en recevoir !
	recv est une fonction BLOQUANTE, c'est a dire que le programme reste dessus jusqu'a ce qu'il y 
	aie sizeof(buffer) octets a récupérer !


		WSACleanup();
	
	--> permet de dire a Windows qu'on a fini avec le réseau. Important.

	Pour pouvoir faire un chat digne de ce nom, il y a donc 2 possibilités :
	- utiliser un recv non bloquant.
	- faire du parrallélisme


	----------------
	Pour le client :
	----------------
	
	Je vais donc allez plus vite sur le client.
	Pareil que pour le serveur, on remplit la structure csin :

	les parametres sont identiques sauf :

		s_addr=inet_addr(ip);
	
	--> Contrairement au serveur, vous n'attendez pas n'importe qui, vous dites que vous ne voulez parler qu'au serveur !
	(dont vous connaissez l'IP)

	la focntion socket est la meme que pour le serveur, vous établissez votre canal de communication.

	connect va vous permettre de vous connecter au serveur, mais vous garderez la même socket de communication.
	
	Une fois connecté, tout se passe comme pour le serveur !!