[BITS 16]
jmp debut
%include "../index.html"
call init_article
cmp init_artcle, OK
jne error
jmp end
============
Cours 5: C =
============
1: INTRODUCTION:
Ce cours vous propose une approche des socket (pour la connectivite) et du multithreading (plusieurs taches d'executant en meme temps). Notez toutefois que ce cours utilise des librairies unix/linux et utilise le compilateur gcc. (Il serait cependant possible d'utiliser cygwin sous windows mais je ne l'ai jamais utilise moi meme..).
Ce cours vous donnera d'abors une approche des socket avec exemple a l'appuis, une fois ces notions bien assimiles, nous passeront au multithreading (sans toutefois entrer dans des fonctions complexes, ce sera donc du multithreading de "bas niveau" :) ). Le but de ce cours etant de realiser un petit tchat avec un client et un serveur. ^^
2: LES SOCKETS - INTRODUCTION:
Voyons tout d'abord l'architecture d'une connexion avec l'interraction client-serveur par ce schema (pour une connection oriente connexion (TCP):
SERVER | | CLIENT |
-socket() | | |
-bind() | | |
-listen() | | |
-accept() | | |
(blocage jusqu'a connexion du client) | | -socket() |
| | -connect() |
-recv() | << donnees << | -send() |
-send() | >> donnees >> | -recv() |
| etc..(jusqu'a la fermeture) | |
-shutdown() | | -shutdown() |
-close() | | -close() |
Les noms des fonctions employes dans le schema sont ceux que nous utiliseront pour les manipulations.
Voici les presentations de ces differentes fonctions:
3: LES SOCKETS - FONCTIONS:
-int socket(int domain, int type, int protocol);
Est utilise pour creer un socket, domain prend pour parametre (le plus courament):
-PF_UNIX: communication locale unix
-PF_INET: communication IPv4 (option la plus utilisee)
-PF_INET6: communication utilisant IPv6
-PF_PACKET: pour les packets de bas niveau (ex: utilisation de raw sockets)
type peut prendre comme valeur:
SOCK_STREAM: oriente connexion (TCP)
SOCK_DGRAM: oriente non-connexion (UDP)
SOCK_RAW: socket de bas niveau (raw socket)
On initialise protocol a 0 par defaut.
La fonction socket renvoit le descripteur de socket (pout les manipulations futures), ou -1 si une erreur a ete rencontree.
-int bind(int s, const struct sockaddr *name, int namelen);
La fonction bind definie l'IP et le port local de l'application. s est le descripteur de socket renvoye par socket(), le deuxieme argument est une structure de type sockaddr definie comme suit:
struct sockaddr {
u_short sa_family; /* Famille d'adresse */
char sa_data[14]; /* Parametress */
};
Que l'on initialise avec une structure sockaddr_in definie comme suit:
struct sockaddr_in {
short sin_family; /* Famille d'adresse */
u_short sin_port; /* Port de connexion */
struct in_addr sin_addr; /* Adresse hote */
char sin_zero[8]; /* Padding, Ignore */
};
namelen est initialise avec la taille d'une structure sockaddr_in.
(Voir l'exemple plus loin, c'est pratique pour comprendre ;) ).
-int listen(int s, int backlog);
Listen est utilise pour dire que le serveur attent des connexions (donc qu'il est en ecoute).
s est le descripteur de socket.
backlog specifit le nombre maximum de demandes de connexion en attendant d'etre acceptes.
-int accept(int s, struct sockaddr *addr, int *addrlen);
Accept creer un nouveau socket ayant les memes proprietes que "s" afin de communiquer avec le client, ainsi le serveur creer un nouveau socket par client en respectant l'ordre des demandes.
s est (encore ^^) le descripteur de socket.
*addr contient l'adresse du client connecte.
addrlen contient la taille de la structure addr retournee
Accept retourne le nouveau descripteur de socket ou -1 en cas d'erreur. Notez toutefois que "s" n'est pas altere par cette manipulation et qu'il reste en ecoute. Notez aussi que la fonction accept bloque jusqu'a ce qu'une demande de connexion a lieu (tres important).
-int connect(int s, struct sockaddr *name, int namelen);
Permet au client de se connecter a un serveur.
Connect s'utilise de la meme maniere que bind. Notez tourefois que le client n'utilise pas bind et que le serveur n'utilise pas connecte (en regle general, dans un environnement client-serveur).
-int send(int s, const char *msg, int len, int flags);
Sert a envoyer des donnees. s est le socket utilise pour l'envoit, le deuxieme argument est la chaine envoyee. len specifit la taille de la chaine a envoyer, enfin flags est generalement mis a 0.
-int recv(int s, char *buf, int len, int flags);
Sert a recevoir des donnees, cette fonction s'enploit comme la fonction send sauf que le deuxieme argument est ici un buffer destine a recevoir les donnees. Notez que la fonction de recv bloque jusqu'a ce qu'une donnee a ete recuperee.
-int shutdown(int s, int how);
shutodown permet de fermer un ou les deux sens de la connexion.
s est le socket et how peut prendre les valeurs suivantes:
0: La reception est fermee.
1: L'emission est fermee.
2: L'emission et la reception sont fermees.
-int close(int fildes);
Close ferme la socket fildes apres avoir envoye les donnees restente (dans le cas d'une connexion TCP).
Il existe bien entendus beaucoup d'autres fonctions dont certaines que nous verront dans l'exemple, mais le but de ce cours n'est pas de donnees les definitions de toutes les fonctions existantes, mais de donner un support suite a une utilisation plus avancee ou non.
4: LES SOCKETS - EXEMPLE:
Vu que les notions apportees plus haut peuvent etre difficile a assimile, l'exemple suivant abandament commente, fonctionnel, et simple devrait vous aidez :)
SERVEUR:
#include
#include
int main(void) {
int sock, cli_sock, len;
struct sockaddr_in sin, cli_sin;
char buffer[4096];
printf("\nExemple de programme utilisant les sockets:\n\n");
/*On commence par creer le socket*/
if((sock=socket(PF_INET, SOCK_STREAM, 0))==-1) {
printf("Erreur de creation du socket"); /*Si socket echout*/
return(1);
}
/*Si le socket est bien cree, on continus :) */
/*On initialise la structure sin qu'on passera a bind*/
bzero(&sin, sizeof(sin)); /*On la clean d'abors au cas ou*/
sin.sin_family=PF_INET; /*On utilise PF_INET*/
sin.sin_port=htons(10000); /*On utilisera le port 10000 (notez la fonction htons)*/
sin.sin_addr.s_addr=htonl(INADDR_ANY); /*L'adresse: comme ce programme est un serveur,
on utilise INADDR_ANY, notez l'emploit de htonl*/
/*Maintenant que la structure est initialisee, on peut utiliser bind*/
if((bind(sock, (struct sockaddr *)&sin, sizeof(sin)))==-1) {
printf("Erreur Bind"); /*Si bind echout*/
return (1);
}
/*le (struct sockaddr *) est utilise pour convertir sin au bon format (pointeur de
structure sockaddr*/
if((listen(sock, 5))==-1) { /*Simple utilisation de listen*/
printf("Erreur Listen"); /*Si listen echout*/
return (1);
}
len=sizeof(cli_sin); /*On recupere la taille de la structure cli_sin pour accept*/
while(1) { /*Boucle infinis pour les demandes de connexions*/
if((cli_sock=accept(sock, (struct sockaddr *)&cli_sin, &len))==-1)
printf("Erreur Accept\n\n"); /*Si accept echout*/
/*Meme si une erreur a lieu, on continus pour ne pas rendre inactif le serveur
juste pour une erreur*/
/*On envoit puis on recoit des donnees*/
send(cli_sock, "\nServeur: Bienvenue :)\n\n", 25, 0); /*Envoit de donnees*/
recv(cli_sock, buffer, sizeof(buffer), 0); /*Reception de donnees*/
printf("%s", buffer); /*On affiche les donnees recus*/
/*Et enfin on ferme la connexion, le test etant termine*/
shutdown(cli_sock, 2);
close(cli_sock);
/*Puis le socket du serveur*/
shutdown(sock, 2);
close(sock);
return(0);
}
return(0);
}
CLIENT:
#include
#include
int main(void) {
int sock;
struct sockaddr_in sin;
char buffer[4096];
printf("\nExemple de programme utilisant les sockets:\n\n");
/*On commence par creer le socket*/
if((sock=socket(PF_INET, SOCK_STREAM, 0))==-1) {
printf("Erreur de creation du socket"); /*Si socket echout*/
return(1);
}
/*Si le socket est bien cree, on continus :) */
/*On initialise la structure sin qu'on passera a bind*/
bzero(&sin, sizeof(sin)); /*On la clean d'abors au cas ou*/
sin.sin_family=PF_INET; /*On utilise PF_INET*/
sin.sin_port=htons(10000); /*On utilisera le port 10000 (notez la fonction htons)*/
sin.sin_addr.s_addr=inet_addr("127.0.0.1"); /*L'adresse: comme ce programme est un
client, on utilise l'addresse IP du serveur pour la connexion, ici 127.0.0.1 est
l'addresse local (comme le serveur et le client s'executent sur la meme machine),
notez la fonction inet_addr*/
/*Maintenant que la structure est initialisee, on peut utiliser bind*/
if((connect(sock, (struct sockaddr *)&sin, sizeof(sin)))==-1) {
printf("Erreur Connect"); /*Si connect echout*/
return (1);
}
/*le (struct sockaddr *) est utilise pour convertir sin au bon format (pointeur de
structure sockaddr*/
recv(sock, buffer, sizeof(buffer), 0); /*Reception de donnees*/
printf("%s", buffer); /*On affiche les donnees recus*/
send(sock, "\nClient: Salut! :)\n\n", 21, 0); /*Envoit de donnees*/
/*Et enfin on ferme la connexion, le test etant termine*/
shutdown(sock, 2);
close(sock);
return(0);
}
Remarquez le #include qui est necessaire pour les fonctions utilisant les sockets. Pour faire fonctionner le programme, il suffit de lancer d'abord le serveur et ensuite le client. Une fois le serveur referme, il vous faudra attendre quelques temps avant de le relancer sous peine d'avoir une erreur bind (dut au noyau de linux).
5: LE MULTITHREADING - INTRODUCTION:
Le multithreading est "l'art" :D de faire s'executer plusieurs fonctions en meme temps. La librairie pthread employe sous linux permet beaucoup de choses niveaux multithreading comme les mutex; les semaphores etc. Ici nous n'utiliseront pas de fonctions "avancees" et ceci pour deux raisons:
-Ce cours est deja assez complet niveau nouvelles notions.
-Pour l'instant vous n'avez surment pas besoin d'utiliser ces fonctions avancees.
L'exemple du client-serveur plus haut vous a peut etre montre la faiblesse du programme: le programme bloque sur l'appel accept tant qu'il n'y a pas de connexion, ainsi imaginez que vous voulez recuperer des chaines de caracteres avec fgets et que le programme est "bloquer": vous ne pouvez pas ;) alors que si vous avez le programme "bloque" sur l'appel accept et un thread attribue a une fonction de saisi, les deux fonctions s'executeront en meme temps :D (notez que vous pouvez creer plusieurs threads en meme temps).
6: LE MULTITHREADING - LES FONCTIONS:
Voici les fonctions que nous utiliseront , une fois la notions de multithreading bien assimilee, vous serez en mesure d'aborder des cours plus avances.
-int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
pthread_create est utilise pour creer un nouveau thread, le premier argument est le nom du thread (du type pthread_t), le second indique les attributs du thread, ici nous ne les utiliseront pas est nous mettrons NULL. Le troisieme argument est un la fonction a laquelle sera assigne le thread, enfin le quatrieme argument est l'argument de la fonction a laquelle le thread est assigne (peut etre NULL si elle n'accepte pas d'arguments).
Cette fonction renvoit un entier non-nul en cas d'echec.
-void pthread_exit(void *retval);
Est utilise pour terminer le thread en cours, retval est la valeur de retour du thread (0 si tout se passe bien).
-int pthread_join(pthread_t th, void **thread_return);
Le programme principal (ou un thread) bloque sur l'appel pthread_join jusqu'a ce que le thread "th" est termine (par l'appel pthread_exit), thread_return renvoit la valeurs renvoyee par l'appel pthread_exit (sauf si thread_return est egal a NULL).
-int pthread_cancel(pthread_t thread);
Envoit une requete d'annulation au thread "thread" a partir d'un autre thread (on peut donc detruire un thread d'une maniere indirect (a partir d'un autre thread)).
-int pthread_setcancelstate(int state, int *etat_pred);
Cette fonction sert a determiner pour le thread si celui-ci accepte d'etre detruit par un appel thread_cancel ou non. Si oui, state est remplace par PTHREAD_CANCEL_ENABLE, sinon par PTHREAD_CANCEL_DISABLE. Le deuxieme argument sauvegarde le precedent etat d'annulation (il peut etre a NULL pour ne pas en prendre compte.
7: LE MULTITHREADING - EXEMPLE:
Cet exemple reprend le precedent agremente de quelques trucs utils qui fera de notre programme un mini-tchat client-server (donc pour deux personnes).
Attention: Ces exemples utilises la librairies pthread, donc pour compiler les exemples ci-dessous, vous devez faire:
gcc -o fichier fichier.c -lpthread
SERVEUR:
#include
#include
#include
pthread_t thread_write; /*Ce thread servira a execute la fonction d'ecriture*/
pthread_t thread_read; /*Ce thread servira a execute la fonction de lecture*/
int cli_sock; /*Socket client*/
void *write(void); /*Defnition fonction write*/
void *read(void); /*Definition fonction read*/
int main(void) {
int sock, len; /*Socket serveur et longueur structure cli_sin*/
struct sockaddr_in sin, cli_sin;
short thread_ok=0; /*Verifie si les threads on deja ete crees*/
printf("\nExemple de programme utilisant les sockets:\n\n");
/*On commence par creer le socket*/
if((sock=socket(PF_INET, SOCK_STREAM, 0))==-1) {
printf("Erreur de creation du socket\n"); /*Si socket echout*/
return(1);
}
/*Si le socket est bien cree, on continus :) */
/*On initialise la structure sin qu'on passera a bind*/
bzero(&sin, sizeof(sin)); /*On la clean d'abors au cas ou*/
sin.sin_family=PF_INET; /*On utilise PF_INET*/
sin.sin_port=htons(10000); /*On utilisera le port 10000 (notez la fonction htons)*/
sin.sin_addr.s_addr=htonl(INADDR_ANY); /*L'adresse: comme ce programme est un serveur,
on utilise INADDR_ANY, notez l'emploit de htonl*/
/*Maintenant que la structure est initialisee, on peut utiliser bind*/
if((bind(sock, (struct sockaddr *)&sin, sizeof(sin)))==-1) {
printf("Erreur Bind\n"); /*Si bind echout*/
printf("Si vous venez de killer le serveur, attendez quelques instants..\n");
return (1);
}
/*le (struct sockaddr *) est utilise pour convertir sin au bon format (pointeur de
structure sockaddr*/
if((listen(sock, 5))==-1) { /*Simple utilisation de listen*/
printf("Erreur Listen\n"); /*Si listen echout*/
return (1);
}
len=sizeof(cli_sin); /*On recupere la taille de la structure cli_sin pour accept*/
while(1) { /*Boucle infinis pour les demandes de connexions*/
if(thread_ok==0) { /*Si threads pas encore crees*/
if((cli_sock=accept(sock, (struct sockaddr *)&cli_sin, &len))==-1) {
printf("Erreur Accept\n\n"); /*Si accept echout*/
continue; /*Pour ne pas executer la suite*/
/*Meme si une erreur a lieu, on continus pour ne pas rendre
inactif le serveur juste pour une erreur et on relance la
fonction accept*/
}
pthread_create(&thread_write, NULL, write, NULL); /*Creation thread
write*/
pthread_create(&thread_read, NULL, read, NULL); /*Creation thread
read*/
thread_ok=1;
send(cli_sock, "\nServeur: Bienvenue :)\n\n", 25, 0); /*Envoit du
message de bienvenue*/
}
/*Si threads deja crees, on attent leurs fin avant de relancer le processus*/
pthread_join(thread_write, NULL); /*Attent fin de thread_write*/
pthread_join(thread_read, NULL); /*Attent fin de thread read*/
thread_ok=0; /*On relance le processus*/
}
/*Pour faire jolis ^^ enfaite le socket ne sera jamais kille avant que le programme
lui-meme ne soit kille (Ctrl+C) (le programme est un serveur, en theorie un serveur ne
se kill pas :), mais celui-ci est un test..*/
shutdown(sock, 2);
colse(sock);
return (0);
}
void *write(void) {
char buffer[4096]; /*Buffer d'entre*/
char buffer2[4096]; /*Pour sprintf*/
while(1) {
bzero(&buffer, sizeof(buffer)); /*On clean buffer "important!"*/
bzero(&buffer2, sizeof(buffer2));
fgets(buffer, sizeof(buffer), stdin); /*Recupere une chaine a envoyer, le
-10 correspond au Serveur: ...\n qui sera ajoute par sprintf*/
if((strcmp(buffer, "quit"))==0) /*Si la chaine entree est "quit"*/
break; /*On quite la boucle infinis*/
sprintf(buffer2, "Serveur: %s\n", buffer);
/*La fonction sprintf vous est probablement nouvelle, sa syntaxe est
-int sprintf (char *str, const char *format, ...);
Son premier argument est une chaine de caractere dans laquelle sera copie le
second argument, ici, on ajoute Serveur: 'La chaine capturee par fgets'\n
afin de mettre en forme la chaine envoye au client*/
send(cli_sock, buffer2, strlen(buffer2), 0); /*Et on l'envoit*/
}
pthread_cancel(thread_read); /*On detruit le thread read*/
shutdown(cli_sock, 2); /*En meme temps on kill le socket client (on le deconnecte)*/
close(cli_sock);
pthread_exit(0); /*On termine le thread*/
/*Remarquez que le "quit" ne ferme en rien le programme mais deconnecte le client et
en attent un nouveau*/
}
void *read(void) {
char buffer[4096];
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); /*Accepte les requetes
d'annulation (ici envoye par le thread write*/
while(1) { /*Boucle infinie jusqu'a la destruction du thread par celui de write*/
bzero(&buffer, sizeof(buffer)); /*On clean buffer "important!"*/
recv(cli_sock, buffer, sizeof(buffer), 0); /*Recoit les donnees du client*/
puts(buffer); /*On affiche ses donnees*/
}
}
CLIENT:
#include
#include
#include
pthread_t thread_read; /*Ce thread servira a execute la fonction de lecture*/
pthread_t thread_write; /*Ce thread servira a execute la fonction d'ecriture*/
void *read(void); /*Declaration fonction read*/
void *write(void); /*Declaration fonction write*/
int sock; /*Socket*/
int main(void) {
struct sockaddr_in sin;
char buffer[64]; /*Destine a recevoir le message de bienvenue*/
printf("\nExemple de programme utilisant les sockets:\n\n");
/*On commence par creer le socket*/
if((sock=socket(PF_INET, SOCK_STREAM, 0))==-1) {
printf("Erreur de creation du socket\n"); /*Si socket echout*/
return(1);
}
/*Si le socket est bien cree, on continus :) */
/*On initialise la structure sin qu'on passera a bind*/
bzero(&sin, sizeof(sin)); /*On la clean d'abors au cas ou*/
sin.sin_family=PF_INET; /*On utilise PF_INET*/
sin.sin_port=htons(10000); /*On utilisera le port 10000 (notez la fonction htons)*/
sin.sin_addr.s_addr=inet_addr("127.0.0.1"); /*L'adresse: comme ce programme est un
client, on utilise l'addresse IP du serveur pour la connexion, ici 127.0.0.1 est
l'addresse local (comme le serveur et le client s'executent sur la meme machine),
notez la fonction inet_addr*/
/*Maintenant que la structure est initialisee, on peut utiliser bind*/
if((connect(sock, (struct sockaddr *)&sin, sizeof(sin)))==-1) {
printf("Erreur Connect\n"); /*Si connect echout*/
return (1);
}
/*le (struct sockaddr *) est utilise pour convertir sin au bon format (pointeur de
structure sockaddr*/
recv(sock, buffer, sizeof(buffer), 0); /*Pour le message de bienvenue*/
puts(buffer); /*Qu'on affiche*/
/*On creer les deux threads*/
pthread_create(&thread_read, NULL, read, NULL);
pthread_create(&thread_write, NULL, write, NULL);
/*On attent qu'il sont detruits avant de finir le programme*/
pthread_join(thread_write, NULL); /*Attent fin de thread_write*/
pthread_join(thread_read, NULL); /*Attent fin de thread read*/
/*On detruit enfin le socket*/
shutdown(sock, 2);
close(sock);
return(0);
}
void *write(void) {
char buffer[4096]; /*Buffer d'entre*/
char buffer2[4096]; /*Pour sprintf*/
while(1) {
bzero(&buffer, sizeof(buffer)); /*On clean buffer "important!"*/
bzero(&buffer2, sizeof(buffer2));
fgets(buffer, sizeof(buffer), stdin); /*Recupere une chaine a envoyer, le
-9 correspond au Client: ...\n qui sera ajoute par sprintf*/
if((strcmp(buffer, "quit"))==0) /*Si la chaine entree est "quit"*/
break; /*On quite la boucle infinis*/
sprintf(buffer2, "Client: %s\n", buffer);
/*La fonction sprintf vous est probablement nouvelle, sa syntaxe est
-int sprintf (char *str, const char *format, ...);
Son premier argument est une chaine de caractere dans laquelle sera copie le
second argument, ici, on ajoute Client: 'La chaine capturee par fgets'\n
afin de mettre en forme la chaine envoye au client*/
send(sock, buffer2, strlen(buffer2), 0); /*Et on l'envoit*/
}
pthread_cancel(thread_read); /*On detruit le thread read*/
pthread_exit(0); /*On termine le thread*/
}
void *read(void) {
char buffer[4096];
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); /*Accepte les requetes
d'annulation (ici envoye par le thread write*/
while(1) { /*Boucle infinie jusqu'a la destruction du thread par celui de write*/
bzero(&buffer, sizeof(buffer)); /*On clean buffer "important!"*/
recv(sock, buffer, sizeof(buffer), 0); /*Recoit les donnees du serveur*/
puts(buffer); /*On affiche ses donnees*/
}
}
Bien entendus ce code n'est pas super-optimise mais comme exemple il me parait bon. Les plus agueris auront tout de suite remarques quelques "possibilite" annexes ;) mais le but de cet exemple n'est pas d'avoir un serveur securise, mais un exemple fonctionnel et comprehensible :) .
Les plus motives pourront prendre ce code comme squelette et en faire par exemple un serveur multi-client :) et rajouter quelques fonctionnalites comme la gestion de pseudos etc.
8: LE MOT DE LA FIN.. :
Cette serie de cours est termine pour la moment, c'est a dire que je ne n'ecrirais plus d'autres cours sur le C sauf si j'ai un elan de motivation ou que je trouve un sujet tres interessant, ou que j'ai de la demande.. Vous pouvez me proposer des sujets de cours ou des question a necromagik@hotmail.com je repondrais a votre demande, il se pourrait donc bien qu'il y est bon nombre de prochains cours si je recois beaucoup de mails ;)
By NecroMagik
error:
ret
end:
ret