7
votes

C - Comment utiliser AIO_Read () et AIO_WRITE ()

Je mettez le serveur de jeu où j'ai besoin de lire et d'écrire. Donc, j'accepte la connexion entrante et commencez à lire à partir de celui-ci en utilisant AIO_RELAD () FORT> mais quand je dois envoyer quelque chose, je cesse de lire en utilisant AIO_CANCEL () STRY>, puis utilisez AIO_WRITE () fort>. Dans le rappel de l'écriture, je reprends la lecture. Donc, je lis tout le temps, mais quand j'ai besoin d'envoyer quelque chose - je pause em> lecture.

Cela fonctionne pendant environ 20% du temps - dans d'autres cas d'appel à AIO_CANCEL () strong> échoue avec "opération maintenant en cours" - et je ne peux pas l'annuler (même dans les permanents tandis que le cycle em>). Donc, mon opération d'écriture ajoutée n'arrive jamais. P>

Comment utiliser ces fonctions bien? Qu'est-ce que j'ai manqué? P>

Edit: Utilisé sous Linux 2.6.35. Ubuntu 10 - 32 Bit. P>

Exemple code: P>

void handle_read(union sigval sigev_value) { /* handle data or disconnection */ }
void handle_write(union sigval sigev_value) { /* free writing buffer memory */ }
void start()
{
    const int acceptorSocket = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
    bind(acceptorSocket, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));

    listen(acceptorSocket, SOMAXCONN);

    struct sockaddr_in address;
socklen_t addressLen = sizeof(struct sockaddr_in);

    for(;;)
    {
         const int incomingSocket = accept(acceptorSocket, (struct sockaddr*)&address, &addressLen);
         if(incomingSocket == -1)
         { /* handle error ... */}
         else
         {
              //say socket to append outcoming messages at writing:
              const int currentFlags = fcntl(incomingSocket, F_GETFL, 0);
              if(currentFlags < 0) { /* handle error ... */ }
              if(fcntl(incomingSocket, F_SETFL, currentFlags | O_APPEND) == -1) { /* handle another error ... */ }

              //start reading:
              struct aiocb* readingAiocb = new struct aiocb;
              memset(readingAiocb, 0, sizeof(struct aiocb));
              readingAiocb->aio_nbytes = MY_SOME_BUFFER_SIZE;
              readingAiocb->aio_fildes = socketDesc;
              readingAiocb->aio_buf = mySomeReadBuffer;
              readingAiocb->aio_sigevent.sigev_notify = SIGEV_THREAD;
              readingAiocb->aio_sigevent.sigev_value.sival_ptr = (void*)mySomeData;
              readingAiocb->aio_sigevent.sigev_notify_function = handle_read;
              if(aio_read(readingAiocb) != 0) { /* handle error ... */ }
          }
    }
}

//called at any time from server side:
send(void* data, const size_t dataLength)
{
    //... some thread-safety precautions not needed here ...

    const int cancellingResult = aio_cancel(socketDesc, readingAiocb);
    if(cancellingResult != AIO_CANCELED)
    {
        //this one happens ~80% of the time - embracing previous call to permanent while cycle does not help:
        if(cancellingResult == AIO_NOTCANCELED)
        {
            puts(strerror(aio_return(readingAiocb))); // "Operation now in progress"
            /* don't know what to do... */
        }
    }
    //otherwise it's okay to send:
    else
    {
        aio_write(...);
    }
}


6 commentaires

Vous devez spécifier le système d'exploitation que c'est. Essayez également de créer un exemple autonome minimal qui montre le problème. Sinon, êtes-vous sûr de votre nécessité de l'annuler? Je pense qu'il est parfaitement correct d'avoir des opérations de lecture et d'écriture en attente sur le même descripteur de fichiers.


En C ++, j'utilise deux threads pour fonctionner sur la même prise en même temps. Un lecteur, un écrivain. Je pense que cela pourrait aussi travailler avec l'AIO, mais je ne suis pas sûr, mais peut-être que cela pourrait répondre à quelqu'un.


