Différences entre versions de « C socket »
Ligne 97 : | Ligne 97 : | ||
= Exemple = | = Exemple = | ||
== Côté serveur == | == Côté serveur == | ||
+ | Voici un exemple de serveur ''echo'' qui renvoie le message au client. | ||
+ | <source lang="c"> | ||
+ | #include <unistd.h> | ||
+ | #include <stdio.h> | ||
+ | #include <sys/socket.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <netinet/in.h> | ||
+ | #include <string.h> | ||
+ | #include <errno.h> | ||
+ | #include <arpa/inet.h> | ||
+ | |||
+ | // Port d'écoute de la socket | ||
+ | #define PORT 8080 | ||
+ | // Adresse d'écoute (toutes les adresses) | ||
+ | #define IP INADDR_ANY | ||
+ | // Taille de la file d'attente | ||
+ | #define BACKLOG 3 | ||
+ | // Message à envoyer au client | ||
+ | # define WELCOME_MESSAGE "Entrez 'exit' pour quitter\n" | ||
+ | // Taille du tampon de lecture des messages | ||
+ | #define BUFFER_LEN 200 | ||
+ | // Commande pour arrêter le serveur | ||
+ | #define EXIT_WORD "exit" | ||
+ | |||
+ | void initAdresse(struct sockaddr_in * adresse); | ||
+ | int initSocket(struct sockaddr_in * adresse); | ||
+ | int waitForClient(int * serverSocket); | ||
+ | |||
+ | int main(void) { | ||
+ | // Structure contenant l'adresse | ||
+ | struct sockaddr_in adresse; | ||
+ | initAdresse(&adresse); | ||
+ | // Descripteur de la socket du serveur | ||
+ | int serverSocket = initSocket(&adresse); | ||
+ | while (1) { | ||
+ | // Descripteur de la socket du client, on attend une connexion | ||
+ | int clientSocket = waitForClient(&serverSocket); | ||
+ | // Envoie du message de bienvenu | ||
+ | send(clientSocket, WELCOME_MESSAGE, strlen(WELCOME_MESSAGE), 0); | ||
+ | char buffer[BUFFER_LEN] = ""; | ||
+ | while(strncmp(buffer, EXIT_WORD, 4) != 0){ | ||
+ | int len = read(clientSocket, buffer, BUFFER_LEN); | ||
+ | // Ajout du terminateur de chaîne | ||
+ | buffer[len] = '\0'; | ||
+ | // On renvoie le texte au client | ||
+ | send(clientSocket, "Vous avez dit : ", strlen("Vous avez dit : "), 0); | ||
+ | send(clientSocket, buffer, strlen(buffer), 0); | ||
+ | } | ||
+ | send(clientSocket, "Bye\n", strlen("Bye\n"), 0); | ||
+ | printf("Fermeture de la connexion avec le client\n"); | ||
+ | close(clientSocket); | ||
+ | } | ||
+ | return EXIT_SUCCESS; | ||
+ | } | ||
+ | // Initialisation de la structure sockaddr_in | ||
+ | void initAdresse(struct sockaddr_in * adresse) { | ||
+ | (*adresse).sin_family = AF_INET; | ||
+ | (*adresse).sin_addr.s_addr = IP; | ||
+ | (*adresse).sin_port = htons( PORT); | ||
+ | } | ||
+ | // Démarrage de la socket serveur | ||
+ | int initSocket(struct sockaddr_in * adresse) { | ||
+ | // Descripteur de socket | ||
+ | int fdsocket; | ||
+ | // Nombre d'options | ||
+ | int opt = 1; | ||
+ | // Création de la socket en TCP | ||
+ | if ((fdsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { | ||
+ | printf("Echéc de la création: %s\n", strerror(errno)); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | printf("Création de la socket\n"); | ||
+ | // Paramètrage de la socket | ||
+ | if (setsockopt(fdsocket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, | ||
+ | sizeof(opt)) != 0) { | ||
+ | printf("Echéc de paramètrage: %s\n", strerror(errno)); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | printf("Paramètrage de la socket\n"); | ||
+ | // Attachement de la socket sur le port et l'adresse IP | ||
+ | if (bind(fdsocket, (struct sockaddr *) adresse, sizeof(*adresse)) != 0) { | ||
+ | printf("Echéc d'attachement: %s\n", strerror(errno)); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | printf("Attachement de la socket sur le port %i\n", PORT); | ||
+ | // Passage en écoute de la socket | ||
+ | if (listen(fdsocket, BACKLOG) != 0) { | ||
+ | printf("Echéc de la mise en écoute: %s\n", strerror(errno)); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | printf("Mise en écoute de la socket\n"); | ||
+ | return fdsocket; | ||
+ | } | ||
+ | // Attente de connexion d'un client | ||
+ | int waitForClient(int * serverSocket) { | ||
+ | int clientSocket; | ||
+ | // Structure contenant l'adresse du client | ||
+ | struct sockaddr_in clientAdresse; | ||
+ | int addrLen = sizeof(clientAdresse); | ||
+ | printf("En attente d'une connexion\n"); | ||
+ | if ((clientSocket = accept(*serverSocket, (struct sockaddr*) &clientAdresse, | ||
+ | (socklen_t*) &addrLen)) == -1) { | ||
+ | printf("Echéc de la récupération de la socket du client: %s\n", | ||
+ | strerror(errno)); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | // Convertion de l'IP en texte | ||
+ | char ip[INET_ADDRSTRLEN]; | ||
+ | inet_ntop(AF_INET, &(clientAdresse.sin_addr), ip, INET_ADDRSTRLEN); | ||
+ | printf("Connexion de %s:%i\n", ip, clientAdresse.sin_port); | ||
+ | return clientSocket; | ||
+ | } | ||
+ | </source> | ||
== Côté client == | == Côté client == |
Version du 26 octobre 2018 à 18:20
Introduction
Les sockets permettent de connecter deux programmes qui s'exécutent sur deux machines différentes. Cette connexion se fait à travers le réseau grâce à l'utilisation d'un port (TCP ou UDP) et d'une adresse IP. Il y a deux types de sockets, une qui est démarrée par la partie serveur en écoute et l'autre démarrée par la partie cliente qui se connecte à la première. Ci-contre une image résumant les différentes étapes pour arriver à l'envoie de données. |
Utilisation
Côté serveur
Tout d'abord il faut créer l'objet socket:
#include <sys/socket.h>
#include <netinet/in.h>
int socket(int domain, int type, int protocol)
- domain → integer, communication domain e.g., AF_INET (IPv4 protocol) , AF_INET6 (IPv6 protocol)
- type → type de communication
- SOCK_STREAM: TCP
- SOCK_DGRAM: UDP
- SOCK_RAW: socket à l'état brut (bas niveau)
- protocol → valeur du champ protocol de l'entête de niveau 3 (généralement 0)
- la valeur de retour est le fichier descripteur de la socket ou -1 en cas d'erreur (errno est positionné)
Une fois le descripteur de socket créé, il est possible de le configurer:
#include <sys/socket.h>
#include <netinet/in.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)
- sockfd → le fichier descripteur de la socket;
- level → niveau d'application de l'option (SOL_SOCKET, TCP, UDP, ...);
- optname → le nom de l'option (SO_REUSEADDR, SO_REUSEPORT, SO_KEEPALIVE, ...)
- optval, optlen → utilisé pour accéder aux options de la socket;
- le code retour varie entre 0 et -1 en cas d'erreur (errno est positionné)
Il existe plusieurs options de socket, les plus utilisées étant :
- SO_REUSEADDR → permet de réutiliser l'adresse de la socket tout de suite même si cette dernière est dans l'état wait;
- SO_REUSEPORT → permet de réutiliser le port de la socket tout de suite même si cette dernière est dans l'état wait;
- SO_KEEPALIVE → envoie des informations périodiquement pour tester si l'extrémité du tunnel est toujours présente;
Il faut maintenant attacher la socket à un port Internet et une adresse IP:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
- sockfd → le fichier descripteur de la socket;
- addr → une structure symbolisant l'adresse
struct sockaddr_in {
short sin_family; // e.g: AF_INET
unsigned short sin_port; // e.g: htons(3490)
struct in_addr sin_addr; // détaillé ci-dessous
char sin_zero[8]; // '0' habituellement
};
struct in_addr {
unsigned long s_addr; // initialiser avec inet_aton()
};
- addrlen → la taille de l'objet addr;
- le code retour varie entre 0 et -1 en cas d'erreur (errno est positionné)
On peut maintenant passer la socket en état d'écoute, elle est prête à recevoir des connexions:
int listen(int sockfd, int backlog)
- sockfd → le fichier descripteur de la socket;
- backlog → taille maximum de la file d'attente de la socket après laquelle le système répond avec ECONNREFUSED;
- le code retour varie entre 0 et -1 en cas d'erreur (errno est positionné)
On peut maintenant attendre une connexion:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd → le fichier descripteur de la socket;
- addr → l'adresse du client;
- addrlen → la taille de la structure addr;
- le code retour varie entre un entier positif qui correspond au descripteur de la socket cliente et -1 en cas d'erreur (errno est positionné)
Côté client
Après avoir créé la socket avec socket on peut se connecter à la partie serveur:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd → le fichier descripteur de la socket;
- addr → une structure symbolisant l'adresse (cf. ci-dessus)
- addrlen → la taille de l'objet addr;
- le code retour varie entre 0 et -1 en cas d'erreur (errno est positionné)
Exemple
Côté serveur
Voici un exemple de serveur echo qui renvoie le message au client.
#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
// Port d'écoute de la socket
#define PORT 8080
// Adresse d'écoute (toutes les adresses)
#define IP INADDR_ANY
// Taille de la file d'attente
#define BACKLOG 3
// Message à envoyer au client
# define WELCOME_MESSAGE "Entrez 'exit' pour quitter\n"
// Taille du tampon de lecture des messages
#define BUFFER_LEN 200
// Commande pour arrêter le serveur
#define EXIT_WORD "exit"
void initAdresse(struct sockaddr_in * adresse);
int initSocket(struct sockaddr_in * adresse);
int waitForClient(int * serverSocket);
int main(void) {
// Structure contenant l'adresse
struct sockaddr_in adresse;
initAdresse(&adresse);
// Descripteur de la socket du serveur
int serverSocket = initSocket(&adresse);
while (1) {
// Descripteur de la socket du client, on attend une connexion
int clientSocket = waitForClient(&serverSocket);
// Envoie du message de bienvenu
send(clientSocket, WELCOME_MESSAGE, strlen(WELCOME_MESSAGE), 0);
char buffer[BUFFER_LEN] = "";
while(strncmp(buffer, EXIT_WORD, 4) != 0){
int len = read(clientSocket, buffer, BUFFER_LEN);
// Ajout du terminateur de chaîne
buffer[len] = '\0';
// On renvoie le texte au client
send(clientSocket, "Vous avez dit : ", strlen("Vous avez dit : "), 0);
send(clientSocket, buffer, strlen(buffer), 0);
}
send(clientSocket, "Bye\n", strlen("Bye\n"), 0);
printf("Fermeture de la connexion avec le client\n");
close(clientSocket);
}
return EXIT_SUCCESS;
}
// Initialisation de la structure sockaddr_in
void initAdresse(struct sockaddr_in * adresse) {
(*adresse).sin_family = AF_INET;
(*adresse).sin_addr.s_addr = IP;
(*adresse).sin_port = htons( PORT);
}
// Démarrage de la socket serveur
int initSocket(struct sockaddr_in * adresse) {
// Descripteur de socket
int fdsocket;
// Nombre d'options
int opt = 1;
// Création de la socket en TCP
if ((fdsocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("Echéc de la création: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("Création de la socket\n");
// Paramètrage de la socket
if (setsockopt(fdsocket, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
sizeof(opt)) != 0) {
printf("Echéc de paramètrage: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("Paramètrage de la socket\n");
// Attachement de la socket sur le port et l'adresse IP
if (bind(fdsocket, (struct sockaddr *) adresse, sizeof(*adresse)) != 0) {
printf("Echéc d'attachement: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("Attachement de la socket sur le port %i\n", PORT);
// Passage en écoute de la socket
if (listen(fdsocket, BACKLOG) != 0) {
printf("Echéc de la mise en écoute: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("Mise en écoute de la socket\n");
return fdsocket;
}
// Attente de connexion d'un client
int waitForClient(int * serverSocket) {
int clientSocket;
// Structure contenant l'adresse du client
struct sockaddr_in clientAdresse;
int addrLen = sizeof(clientAdresse);
printf("En attente d'une connexion\n");
if ((clientSocket = accept(*serverSocket, (struct sockaddr*) &clientAdresse,
(socklen_t*) &addrLen)) == -1) {
printf("Echéc de la récupération de la socket du client: %s\n",
strerror(errno));
exit(EXIT_FAILURE);
}
// Convertion de l'IP en texte
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(clientAdresse.sin_addr), ip, INET_ADDRSTRLEN);
printf("Connexion de %s:%i\n", ip, clientAdresse.sin_port);
return clientSocket;
}