lire et écrire sur le même socket (TCP) en utilisant select

Nous écrivons un client et un serveur pour faire (ce que je pensais être) des communications réseau assez simples. Plusieurs clients se connectent au serveur, qui est alors censé renvoyer les données à tous les autres clients.

Le serveur se trouve dans une boucle de select bloquante en attente de trafic et, le cas échéant, envoie les données aux autres clients. Cela semble bien fonctionner.

Le problème est le client. En réponse à une lecture, il voudra parfois faire une écriture.

Cependant, j’ai constaté que si j’utilise:

  rv = select(fdmax + 1, &master_list, NULL, NULL, NULL); 

Mon code va bloquer jusqu’à ce qu’il y ait de nouvelles données à lire. Mais parfois (de manière asynchrone, à partir d’un autre thread), j’aurai de nouvelles données à écrire sur le thread de communication réseau. Donc, je veux que mon choix se réveille périodiquement et me laisse vérifier s’il y a des données à écrire, comme:

 if (select(....) != -1) { if (FD_SET(sockfd, &master_list)) // handle data or disconnect else // look for data to write and write() / send() those. } 

J’ai essayé de définir le mode de sélection en mode de scrutation (ou des délais ridiculement courts) avec:

 // master list contains the sockfd from the getaddrinfo/socket/connect seq struct timeval t; memset(&t, 0, sizeof t); rv = select(fdmax + 1, &master_list, NULL, NULL, &t); 

mais ont constaté qu’alors le client n’obtient jamais de données entrantes.

J’ai aussi essayé de définir le socket fd pour qu’il ne soit pas bloquant, comme ceci:

 fcntl(sockfd, F_SETFL, O_NONBLOCK); 

mais cela ne résout pas le problème:

  1. si mon client select() n’a pas de struct timeval , la lecture des données fonctionne, mais elle ne se débloque jamais pour me permettre de rechercher des données en écriture.
  2. si mon client select() dispose d’un timeval pour l’interroger, il ne signale jamais qu’il y a des données entrantes à lire et mon application se fige en pensant qu’il n’y a pas de connexion réseau établie (malgré le fait que tous les autres appels de fonctions ont abouti)

Des indications sur ce que je pourrais faire de mal? N’est-il pas possible de faire en lecture-écriture sur le même socket (je ne peux pas croire que cela soit vrai).

(EDIT: La bonne réponse, et ce dont je me souvenais sur le serveur mais pas sur le client, est d’avoir un deuxième fd_set et de copier master_list avant chaque appel de select ():

 // declare and FD_ZERO read_fds: // put sockfd in master_list while (1) { read_fds = master_list; select(...); if (FD_ISSET(read_fds)) .... else // sleep or otherwise don't hog cpu resources } 

)

Tout va bien, sauf la ligne où vous faites if (FD_SET(sockfd, &master_list)) . J’ai une structure de code très similaire et j’ai utilisé FD_ISSET . Vous êtes censé tester si la liste est définie, ne pas la redéfinir. À part ça, je ne vois rien d’autre.

Modifier. En outre, j’ai les éléments suivants pour le délai d’attente:

 timeval listening_timeout; listening_timeout.tv_sec = timeout_in_seconds; listening_timeout.tv_usec = 0; 

peut-être y at-il un problème si vous le définissez sur 0 (comme vous semblez le faire?)

Edit2. Je me suis rappelé que j’avais rencontré un problème étrange alors que je n’effaçais pas le jeu de lecture après la sortie de la sélection et avant que je ne le repasse. Je devais faire quelque chose comme:

 FD_ZERO(&sockfd); FD_SET(sockfd, &rd); 

avant que je sois entré select . Je ne me souviens plus pourquoi.

Il me semble me rappeler une astuce sur la création et le partage d’un archiveur de fichiers en lecture / écriture entre le fil réseau et le fil principal ajouté aux descripteurs de l’appel de sélection. Ce fd a un octet écrit par le thread principal quand il a quelque chose à envoyer. L’écriture réveille le thread réseau de l’appel de sélection et le thread réseau récupère ensuite les données d’un tampon partagé et les écrit sur le réseau, puis se met en veille dans le select.

Désolé si c’est un peu vague et qu’il manque de code … et que ma mémoire est peut-être incorrecte … alors d’autres personnes pourraient devoir vous guider davantage.

Je ne vois rien de mal avec votre code, donc ça devrait marcher. Si vous ne pouvez pas le faire fonctionner, une façon de le contourner serait de créer un tuyau à utiliser par votre thread de lecture et le fil préparant les choses pour l’écriture, et d’append la fin de lecture du tuyau à votre ensemble de select . Ensuite, lorsque l’autre thread a préparé l’écriture des données, il envoie simplement quelque chose sur le tuyau, votre thread de lecture est réveillé à partir de la select et il peut ensuite écrire. Selon la fréquence à laquelle il y a des données à lire ou à écrire, cela pourrait également être plus efficace.

Deux threads doivent pouvoir travailler avec le même socket à la fois, votre thread principal doit donc pouvoir écrire sur un client pendant que l’autre dort en attente d’attendre les données entrantes. Bien entendu, cela suppose que les deux threads ont access à la liste des clients.