Pourriez-vous expliquer pourquoi vous devez mettre en pause la lecture? L'AIO ne vous permet pas de délivrer une demande d'écriture avec l'annulation de la lecture?


Lorsque je le fais, je reçois mes données uniquement après que certaines données soient lues après appel à AIO_WRITE () - SO AIO en faisait ces opérations en une seule file d'attente. Quand j'obtiens des données (dans le handle_read ()), j'appelle à nouveau AIO_RELAD () pour continuer à lire - il est donc devenu possible simplement de lire-écriture-lecture-écrire ... pas comme une liste de lecture-écriture-écriture-écriture .. .


Le serveur de jeu fonctionne actuellement en utilisant une approche Epoll, mais lorsqu'il est confronté à notre sommet de clients à ~ 5000 - elle consommait environ 30% de la CPU. Mon patron envisage d'atteindre beaucoup plus de clients (on dirait qu'il y aura - c'est un jeu social - il y a des tonnes de joueurs) - j'ai lu quelque part que l'utilisation de l'AIO APROCH est beaucoup plus rapide mais n'a vu aucun exemple de son utilisation (c'est plus moderne que les sockets de toute façon). Quelqu'un a-t-il utilisé? Ou suis-je seul?


@Slav AIO est mort et Braindead. Certaines implémentations le font en réalité en utilisant des threads;).


4 Réponses :


6
votes

