SYNOPSIS
cc [ flag ... ] file ... -lsocket -lnsl [ library ... ]
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
Cette fonction rend un (petit) entier qui servira à identifier la
socket créée. Cet entier est un index dans la table
des descripteurs de fichiers du processus. Il s'agit donc d'une
ressource qui est du même genre que ce qui est allouée par la
fonction open(). De ce fait, la
destruction de la socket se fait avec le même appel que celui de la
fermeture d'un fichier : close().
SYNOPSIS
cc [ flag ... ] file ... -lsocket -lnsl [ library ... ]
#include <sys/types.h>
#include <sys/socket.h>
int bind(int s, const struct sockaddr *name, int namelen);
struct in_addr {
u_long S_addr;
};
struct sockaddr {
u_short sa_family; /* address family */
char sa_data[14]; /* up to 14 bytes of direct address */
};
struct sockaddr_un {
short sun_family; /* AF_UNIX */
char sun_path[108]; /* path name */
};
struct sockaddr_in {
short sin_family; /* AF_INET */
u_short sin_port; /* le numéro de port */
struct in_addr sin_addr; /* l'adresse Internet */
char sin_zero[8]; /* un champ de 8 zéros */
};
Dans le domain AF_INET, le premier champ doit contenir
AF_INET. Le troisième champ identifie la machine par son
numéro IP. La machine émetrice doit remplir ce champ avec le numéro
de la machine à laquelle elle veut envoyer des données. On peut
spécifier ce numéro "à la main", si on le connaît, ou utiliser le
résultat de gethostbyname()
si on connaît la machine destinatrice par son nom.
SYNOPSIS
cc [ flag ... ] file ... -lnsl [ library ... ]
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
Enfin, le deuxième champ doit contenir le numéro de port voulu sur la
machine destinatrice. Là encore, on peut fixer ce numéro "à la
main" ou utiliser le résultat de la fonction
getservbyname() :
SYNOPSIS
cc [ flag ... ] file ... -lsocket -lnsl [ library ... ]
#include <netdb.h>
struct servent *getservbyname(const char *name, const char
*proto);
Cette fonction utilise le fichier /etc/services qui décrit les numéros
de port universels.
On peut aussi du côté du récepteur laisser le système choisir un numéro en initialisant sin_port à zéro.
Du côté du récepteur, il faut obligatoirement appeler la fonction bind() pour que d'autres processus puissent contacter le récepteur à travers la socket ouverte. Cette fonction attend comme deuxième argument l'adresse de la machine réceptrice. Il faut effectivement y spécifier un numéro de port dans le deuxième champ. Par contre, pour avoir un programme portable d'une part, et qui fonctionne sur les machine reliées à plusieurs réseaux, dans le troisième champ (celui qui devrait contenir un numéro IP), on utilise souvent la constante INADDR_ANY définie dans le fichier /usr/include/netinet/in.h.
On peut retrouver l'adresse attachée à une socket dont on ne connaît que le descripteur grâce à la fonction getsockname().
SYNOPSIS
cc [ flag ... ] file ... -lsocket -lnsl [ library ... ]
#include <sys/types.h>
#include <sys/socket.h>
int getsockname(int s, struct sockaddr *name, int *namelen);
| Côté émetteur | Côté récepteur | ||
|---|---|---|---|
| Créer une socket | socket() | Créer une socket | socket() |
| - | - | Attacher la socket à une adresse | bind() |
| Envoyer un message | sendto() | Recevoir le message | recvfrom() |
SYNOPSIS
cc [ flag ... ] file ... -lsocket -lnsl [ library ... ]
#include <sys/types.h>
#include <sys/socket.h>
int sendto(int s, const char *msg, int len, int flags,
const struct sockaddr *to, int tolen);
Cette fonction retourne le nombre d'octets effectivement transmis et -1
en cas d'échec. Toutefois, les conditions d'échec ne sont que
locales, en particulier, si on envoie un message à une machine et un
numéro de port où aucun processus n'écoute, il n'y aura pas de
compte rendu d'erreur, et le message sera perdu sans que l'émetteur
en soit avisé. Par contre, la validité du descripteur de socket et de
l'adresse sont vérifiés.
SYNOPSIS
cc [ flag ... ] file ... -lsocket -lnsl [ library ... ]
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int recvfrom(int s, char *buf, int len, int flags,
struct sockaddr *from, int *fromlen);
Pour pouvoir récupérer un message avec cette fonction, elle doit être
appelée après l'attachement de la socket, et après qu'un émetteur
ait envoyé un message après cet attachement.
Un message complet sera extrait de la file, même si sa longueur est
plus grande que len, auquel cas la fin du message sera
irrémédiablement perdu. Cette fonction retourne le nombre d'octets
reçus, et -1 en cas d'erreur. C'est une fonction bloquante.
Le récepteur peut connaître l'adresse de l'émetteur grâce à
l'argument from, à condition de bien passer le nombre
d'octets alloués pour from dans fromlen, la
longueur effective de l'adresse se trouvant dans fromlen au
retour.
Un circuit virtuel est établi entre les deux entités communiquantes. Une fois la connexion établie, les deux entités jouent un rôle symétrique. Avant la connexion, l'une des deux entités doit attendre une connexion, et l'autre la demander ; elles n'ont donc pas un rôle symétrique.
Outre la fiabilité apportée par la communication avec le protocole TCP, un autre aspect de ce mode de communication est l'aspect continu de l'information : il n'y a pas de frontières de messages.
| Côté émetteur | Côté récepteur | ||
|---|---|---|---|
| Créer une socket | socket() | Créer une socket | socket() |
| - | - | Attacher la socket à une adresse | bind() |
| - | - | Ecouter les demandes de connexions | listen() |
| Demander une connexion | connect() | - | - |
| - | - | Accepter la connexion | accept() |
| Envoyer des données | send() | Recevoir les données | recv() |
Pour réaliser un véritable serveur, il faut créer un nouveau processus dès qu'une demande de connexion arrive et débloque la fonction accept(). Le processus fils traitera alors le dialogue avec le client, pendant que le père attendra de nouvelles demandes de connexion sur la première socket qu'il a créée. Si on ne procède pas de cette façon, le serveur ne peut alors traiter qu'une seule demande à la fois, et toutes les autres demandes provenant d'autres clients sont mises en attente jusqu'à ce que le serveur ait terminé la transaction avec le client en cours de traitement.
Les émissions de données peuvent se faire avec send() ou write(). Ces appels sont bloquants lorsque le tampon de réception de la socket distante et le tampon d'émission de la socket locale sont pleins. La primitive send() permet en outre de pouvoir envoyer des messages "hors-bande" avec le flag MSG_OOB, ce que l'on ne peut pas faire avec write(). Par contre, on peut utiliser au dessus de write() les fonctions de la librairie standard d'entrée-sortie, par exemple fprintf().
La réception de données peut se faire avec recv() ou read().
Ces appels sont bloquants si aucun caractère n'est disponible pour la lecture. Le processus sera réveillé dès qu'(au moins) un caractère sera disponible. La primitive recv() permet en outre de recevoir les messages "hors-bande" avec le flag MSG_OOB et la consultation sans extraction avec le flag MSG_PEEK. Par contre, on peut utiliser au dessus de read() les fonctions de la librairie standard d'entrée-sortie, par exemple fgets().