2010-01-29 7 views
1

Tout d'abord, je n'ai jamais travaillé avec C avant (principalement Java, c'est la raison pour laquelle vous me trouverez écrire du code C naïf). Je suis en train d'écrire un interpréteur de commande simple C. J'ai quelque chose comme ceci:C: En utilisant un appel select, quand je lis, comment puis-je garder une trace des données?

//Initialization code 

if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) { 
    perror("Select dead"); 
    exit(EXIT_FAILURE); 
} 

.... 
.... 
//Loop through connections to see who has the data ready 
//If the data is ready 
if ((nbytes = recv(i, buf, sizeof(buf), 0)) > 0) { 
    //Do something with the message in the buffer 
} 

Maintenant, si je regarde quelque chose comme un long paragraphe de commandes, il est évident qu'un tampon de 256 octets ne sera pas en mesure pour obtenir la commande entière. Pour le moment, j'utilise un tampon de 2056 octets pour obtenir la commande entière. Mais si je veux utiliser le tampon de 256 octets, comment pourrais-je faire cela? Est-ce que je garde la trace du client qui m'a donné quelles données et l'a ajouté à un tampon? Je veux dire, utiliser quelque chose comme des tableaux bidimensionnels et autres?

Répondre

3

Oui, l'approche habituelle consiste à avoir un tampon de "données que j'ai reçues mais non traitées" pour chaque client, assez grand pour contenir le plus grand message de protocole. Vous lisez dans ce tampon (en gardant toujours une trace de combien de données est actuellement dans le tampon), et après chaque lecture, vérifiez si vous avez un message complet (ou message (s), car vous pourriez obtenir deux immediatement!). Si vous le faites, vous traitez le message, retirez-le du tampon et déplacez toutes les données restantes jusqu'au début du tampon.

Quelque chose à peu près le long des lignes de:

for (i = 0; i < nclients; i++) 
{ 
    if (!FD_ISSET(client[i].fd, &read_fds)) 
     continue; 

    nbytes = recv(client[i].fd, client[i].buf + client[i].bytes, sizeof(client[i].buf) - client[i].bytes, 0); 

    if (nbytes > 0) 
    { 
     client[i].bytes += nbytes; 

     while (check_for_message(client[i])) 
     { 
      size_t message_len; 

      message_len = process_message(client[i]); 
      client[i].bytes -= message_len; 
      memmove(client[i].buf, client[i].buf + message_len, client[i].bytes); 
     } 
    } 
    else 
     /* Handle client close or error */ 
} 

Par ailleurs, vous devriez vérifier si errno == EINTRselect() retourne -1, et la boucle juste encore - ce n'est pas une erreur fatale.

2

Je garderais une structure autour de chaque client. Chaque structure contient un pointeur vers un tampon où la commande est lue. Peut-être que vous libérez les tampons quand ils ne sont pas utilisés, ou peut-être vous les gardez autour. La structure pourrait également contenir le fd du client. Ensuite, vous avez juste besoin d'un tableau (ou liste) de clients que vous bouclez.

L'autre raison que vous voudriez faire ceci, outre le fait que 256 octets pourraient ne pas être assez, est que recv ne remplit pas toujours le tampon. Certaines données peuvent encore être en transit sur le réseau. Cependant, si vous conservez des tampons pour chaque client, vous pouvez lancer l'attaque «slowloris», où un seul client envoie de petites quantités de données et occupe toute votre mémoire.

+0

Parfait! Je ne connaissais pas l'attaque slowloris ... Merci beaucoup aux autres aussi ... – Legend

1

Cela peut être très pénible lorsque vous recevez des tonnes de données de ce type sur un réseau. Il y a un échange constant entre l'allocation d'un grand tableau ou plusieurs lectures avec des mouvements de données. Vous devriez envisager d'obtenir une liste de tampons prête à l'emploi, puis de parcourir la liste chaînée tout en lisant les tampons dans chaque nœud de la liste chaînée. De cette façon, il évolue avec élégance et vous pouvez rapidement supprimer ce que vous avez traité. Je pense que c'est la meilleure approche et c'est aussi comment boost asio implémente des lectures bufférisées.

1

Si vous traitez avec plusieurs clients une approche commune à fork/exec pour chaque connexion. Votre serveur serait à l'écoute des connexions entrantes, et quand il est fait, il fork et et exec une version enfant de lui-même qui gérerait alors la partie "interpréteur de commandes" du problème. De cette façon, vous laissez le système d'exploitation gérer les processus client, c'est-à-dire que vous n'avez pas besoin d'une structure de données dans votre programme pour les gérer. Vous devrez toujours nettoyer les processus enfants sur votre serveur lorsqu'ils se terminent. En ce qui concerne la gestion de la mémoire tampon ... Combien de données attendez-vous avant de publier une réponse? Vous devrez peut-être être préparé pour ajuster dynamiquement la taille de votre tampon.

+0

Je pensais en fait à vérifier la présence d'un caractère EOF que mon protocole a ... Donc je suppose que ce que je ferais c'est de continuer à ajouter ce tampon à la ligne de données principale jusqu'à ce que je vois réellement ce personnage .. Que pensez-vous? – Legend

+0

J'aurais peur de ne pas avoir de garde. Avez-vous confiance à l'expéditeur des données pour se comporter - pour toujours envoyer des données bien formées? Qu'en est-il du réseau? Qu'en est-il du système d'exploitation? Je ne voudrais pas aveuglément ajouter des données d'origine inconnue à un tampon sans un peu de protection en place. Même si vous avez écrit le client, les données sont toujours d'origine inconnue, c'est-à-dire ne vous faites pas confiance. –

+0

Je vois ... Vous avez votre logique ... Merci pour les pointeurs ... – Legend