Les sockets sont basés sur une architecture client/serveur: Le serveur décide d’accepter les demandes de connection sur un port particulier, tandis que le client demander une connexion sur le serveur.
Une fois la connexion établie, les deux programmes peuvent communiquer à l’aide de read et write, exactement de la même manière que lorsque l’on lit/écrit dans un fichier.
Un exemple est fourni : le serveur Exemple-simple/socket_srv.c
et le client Exemple-simple/socket_clt.c
. Vous pourrez suivre les explications en lisant les sources fournis.
/* Serveur sockets TCP * affichage de ce qui arrive sur la socket * socket_server port (port > 1024 sauf root) */ #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char** argv ) { char datas[] = "hello\n"; int sockfd,newsockfd,clilen,chilpid,ok,nleft,nbwriten; char c; struct sockaddr_in cli_addr,serv_addr; if (argc!=2) {printf ("usage: socket_server port\n");exit(0);} printf ("server starting...\n"); /* ouverture du socket */ sockfd = socket (AF_INET,SOCK_STREAM,0); if (sockfd<0) {printf ("impossible d'ouvrir le socket\n");exit(0);} /* initialisation des parametres */ bzero((char*) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); /* effecture le bind */ if (bind(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))<0) {printf ("impossible de faire le bind\n");exit(0);} /* petit initialisation */ listen(sockfd,1); /* attend la connection d'un client */ clilen = sizeof (cli_addr); newsockfd = accept (sockfd,(struct sockaddr*) &cli_addr, &clilen); if (newsockfd<0) {printf ("accept error\n"); exit(0);} printf ("connection accepted\n"); while (1) { while (read(newsockfd,&c,1)!=1); printf("%c",c); } /* attention il s'agit d'une boucle infinie * le socket nn'est jamais ferme ! */ return 1; }
/* Client pour les sockets * socket_client ip_server port */ #include <stdlib.h> #include <string.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char** argv ) { int sockfd,newsockfd,clilen,chilpid,ok,nleft,nbwriten; char c; struct sockaddr_in cli_addr,serv_addr; if (argc!=3) {printf ("usage socket_client server port\n");exit(0);} /* * partie client */ printf ("client starting\n"); /* initialise la structure de donnee */ bzero((char*) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); /* ouvre le socket */ if ((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) {printf("socket error\n");exit(0);} /* effectue la connection */ if (connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr))<0) {printf("socket error\n");exit(0);} /* repete dans le socket tout ce qu'il entend */ while (1) {c=getchar();write (sockfd,&c,1);} /* attention il s'agit d'une boucle infinie * le socket n'est jamais ferme ! */ return 1; }
Pour tester l’exemple :
socket_srv.c
et socket_clt.c
: gcc socket_client.c -o socket_client gcc socket_server.c -o socket_server
./socket_srv 9999
./socket_clt <ip du serveur> 9999
Avant toute chose, de la même manière que lorsque l’on ouvre un fichier, le serveur se doit d’obtenir un identificateur de socket avec la fonction socket()
(man 2 socket
).
int socket(int family, int type, int protocol);
Cette fonction prend trois paramètres qui décrivent les protocoles utilisés.
Le paramètre family
(AF_UNIX, AF_INET, AF_INET6, AF_IPX, AF_NETLINK, AF_X25, AF_AX25, AF_ATMPVC, AF_APPLETALK, AF_PACKET
) que nous fixons à AF_INET
pour choisir IP
.
Le paramètre type
précise la couche transport. le couple family/type
correspond généralement à un couple de protocoles reseau/transport
(TCP/IP ou UDP/IP). Dans cet exemple c’est TCP qui est utilisé (SOCK_STREAM
pour TCP et SOCK_DGRAM
).
Le dernier paramètre protocole, est un paramètre additionnel, que nous mettrons à 0. Socket renvoie alors un identificateur de socket que l’on utilisera par la suite.
int bind(int sockfd, struct sockaddr *myaddr, int addrlen);
Le bind sert a indiquer au système la façon dont il doit accepter les connections de la part d’éventuels clients, il prend comme paramètre l’identificateur de socket sockfd
, obtenu lors de l’ouverture du socket, ainsi qu’une structure de données myaddr
spécifique au protocole utilisé contenant les informations nécessaire, ainsi que la taille addrlen
de cette structure. Le bind revoie une valeur n ́gative s’il n’a pas pu s’effectuer correctement.
Dans le cas de TCP il faut remplir une structure de type sockaddr in, en initialisant les champs sin_family
, sin_addr.s_addr
et sin_port
* sin_family
est la famille utilisée, ici AF_INET
* sin_addr.s_addr
indique les adresses d’où peuvent être acceptées les requêtes de connexion, dans notre cas on pourra utiliser la valeur INADDR_ANY
. Cependant cette doit être convertie au bon format grâce à la fonction htonl().
sin_port
est le numéro de port où doit être adressé la connexion, il suffit de choisir un nombre arbitraire plus grand que 1024 (les ports avec des numéros plus petits sont réservés pour des fonctions système). Cette valeur doit être également convertie au bon format grâce à la fonction htons()
.
Les autre champs de la structure doivent être initialisés à zéro (utiliser memset()
ou bzero()
).
bzero((char*)&serv_addr,sizeof(serv_addr)); serv_addr.sinfamily = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_TCP_PORT);
int listen(int sockfd, int backlog);
le listen sert à spécifier le nombre de connections simultanées que le serveur peut accepter, la valeur la plus communément employée est 5 qui est la plus grande autorisée. listen revoie une valeur négative en cas de problème.
int accept(int sockfd, struct sockaddr *peer, int *addrlen);
Le accept() est un fonction bloquante qui attend qu’une demande de connection arrive, lorsque c’est le cas, une copie du socket est effectuée et son identificateur est renvoyé. Cette manière de faire (copie du socket existant) peut paraître curieuse au premier abord mais elle est très utilise pratique lorsque l’on écrit un serveur capable d’accepter plusieurs connections simultanées.
Les coordonnées de l’auteur de la requête se trouvent dans la structure peer au retour de la fonction accept, de même addrlen permet de connaitre la longueur de la structure en question. Noter que les transactions read/write devront se faire en utilisant l’identificateur de socket renvoyé par accept() et non pas celui renvoyé par socket()
Les read/write permettent ensuite de lire/écrire dans le socket. Ils s’utilisent exactement comme ceux des fichiers binaire, si ce n’est qu’ils utilisent un identificateur de socket au lieu d’un identificateur de fichier.
int close(int sockfd);
Permet de fermer un socket. Il est conseillé de fermer un socket que l’on utilise plus.
La partie client est la partie qui effectue demande de connection, elle se programme en ouvrant tout d’abord un socket avec socket()
de la même manière que le serveur, il s’agit ensuite d’établir la connection avec connect()
.
int connect(int sockfd, struct sockaddr *servaddr, int addrlen);
connect()
prend en paramètres, outre l’ID du socket, une structure qui lui indique où il doit essayer de se connecter, il y a trois valeurs à initialiser dans cette structure de type sockaddr
(les autres doivent être mises à zéro).
sin_family
est la famille utilisée, ici AF_INET
sin_addr.s_addr
contient indique l’adresse du serveur, cette adresse est généralement connue sous forme texte (exemple: 10.9.185.203
) et doit être convertie en un entier grâce à la fonction inet_addr()
.sin_port
est le numéro de port du serveur où doit aboutir la connection. Cette valeur doit être également convertie au bon format grace à la fonction htons().
Une fois la connection acceptée, le client n’a plus qu’à lire/écrire dedans et l’aide des fonctions read()
et write()
puis à le fermer à l’aide de close().
Un serveur pour ces protocoles écoute sur la machine lsis.univ-tln.fr
.
Attention, il y a plusieurs difficultés :
unsigned long int
.ntohl
).ctime
pour afficher la date. Attention, elle prend un paramètre de type time_t
(codé sur 8 octets). Pour l’afficher avec printf, vous pouvez utiliser un cast vers un unsigned long ou bien le masque “%zu”.
Dans l’exercice précédent votre programme traite les clients les uns après les autres même si ces derniers se sont connectés au même moment au serveur. Généralement quand on développe un serveur on souhaite qu’il puisse dialoguer en simultané avec plusieurs clients. Pour cela après l’exécution de la primitive accept
on utilise la primitive fork
qui créé un processus fils qui se chargera de la communication pendant que le père pourra retourner en attente sur accept
. La syntaxe générale d’un serveur est donc :
while (1) { scomm = accept(sd, NULL,NULL); pid = fork(); if (pid == 0) /* c’est le fils */ { close(sd); /* socket inutile pour le fils */ ... /* traiter la communication */ ... close(scomm); exit(0); /* on force la terminaison du fils */ } else /* c’est le pere */ { close(scomm); /* socket inutile pour le pere */ .... } } close(sd);
Remarque: Les processus fils qui se terminent deviennent zombies si dans le code du processus père on ne rajoute pas des instructions pour lui indiquer de lire le code retour de ses fils. Pour éviter cette situation vous rajouterez ici (avant la boucle) l’instruction signal(SIGCHLD,SIG_IGN)
qui a pour effet sous Linux d’éliminer directement les processus qui se terminent sans les laisser dans l’état zombie (notez que cette façon de traiter la fin des processus fils est déconseillée par la norme POSIX).
socket_server
et socket_client
) de façon à ce que les clients soient traités en parallèle par le serveur.
title
../rss_client 10.1.65.61 80 http://www.univ-tln.fr/backend-breves.php3
man gethostbyname
), soit l’adresse IP.
maitinfo1:~> ./scan sinfo1 Le port 22 est ouvert Le port 80 est ouvert Le port 111 est ouvert Le port 199 est ouvert Le port 763 est ouvert Le port 2049 est ouvert Le port 4001 est ouvert Le port 5432 est ouvert maitinfo1:~> ./scan 10.9.185.1 Le port 22 est ouvert Le port 80 est ouvert Le port 111 est ouvert Le port 199 est ouvert Le port 763 est ouvert Le port 2049 est ouvert Le port 4001 est ouvert Le port 5432 est ouvert
~~DISCUSSION~~
—- dataentry page —- type : TP enseignement_tags : S52 technologies_tags : Socket, TCP themes_tags : Réseaux