Tout d'abord, envisager dumping AIO . Il y a beaucoup d'autres façons de faire des E / S asynchrones qui ne sont pas comme Braindead (oui, l'AIO est une refoulée). Beaucoup d'alternatives; Si vous êtes sous Linux, vous pouvez utiliser libaïio ( io_submit et amis). AIO (7) mentionne ceci.

Retour à votre question.
Je n'ai pas utilisé aio dans une longue période, mais voici ce que je me souviens. AIO_RELAD et AIO_WRITE Mettez les deux Demandes ( aiocb ) sur une queue. Ils reviennent immédiatement même si les demandes complèteront quelque temps plus tard. Il est tout à fait possible de faire la queue de plusieurs demandes sans attentionné ce qui est arrivé aux précédents. Donc, en un mot: Arrêtez d'annuler les demandes de lecture et continuez à les ajouter. xxx

plus tard, vous êtes libre d'attendre avec AIO_SUSPEND < / Code>, sondage en utilisant AIO_ERROR , attendez les signaux, etc.

Je vois que vous mentionnez Epoll dans votre commentaire. Vous devriez certainement aller pour libaïio .


6 commentaires

Merci. Qu'utilise la chaîne "lecture-écriture-lecture-écriture"? Est-ce que je serai capable de, par exemple, écrivez deux fois sans lire entre eux? Actuellement, je fais face à une vraie file d'attente - je ne peux pas écrire sans attendre Lisa_handler (je fais aio_write (), mais cela ne fait rien avant que AIO_RELAD () le fera de son travail - lisez au moins un octet du client). Je vais regarder libaïio .


ls.sourceforge.net/io/aio.html - est-ce à propos de Libaio? Si tel est le cas, il a "AIO lecture et écrivez sur les sockets" clause dans "Qu'est-ce qui ne fonctionne pas?" MENU. Ai-je manqué quelque chose?


@Slav Voir le lien vers io_submit dans la réponse.


"La mise en œuvre actuelle de Linux POSIX AIO est fournie dans les utilisateursPace par glibc." Wtf? Comment vient-il une idée de mettre en œuvre quelque chose comme celui-ci dans Wenterland? Je veux dire, n'est-il pas clair que tout le monde vaut mieux que cela soit mis en œuvre qu'avec quelque chose qui accélère les threads Userland?


@MK: POSIX AIO n'est pas une alternative à l'IO non bloquante et à sélectionner () / sondage () / Epoll (); C'est une alternative pour rouler votre propre piscine de fil lors de l'IO sur des fichiers réguliers. Voir ma réponse.


@Slav a raison. Libaio est synchrone pour les sockets.



1
votes

Il ne devrait pas y avoir aucune raison d'arrêter ou d'annuler une demande de lecture ou d'écriture AIO simplement parce que vous devez faire une autre lecture ou écrire. Si tel était le cas, cela vaincre tout le point de lecture et d'écriture asynchrones puisqu'il s'agit de vous permettre de configurer une lecture ou une opération d'écriture, puis passez à autre chose. Étant donné que plusieurs demandes peuvent être en file d'attente, il serait beaucoup mieux de configurer quelques pools de lecteur / écrivain asynchrones dans lesquels vous pouvez saisir un ensemble de structures d'AIOCB AiOCB pré-initialisées à partir d'une piscine "disponible". Configuration des opérations asynchrones Chaque fois que vous en avez besoin, puis retournez-les dans une autre piscine "finie" lorsqu'elle est terminée et que vous pouvez accéder aux tampons qu'ils indiquent. Pendant que ce sont au milieu d'une lecture ou d'une écriture asynchrone, ils seraient dans une piscine "occupée" et ne seraient pas touchées. De cette façon, vous n'aurez pas à continuer à créer des structures aiocb sur le tas dynamiquement chaque fois que vous devez faire une opération de lecture ou d'écriture, bien que cela ne soit pas bien à faire ... il n'est tout simplement pas très efficace si Vous n'avez jamais prévu d'aller sur une certaine limite ou de ne pas avoir qu'un certain nombre de demandes «en vol».

BTW, gardez à l'esprit avec quelques demandes asynchrones différentes dans les vols que votre gestionnaire de lecture / écriture asymétrique peut effectivement être interrompu par un autre événement de lecture / écriture. Donc, vous ne voulez vraiment pas faire beaucoup de choses avec votre gestionnaire. Dans le scénario ci-dessus, j'ai décrit, votre gestionnaire déplacerait essentiellement le aiocb struct qui a déclenché le gestionnaire de signal de l'une des piscines à la suivante dans la liste "Disponible" -> "Occupé" -> "Terminé "Étapes. Votre code principal, après la lecture de la mémoire tampon pointée par le AIOCB dans la piscine "Terminé" déplacerait alors la structure dans le pool "disponible".


0 commentaires

6
votes

Si vous souhaitez avoir des files d'attente de l'AIO séparées pour des lectures et écrites, de sorte qu'une écriture émise ultérieurement puisse exécuter avant une lecture publiée plus tôt, vous pouvez utiliser DUP () pour créer un duplicata de la prise et utilisez-en un pour émettre des lectures et l'autre pour émettre des écritures.

Cependant, j'ai deuxième que les recommandations permettent d'éviter tout l'AIO et d'utiliser simplement une boucle d'événement Epoll () avec des prises non bloquantes. Cette technique a été montrée à l'échelle à un nombre élevé de clients - si vous obtenez une utilisation élevée du processeur, profilez-le et découvrez où cela se passe, car les chances sont que c'est pas votre boucle d'événement qui est le coupable .


1 commentaires

J'ai utilisé DUP () et cela a fonctionné. Merci. Donc, maintenant, j'ai les deux implémentations - les testera sur le projet en direct et les comparer (la partie réseau est implémentée en tant que module autonome pouvant être commuté au moment de la compilation).



3
votes

Sauf si je ne me trompe pas, POSIX AIO (c'est-à-dire AIO_Read (), AIO_WRITE (), etc.) n'est garanti que sur des descripteurs de fichiers déséquilibrés. De l'AIO_Read () Manpage:

   The  data  is  read starting at the absolute file offset aiocbp->aio_offset, regardless of the
   current file position.  After this request, the value of the current file position is unspeci‐
   fied.


0 commentaires