Guillaume Chanel
Remerciements à Jean-Luc Falcone
Cours système d'exploitation by Guillaume Chanel, Jean-Luc Falcone and University of Geneva is licensed under CC BY-NC-SA 4.0
Rappel:
$ ls -lh /dev | more
ls et moreLes tubes et FIFO:
On peut créer un canal de communication anonyme en utilisant:
int pipe(int fildes[2]);
filedes[0] est un descripteur de fichier représentant la sortie du tube/pipe (i.e. on peu lire sur ce descripteur);filedes[1] est un descripteur de fichier représentant l'entrée du tube/pipe (i.e. on peu écrire sur ce descripteur);errno);mkfifo(1)On peut créer un FIFO avec la commande:
mkfifo [OPTION]... NOM...
NOM est le nom du FIFO à créer-m MODE.mkfifo(2)On peut créer un FIFO avec l'appel système:
int mkfifo(const char *pathname, mode_t mode);
pathname est le nom du fichier à créermode représente les permissions (modifiées mode & ~umask)open/read) mais il faut veiller à respecter la directionalité du fifoInclude example there (see script)
Include example there (see script)
read()write()| internet domain | communication réseau (AF_INET) |
|---|---|
| unix domain | communication locale (AF_UNIX) |
Il existe plusieur types de sockets dont:
| par flot | avec connection, par exemple TCP (SOCK_STREAM) |
|---|---|
| par datagramme | sans connection, par exemple UDP (SOCK_DGRAM) |
| brut | sans protocol de transport (SOCK_RAW) |
Nous couvrirons surtout le premier type.
L'adresse est composée d'une adresse IP et d'un numéro de port (16bits).
inet_pton)La fonction suivante permet d'obtenir une adresse IP valide:
int inet_pton(int af, const char *src, void *dst);
af | Famille d'adresse soit AF_INET, soit AF_INET6 |
|---|---|
src | la représentation de l'adresse (par exemple 192.168.1.1). |
dst | un pointeur vers une structure in_addr ou in6_addr à initialiser. |
Retourne:
| 1 | succès |
|---|---|
| 0 | adresse non-valide |
-1| famille non-valide | |
inet_ntop)La fonction suivante permet d'obtenir la représentation d'une adresse IP:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af | Famille d'adresse soit AF_INET, soit AF_INET6 |
|---|---|
src | un pointeur vers une structure in_addr ou in6_addr initialisée. |
dst | un pointeur vers un buffer (pour obtenir la représentation). |
size | la taille du buffer |
Retourne NULL en cas d'erreur (cf errno)
htons)On peut convertir un entier, en un numéro de port valide grâce à:
uint16_t htons(uint16_t hostshort); //les ports sont codés sur 16bits
uint32_t htonl(uint32_t hostlong) //pour des valeurs codées sur 32bits
Le résultat est dans le bon byte-order (Big-Endian).
Le fonctionement d'un client TCP est le suivant:
socket)connect)read/write)close)socket)On utilise pour créer un socket, l'appel système:
int socket(int domain, int type, int protocol);
| domain | famille d'adresse (AF_INET1, AF_INET61, AF_UNIX, …) |
|---|---|
| type | type de communication (SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, …) |
| protocol | protocol de transport, passer 0 pour UDP et TCP |
Retourne, soit un descripteur de fichier, soit -1 (cf. errno).
connect)L'appel système suivant permet d'initier une connection:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
| sockfd | descripteur de fichier du socket |
|---|---|
| addr | un pointeur vers une adresse |
| addrlen | la longueur de la structure |
Retourne 0 en cas de succès, et -1 sinon (cf. errno).
struct sockaddr_in address;
memset( &address, 0, sizeof(address) );
inet_pton( AF_INET, "192.168.1.1", &(address.sin_addr) );
address.sin_family = AF_INET;
address.sin_port = htons(8080);
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, (struct sockaddr *) &address, sizeof(address));
...
read(sock, ...)
write(sock, ...)
Le fonctionement d'un serveur TCP est le suivant:
socket)bind)listen)accept)read/write)close)close)socket)On utilise l'appel système socket pour créer un socket serveur (cf. client).
bind)On lie un socket à une adresse (interface locale) avec l'appel système:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
| sockfd | le descripteur du socket |
|---|---|
| addr | un pointeur vers l'adresse à lier. |
| addrlen | la taille de la structure d'adresse. |
Retourne 0 en cas de succès, -1 sinon (cf. errno)
listen)L'appel système suivant, permet de marquer un socket comme étant passif, c'est à dire un socket permettant d'accepter des connections:
int listen(int sockfd, int backlog);
| sockfd | le descripteur du socket |
|---|---|
| backlog | taille maximum de la queue de connections en attente. |
Retourne 0 en cas de succès, -1 sinon (cf. errno)
accept)L'appel système suivant permet d'accepter une connection:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd | Descripteur du socket (doit être passif). |
|---|---|
addr | Structure garnie avec les informations du client. |
| addrlen | Longueur de la structure. |
errno)
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_port = htons(8080);
int serverSock = socket(AF_INET, SOCK_STREAM, 0);
bind(serverSock, (struct sockaddr *) &address, sizeof(address));
listen(serverSock, 5);
while( 1 ) {
struct sockaddr_in clientAddress;
unsigned int clientLength = sizeof(clientAddress);
int clientSock = accept(serverSock,
(struct sockaddr *) &clientAddress,
&clientLength);
/* Lectures/Ecritures sure clientSock (read/write) */
close( clientSock );
}
INADDR_ANY permet de se lier à toutes les interfaces réseaux de la machine.htonl pour obtenir une adresse numérique valide:address.sin_addr.s_addr = htonl(INADDR_ANY)
Dans l'exemple précédent, il faudrait:
#CLIENT
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("www.mcmillan-inc.com", 80))
#SERVEUR
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), 80))
serversocket.listen(5)
try {
serverSocket = new ServerSocket(4444);
}
catch (IOException e) {
System.out.println("Could not listen on port: 4444");
System.exit(-1);
}
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
}
catch (IOException e) {
System.out.println("Accept failed: 4444");
System.exit(-1);
}
(define (id-server)
(let ((socket (open-socket)))
(display "Waiting on port ")
(display (socket-port-number socket))
(newline)
(let loop ((next-id 0))
(call-with-values
(lambda ()
(socket-accept socket))
(lambda (in out)
(display next-id out)
(close-input-port in)
(close-output-port out)
(loop (+ next-id 1)))))))
On peut utiliser les appels systèmes suivant à la place de read et de write lorsqu'on utilise des sockets:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
flags permet de passer des paramètres supplémentaires pour contrôler finemement la transmission.recv(sockfd, buf, len, 0) est équivalent à read(sockfd, buf, len)send(sockfd, buf, len, 0) est équivalent à write(sockfd, buf, len)Sous Linux, on peut remplacer une paire read/write ou send/recv, par un appel à sendfile qui permet de rester dans l'espace du noyau:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
out_fd est le descripteur ouvert en écriture (devait être un socket jusqu'à Linux 2.6.33)in_fd est le descripteur ouvert en lecture (ne peut pas être un socket)offset représente l'offset en lecture (peut être NULL)count taille à envoyer.Différences avec TCP:
| Client | pas besoin d'établir une connection. |
|---|---|
| Serveur | pas besoin d'écouter et d'accepter une connection. |
AF_UNIX à l'appel système socket.La fonction suivante permet de résoudre le nom de domaine d'un service:
int getaddrinfo(const char *node,
const char *service,
const struct *hints,
struct addrinfo **res);