Write potentiellement bloquant après un select

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Dans le cadre d'un client / serveur, je suis amené a gérer plusieurs clients en lecture et en écriture. J'utilise la fonction select, qui prend un fd_set de lecture, d'écriture et la valeur max présente dans les fds.

Problématique : si un client se connecte, s'authentifie, je lui envoi des données régulièrement. Si le client reste connecté mais ne read plus (volontairement, il peut mais il le fait pas), et que le serveur veut lui envoyer quelques choses, select me dira que je peux write. Puisque mon client ne read pas, je vais accumuler dans le buffer d'écriture de la socket, toutes les données envoyées. La taille maximale de ce buffer est de 64k. Lorsque le buffer d'écriture de la socket sera rempli, select ne me permettra plus d'écrire. Ce cas là est géré par select, c'est parfait.

Le vrai problème est si select m'autorise à écrire, mais que la quantité de ce que je veux écrire est supérieur à la taille qu'il reste dans le buffer d'écriture de la socket, mon write va être bloquant. Et le méchant client aura simplement fait planter mon server en ne lisant simplement plus ce que je lui envoi.

Comment je peux répondre à cette problématique ?

J'ai pensé à :

  • utiliser ioctl avec un incrémenteur : dès que je write j'additionne la longueur dans cet incrémenteur, dès que j'arrive vers les 60k (mon buffer fait 4k), je fais un FION_WRITE qui me dis combien d'octets sont dans le buffer d'écriture de la socket, et je peux en déduire le nombre d'octets max à écrire et agir en conséquence.
  • connaître la taille minimale du buffer d'écriture pour que select considère que je peux write, et split chacun de mes messages à une taille inférieur.

La première solution est extrèmement couteuse et plutôt lourde à implémenter, mais je ne sais pas comment connaître la taille minimal qui permet au trigger de select de détecter que je peux write sur telle ou telle socket.

J'ai exploré différentes pistes :

  • man de select, select_tu, et tous les autres man lié à select(listen, socket, bind, …)
  • farfouillage dans les .h qu'utilise select
  • exploration du code source de select.

La fonction select appelle core_sys_select. La fonction core_sys_select appelle do_select. Le seul moment où do_select modifie le pointeur du fd_set de write c'est dans un for infinie, qui contient un for qui boucle sur les fds. Tout semble parfait. Le seul moment où il modifie le pointeur passé à select pour surveiller les write, c'est sur une condition à base de POLLSET. C'est une macro contenant plusieurs infos. La plus haute valeur de cette macro étant 256, donc je comprend pas.

Si quelqu'un à cette information, ou si vous avez des remarques / idées…

D'avance, merci.

krostar.

+0 -0

Je vais peut être répondre complètment à côté (ça fait longtemps que j'ai plus touché à ce genre de choses) mais, d'après le man, il est possible de fournir le flag SOCK_NONBLOCK lors de l'appel à socket().
Dans le cas d'un write() qui devrait être bloquant tu pars alors sur une erreur et errno vaut EAGAIN ou EWOULDBLOCK.

C'est pas le fait que la socket soit bloquante le problème : j'utilise select pour justement ne pas être bloquant : select me ressort quand je peux lire, quand je peux écrire. Je n'ai donc pas besoin de read / write non bloquant (ou de send / recv avec des flags qui les rendent non bloquant). Rendre la socket non bloquante me pose plusieurs problèmes :

  • Je n'en vois pas l'utilité : j'utilise select.
  • Je n'ai jamais fais de cette façon (donc ça pourrait être périlleux / totalement pensé différemment).
  • C'est à l'encontre des consignes que je me suis fixé / que l'on m'a donné.

Je ne pense pas que ce soit une solution, si quelqu'un peut confirmer / infirmer ! Merci pour ta réponse en tous cas.

Salut,

La solution proposée par olzd semble faire double emploi de prime abord, mais ce n'est pas le cas. En spécifiant un descripteur comme non bloquant, tu évites les cas de blocage dû à une trop grande quantité de données à écrire (alors que select ne le permet visiblement pas).

+0 -0

Si vraiment tu détectes que ton client ne répond plus (ça peut arriver si le réseau coupe par exemple), tu peux aussi choisir de le déconnecter. Reste à savoir comment tu détectes qu'il ne répond plus: sachant qu'un message de ping laisserait possible le cas du write bloquant, tu peux effectivement analyser la taille du contenu dans le buffer, et kicker le client si cette taille est trop importante.

Dans un projet «réel», la solution serait sûrement les écritures non bloquantes (et tout de même un système de ping timeout pour ne pas conserver un client mort), mais dans le cadre de ton projet scolaire, tu dois pouvoir t'en sortir comme ça.

La question reste d'actualité du coups : ne serait-il pas plus simple de laisser à select le travail de regarder si je peux écrire ou pas (chose qu'il est censé faire beaucoup mieux que moi), et me contenter de vérifier si la taille du message que je veux écrire est inférieure à la taille que select m'autorise à écrire.

Puisque je ne peux pas savoir facilement, sans faire le travail de select, la taille que je peux écrire sur le buffer tcp, j'aimerais simplement récupérer une define ou truc du style, qui défini le seuil minimum à écrire pour que select considère que je peux le faire.

Ce n'est pas la solution que tu souhaites, mais tu peux éviter un blocage complet de ton write en utilisant l'option SO_SNDTIMEO sur le socket. Comme le nom l'indique, elle permet de spécifier un timeout sur les opérations d'écriture bloquantes.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte