D'après mes lectures, ce serait à cause des administrateurs réseaux qui se mettraient trop vite à râler![]()
D'après mes lectures, ce serait à cause des administrateurs réseaux qui se mettraient trop vite à râler![]()
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
ben que ce soit un socjket standard ou un socket unix, ça va quand même créer un process..
Donc si c'est à cause du nombre de process, c'est kifkif..
Si c'est à cause du nombre de ports, sur unixoides on a quand même de la marge... Sur Windows oui c'est (très nettement) plus limité.
Maintenant, pour ton problème de base, pourquoi ne pas utiliser des pipes ?
Parce que c'est mal !
Plus précisément :
- Sémantiquement, tu ne cherches pas à rendre l'application accessible depuis l'extérieur, mais simplement à signaler son existence. Et ça, on ne peut pas le savoir a priori ;
- C'est une faille de sécurité potentielle. Si ton application n'est pas construite pour faire de la gestion réseau bien costaud, je peux éventuellement faire planter ton application et/ou ton système en ouvrant des milliers de connexions en parallèle, ou en lui envoyant des données corrompues ;
- Cela oblige à accaparer un port réseau fixe et, en TCP ou UDP, ceux-ci sont limités. Avec seulement 65536 ports, les risques de collision ne sont pas négligeables ;
- Cela oblige à rajouter une permission dédiée à ton firewall personnel s'il y en a un sur ta machine. Si c'est un administrateur système qui s'occupe de cela. Il est capable de te dire non pour le principe, et il aurait raison ;
- Si tu optes pour le réseau, il faudra choisir le type de réseau que tu utilises. En grandes entreprises et sur de vieux serveurs, il est possible que ces machines n'utilisent pas TCP/IP mais en soient encore à IPX/SPX, par exemple (même si je reconnais que ça devient rare) ;
Mais surtout :
- Un port réseau dédié interdit de fait à ton application de fonctionner sur un serveur mutualisé (ce qui est quand même un des piliers des serveurs UNIX, et pas seulement eux).
Là où je travaillais, nous utilisions plusieurs serveurs centralisés en salle machine (Solaris sur Sunfire, Linux IA32/64, et W2K idem) accédés par les utilisateurs avec des terminaux type thin client qui leur permettaient entre autres d'ouvrir plusieurs sessions en parallèle vers différents serveurs. Or, sur ce genre de plateforme, avec un port réseau dédié, le premier à lancer l'application la verrouille pour tous les autres utilisateurs, et reçoit indûment les requêtes de tous ces autres utilisateurs tentant de démarrer la même application.
Le problème est exactement le même si tu ouvres plusieurs sessions utilisateurs sur un même PC de bureau.
Là, pour le coup, l'admin aura une bonne raison de t'en vouloir, surtout si ton application a été écrite sur mesure (et donc coûté cher). Et si je suis si virulent, c'est parce qu'on a été confronté au problème.
Ça aurait l'avantage de fonctionner partout, y compris sur des UNIX extrêmement vieux ne proposant pas les sockets. Cela dit, si c'est pour voir si une application est déjà ouverte ou pas, on ne peut pas utiliser les pipes anonymes, et les tubes nommés ont les inconvénients rédhibitoires d'être à sens unique, d'une part, et de ne pas permettre de savoir s'il y a quelqu'un à l'autre bout (c'est le but). On reste en mode bloqué (sauf flag) qu'il y ait un processus qui refuse de lire, ou qu'il n'y ait pas de processus du tout.
Euh.. je ne vois pas pourquoi.. Si le serveur est enregistré comme service, n'importe qui peut s'y connecter parallèllement...
oui, mais d'après le message de troumad page précédente (en réponse au tien), il serait pas mal intéressé de pouvoir communiquer avec (lui passer des infos et autres), et pas seulement de savoir si il est ouvert...
Il me semble que des pipes seraient le plus adapté, non ?
Ok, dans ce sens-là…
C'est possible mais ça t'oblige à mettre en place un architecture explicitement client-serveur, et à gérer toutes les transactions avec les autres instances lancées légitimement. Ça complique beaucoup la chose pour rien, car ce n'était pas l'objectif initial (savoir simplement si une instance était déjà lancée). Ça pose aussi la question du propriétaire du processus serveur.
Mais surtout, ça ne justifie toujours pas l'usage du réseau. Tout ceci peut toujours être fait en interne.
C'est d'ailleurs toute la raison d'être des sockets : établir des points de communication entre processus. Mais les domaines dans lesquels ils peuvent être établis sont multiples, et si c'est en interne sur une machine UNIX, le plus indiqué reste toujours AF_UNIX.
À condition que ce soit le père qui lance le fils et qui lui fasse hériter des descripteurs, ce qui n'est pas l'objet du problème. On cherche toujours à savoir si, en lançant une application, celle-ci n'a pas déjà été lancée auparavant. Si c'est le cas, on peut éventuellement lui envoyer des ordres mais cela vient en second lieu.Il me semble que des pipes seraient le plus adapté, non ?
En fait, le problème de Troumad est celui du mutex, au sens large. On pourrait résoudre le problème avec d'autres choses, comme une IPC SysV, une fois mis d'accord sur le lieu de rendez-vous, mais ça pose toujours le problème du choix de l'ID, de la compatibilité entre plate-formes, de la possibilité de lancer plusieurs instances en parallèle, etc.
Un truc à base de communications par sockets, donc identique à la programmation réseau, mais basé sur le système de fichiers (philosophie UNIX) et avec un espace de nommage bien plus vaste et bien plus explicite est clairement le plus indiqué.
en fait, un petit programme Fortran ou Pascal utilisant des common et block data ferait l'affaire
ça doit d'ailleurs bien exister quelque part, la description de l'espace disque commun...
A moins que ça ne soit ce que tu mentionnais plus haut ?
Mais à priori on devrait pouvoir mettre directement tout un tas d'informations, pourvu qu'on ait la même définition de part et d'autre...
(je n'ai jamais d'ailleurs compris pourquoi ce truc tellement utile n'avat pas été repris par les autres langages.. VMS avait à un moment donné (je ne le "fréquente" plus depuis belle lurette) utilisé un mécanisme similaire pour des variables système (un style de getenv/putenv, mais à travers l'ensemble du système, krnel compris))
PS: à propos des sockets et clients/serveurs, je ne sais pas pourquoi cette notion de "service" a été plus ou moins oubliée...
En effet.
Je n'ai jamais eu à faire au Fortran (c'est une lacune :-) mais il me semble qu'un COMMON BLOCK se loge au sein d'un segment de mémoire partagée. Auquel cas le langage se rapproche du système pour créer ce segment puis le remplit, en lui ajoutant éventuellement un identifiant pour qu'on le retrouve.
Donc, dans ce cas, cela va donner lieu à une IPC SysV dont je parlais plus haut.
Là encore, le problème réside non pas dans l'échange de données (d'ailleurs facultatif pour le problème qui nous incombe aujourd'hui), mais dans le choix à l'avance du lieu de rendez-vous, identifiable sans ambiguïté. Accessoirement, ce serait sympa pour l'admin si cette ressource pouvait être humainement identifiable.Mais à priori on devrait pouvoir mettre directement tout un tas d'informations, pourvu qu'on ait la même définition de part et d'autre...
Elle n'a pas été oubliée. Elle passe son temps à être réinventée, à chaque fois sous des noms différents. En général, le cheminement est toujours le même. On crée une application, puis on se retrouve confronté au problème de gestion exclusive des ressources. Donc, on commence par mettre en place un système permettant à une instance de se signaler, puis on s'aperçoit que le problème s'étend au multi-utilisateurs, et là, on en arrive naturellement à créer son daemon pour gérer tout cela, et on se sent une âme d'architecte. :-) Ensuite, les daemons de ce style se multiplient. On se dit que ce serait bien d'avoir un truc unifié, mais cela implique à toutes les applications de respecter les règles d'un même framework.(je n'ai jamais d'ailleurs compris pourquoi ce truc tellement utile n'avat pas été repris par les autres langages.. VMS avait à un moment donné (je ne le "fréquente" plus depuis belle lurette) utilisé un mécanisme similaire pour des variables système (un style de getenv/putenv, mais à travers l'ensemble du système, krnel compris))
PS: à propos des sockets et clients/serveurs, je ne sais pas pourquoi cette notion de "service" a été plus ou moins oubliée...
Et du coup, comme tout le monde réinvente plus ou moins la roue à un moment ou un autre, ça donne naissance à tout un tas de trucs comme CORBA, DCOM, ou DCOP puis D-BUS.
On comprend ainsi que les architectures distribuées, même au sein d'une même machine, sont devenues une composante omniprésente de la programmation moderne. Mais pour qu'un de ces frameworks soit réellement plébiscité, il faudrait qu'il soit standardisé, que les normes des nouveaux systèmes d'exploitation spécifient sa disponibilité par défaut, et qu'il soit suffisamment bien conçu pour ne pas avoir à être revu de fond en comble après quelques années d'existence (c'est l'une des forces d'UNIX d'être encore exploitable avec des API âgées de 35 ans en moyenne).
Visiblement mon problème pose problème !
Après une longue lecture intéressante de vos propositions, j'aimerai voir rapidement les pipes...Je prévois de laisser un fichier avec l'id du processus (je m'en sers déjà sous LInux et Windows de cet id pour tester un peu). Après, je peux voir si le processus avec l'Id en question est bien le bon. Non ?
Et finalement s'il existe je fais un pipe. La communication n'a pas besoin d'être bidirectionnelle. J'ai juste besoin de passer les argc et argv récupéré par le second programme appelé au premier qui tourne déjà.
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
Un certain nombre de programmes procèdent déjà de cette manière.
Mais en créant un socket UNIX, tu fais tout cela en une seule fois. Tu crées un socket avec l'appel socket(), lequel va prendre la forme d'un descripteur de fichier exactement comme le ferait pipe(). En guise d'adresse, tu choisis un nom de fichier, du style « /var/run/monapplication.monpid.sock », en rajoutant éventuellement un UUID si tu crains les collisions. Tu l'associes à ton socket avec bind() exactement comme tu le ferais avec une adresse IP. Cela va automatiquement créer l'entrée sur le système de fichier.
À ce stade, n'importe qui peut voir si ton application est déjà lancée et avec quel PID. Avec un simple « ls », l'admin système peut déjà faire son travail si besoin.
L'application étant identifiée, il suffit de se connecter à ce nom de fichier, là encore de la même façon que tu te connecterais à un port réseau pour voir si l'application répond, et si elle le fait correctement. Ça te permet de savoir d'un coup si
— l'application existe toujours ;
— si le processus qui occupe ledit PID est bien celui que l'on croit ;
— si l'application n'a pas planté.
Dans les autres cas, tu fais le ménage en supprimant l'entrée comme si c'était un fichier ordinaire (celui-là même que tu comptais créer). Donc avec « rm » depuis le Shell ou unlink() depuis le C.
volà de quoi je parlais :
Sharing Data
ou
Using Command Language Interpreter Symbols and Using the Common Area)
Les variables internes du kernel de VMS sont également lisibles/settables par des symboles du même style (LIB$ ou les variables SYS$ (des exemples ici))
Simplement génial...
Et perdu pour beaucoup...
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
J'ai trouvé une petite réponse à une petite question :http://gtk.developpez.com/doc/en/gtk/GtkSocket.html
Mais, vais-bien utiliser les socket si je veux la compatibilité WIndows-Linux ?
À moins que la méthode conseillée sous Windows n'existe pas sous Linux ?
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
Non.
Mais les processus sont une notion inhérente à système d'exploitation, au sens large. Les gérer relève donc de la programmation système. Il te faut donc écrire une fonction appelée par ton programme principal et qui, elle, choisira la meilleure méthode à suivre en fonction de l'environnement dans lequel elle se trouve.
Même au sein d'un même O.S., d'ailleurs. Sous Linux, par exemple, en fonction du fichier de configuration, tu peux lui demander d'utiliser des sockets UNIX par défaut, ou d'utiliser D-Bus, si le fichier le précise et si elle peut en trouver (là encore) une instance active.
Pour Windows, il y a beaucoup de choses disponibles : voir Interprocess Synchronization.
L'idée d'utiliser les sockets correspond surtout à quelque chose que l'on peut considérer comme standard sur tous les UNIX aujourd'hui, et qui fait directement partie du système.
Ici, tu utilises GTK, qui fait une bonne partie du travail à ta place, mais qu'il faut installer avec ton application ou vérifier sa disponibilité. Si tu t'appuies déjà sur cette lib, alors elle doit elle-même proposer directement d'autres facilités pour mettre des données en commun, en s'appuyant sur les facilités que chaque système propose.
Je ne les ai plus en tête, mais tu peux faire une recherche…
Je viens de me faire un client serveur avec vérification simple des données transmises.
Voici le fichier qui marche ne mode client si le serveur est déjà lancé :Ce programme peut avoir plusieurs clients lancés en même temps => ils seront traités simultanément par les fils !
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #define SERVER_PATH "/tmp/server" #define BUFFER_LENGTH 250 #define FALSE 0 #ifndef SUN_LEN #define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) + strlen ((ptr)->sun_path)) #endif int main(int argc, char *argv[]) { int sd=-1, sd2=-1, rc; unsigned short i,c; char buffer[BUFFER_LENGTH],buffer1[BUFFER_LENGTH]; struct sockaddr_un serveraddr; char d; pid_t pid; sd = socket(AF_UNIX, SOCK_STREAM, 0); /* création du socket AF_UNIX : on ne passe pas par un port, ce sera interne au PC */ if (sd < 0) /* retour un entier si ça c'est bien passé, sinon -1 */ { perror("echec de socket()"); } else { serveraddr.sun_family = AF_UNIX; if (argc > 1) strcpy(serveraddr.sun_path, argv[1]); else strcpy(serveraddr.sun_path, SERVER_PATH); rc = connect(sd, (struct sockaddr *)&serveraddr, SUN_LEN(&serveraddr)); if (rc < 0) { perror("serveur absent : le programme devient serveur"); do { memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sun_family = AF_UNIX; strcpy(serveraddr.sun_path, SERVER_PATH); rc = bind(sd, (struct sockaddr *)&serveraddr, SUN_LEN(&serveraddr)); if (rc < 0) { perror("echec de bind()"); break; } do { rc = listen(sd, 10); if (rc< 0) { perror("echec de listen()"); break; } printf("Pret pour la connexion du client.\n"); sd2 = accept(sd, NULL, NULL); if (sd2 < 0) { perror("echec du accept()"); break; } pid = fork(); if (pid==0) { /* le fils prend la main : le père va attendre le prochain processus qui demande la main */ do { /* boucle sur la connexion ouverte */ printf("programme pid %d attend communication\n",getpid()); rc = recv(sd2, buffer, sizeof(buffer), 0); if (rc < 0) { perror("echec de recv()"); break; } if (rc == 0 ) { printf("Le client a fermé la connexion\n"); break; } if (rc<BUFFER_LENGTH) buffer[rc]=0; /*finir la chaine de caractères par un 0 car il n'est pas mis en fin du message */ c=0; for (i=0;i<strlen(buffer);i++) /* fabrication du code de vérification */ { c^=buffer[i]; } printf("%d bites de données ont été reçus signature %hu : %s\n", rc,c,buffer); sprintf(buffer,"%hu",c); rc = send(sd2, buffer, strlen(buffer), 0); if (rc < 0) { perror("echec de send()"); break; } } while (1); /* pour recevoir plusieurs fois : tant que le client n'est pas fermé */ if (sd2 != -1) { close(sd2); sd2=-1; } return 0; /* le fils se tue à la fin de la communication */ } } while (1); /* Fin de la communication avec un process : passage au suivant */ /* si on lance trois fois le programme, le premier sera serveur et commencera à discuter avec le second */ /* le serveur communiquera avec le troisième que lorsque le second sera fermé */ } while (0); /* seul intérêt : arriver pour les break */ unlink(SERVER_PATH); /* supprimer le socket du disque dur */ } else do /* le serveur est présent : le programme prend la version client */ { /* boucle sur la connexion ouverte */ fflush(stdin); fgets(buffer, BUFFER_LENGTH, stdin); rc = send(sd, buffer, strlen(buffer), 0); if (rc < 0) { perror("echec de send()"); break; } c=0; for (i=0;i<strlen(buffer);i++) { c^=buffer[i]; } rc = recv(sd,buffer1,BUFFER_LENGTH, 0); if (rc == 0) { printf("Le serveur a fermé la connexion\n"); break; } if (rc<BUFFER_LENGTH) buffer1[rc]=0; /*finir la chaine de caractères par un 0 */ printf("%d : %s\n",rc,buffer1); sscanf(buffer1,"%hu",&i); if (c==i) printf("Vérification OK\n"); else printf("Erreur dans le transfert ici %hu, là-bas : %hu\n",c,i); fflush(stdin); printf("Continuer ? (o/n) : "); do c=getchar(); while (c!='o' && c!='n'); do /* vider le buffer jusqu'au saut de ligne */ { d=getchar(); printf("%c",d); } while (d!='\n'); } while(c=='o'); } if (sd != -1) close(sd); if (sd2 != -1) close(sd2); return 0; }
Je viens donc de tester aussi les processus père/fils : un gros problème, le pid change. Il va falloir que je ruse ou que je revois ce que j'avais pensé !
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
Hello !
Félicitations, ton programme fonctionne. Il manque toutefois #include <unistd.h> en tête de programme et SUN_LEN() n'est pas reconnu sur ma distrib' Linux (Fedora 13 avec GCC 4.4.5).
Une fois ces deux détails mineurs corrigés chez moi, ton programme semble fonctionner comme tu l'entends.
Je corrige le #include qui manque et je cherche pour SUN_LEN() .
J'ai rajouté deux autres corrections et commentaires.
Je proposerais bien ce code comme exemple dans les sources C ! Mais je continue à travailler dessus...
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
Honnêtement, je ne suis pas assez vieux ni barbu pour savoir d'où vient cette macro, mais il est de fait qu'elle pose les mêmes problèmes à plusieurs personnes.
http://mail-index.netbsd.org/tech-ne...0/11/0008.html
Étant donné que la norme C, elle, spécifie clairement comment fonctionne sizeof et, par définition, fonctionne partout, tu peux utiliser « sizeof serveraddr » (sans l' « & ») à la place, comme tout le monde.
J'ai failli le faire également mais j'ai préféré te laisser aller au bout, et c'était apparemment une bonne chose. N'hésite pas à le faire quand tu auras fini.Je proposerais bien ce code comme exemple dans les sources C ! Mais je continue à travailler dessus...
J'ai trouvé ça pour le remplacer :Je trouve très bizarre le "((struct sockaddr_un *) 0)->sun_path)" ! Ce serait offsetof(struct sockaddr_un, sun_path) si je comprends.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3 #ifndef SUN_LEN #define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) + strlen ((ptr)->sun_path)) #endif
Par contre, dans ton exemple, il y a des choses que je ne comprends pas ! Par exemple :Ça fait quoi ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 #define STATIC_ASSERT(x) \ do { \ switch (1) { \ case 0 != (x): break; \ case 0: break; \ } \ } while (0)
Le programme que tu proposes rajoute une sécurité au niveau du débordement de l'addition.
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
À dire vrai, je ne « proposais » pas le programme joint. C'était la discussion sur SUN_LEN() qui m'intéressait.
Oui. Mais offsetof() est elle-même censée être une macro définie par la norme C (je ne sais pas à partir de quand, par contre) et se trouvant dans stddef.h. Ceci permet d'éviter d'avoir à inclure ce fichier uniquement pour cette ligne.
En plus, à cause de certaines ambiguïtés, stddef.h ne se trouve pas forcément à la racine de /usr/include (Sous GNU/Linux, on ne le trouve qu'en dessous de /usr/include/linux, comme si c'était une spécificité du noyau) et en plus, GCC code cette macro en dur :
http://en.wikipedia.org/wiki/Offsetof
http://gcc.gnu.org/onlinedocs/gcc-4..../Offsetof.html
Ça ressemble à un hack assez obscur pour faire, comme son nom l'indique, un assert statique (même si assert() est une macro, elle teste la véracité de ton expression au runtime et appelle au final une fonction de la libC) en générant un code qui soit ne peut pas compiler si x est faux, soit génère un code toujours vrai et ne faisant rien, pouvant être optimisé et donc annihilé par le compilo. C'est donc un assert() au niveau de la précompilation.Par contre, dans ton exemple, il y a des choses que je ne comprends pas ! Par exemple :Ça fait quoi ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 #define STATIC_ASSERT(x) \ do { \ switch (1) { \ case 0 != (x): break; \ case 0: break; \ } \ } while (0)
Donc, si x est faux, on aura deux "case 0", donc la compilation ne se fera pas. Pour ceci, il faut que x soit connu lors de la compilation. C'est bien ça ?
Le x en question est "sizeof *addr == offsetof(struct sockaddr_un, sun_path) + sizeof addr->sun_path" dans le contexte suivant :Je suis autodidacte en C : mes profs ne connaissaient que le Pascal
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 static socklen_t calc_sockaddr_un_size(const char *path) { struct sockaddr_un *addr; size_t size; if (strlen(path) < sizeof addr->sun_path) { return sizeof *addr; } /* Ensure that sun_path is the last member */ STATIC_ASSERT(sizeof *addr == offsetof(struct sockaddr_un, sun_path) + sizeof addr->sun_path);
Et là, il y a des choses qui me dépassent !
1) pourquoi pas de () après le sizeof
2) je ne comprends pas l'égalité qui doit être testée !
Modérateur Mageia/Mandriva Linux
Amicalement VOOotre
Troumad Alias Bernard SIAUD à découvrir sur http://troumad.org
Mes tutoriels : xrandr, algorigramme et C, xml et gtk...
Partager