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 more
Les 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 UPD (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 |
-1famille 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);
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_INET 1, AF_INET6 1, 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
.gethostbyname
)La fonction suivante permet de résoudre un nom de domaine:
struct hostent *gethostbyname(const char *name);