web-dev-qa-db-fra.com

Comment définir un délai d'attente pour bloquer les sockets dans boost asio?

Existe-t-il un moyen d'annuler une opération en attente (sans déconnexion) ou de définir un délai d'expiration pour les fonctions de bibliothèque de boost?

C'est à dire. Je veux définir un délai d'expiration sur le blocage du socket dans boost asio?

socket.read_some (boost :: asio :: buffer (pData, maxSize), error_);

Exemple: je veux en lire dans le socket, mais je veux lancer une erreur si 10 secondes se sont écoulées.

34
Brian R. Bondy

Sous Linux/BSD, le délai d'expiration des opérations d'E/S sur les sockets est directement pris en charge par le système d'exploitation. L'option peut être activée via setsocktopt(). Je ne sais pas si boost::asio fournit une méthode pour le définir ou expose le scripteur de socket pour vous permettre de le définir directement - ce dernier cas n'est pas vraiment portable.

Par souci d'exhaustivité, voici la description de la page de manuel:

SO_RCVTIMEO et SO_SNDTIMEO

          Specify the receiving or sending  timeouts  until  reporting  an
          error.  The argument is a struct timeval.  If an input or output
          function blocks for this period of time, and data has been  sent
          or  received,  the  return  value  of  that function will be the
          amount of data transferred; if no data has been transferred  and
          the  timeout has been reached then -1 is returned with errno set
          to EAGAIN or EWOULDBLOCK just as if the socket was specified  to
          be  non-blocking.   If  the timeout is set to zero (the default)
          then the operation  will  never  timeout.   Timeouts  only  have
          effect  for system calls that perform socket I/O (e.g., read(2),
          recvmsg(2), send(2), sendmsg(2)); timeouts have  no  effect  for
          select(2), poll(2), epoll_wait(2), etc.
9
Nicola Bonelli

Lorsque cette question a été posée, je suppose que l'ASIO n'avait aucun exemple sur la façon d'accomplir ce dont l'OP avait besoin, c'est-à-dire de temporiser une opération de blocage telle qu'une opération de socket de blocage. Il existe maintenant des exemples pour vous montrer exactement comment procéder. l'exemple semble long, mais c'est parce qu'il est bien commenté. Il montre comment utiliser l'ioservice dans un type de mode 'one shot'.

Je pense que l'exemple est une excellente solution. Les autres solutions ici cassent la portabilité et ne profitent pas d'ioservice. si la portabilité n'est pas importante et que l'ioservice semble trop lourd - ALORS - vous ne devriez pas utiliser ASIO. Quoi qu'il en soit, vous aurez un ioservice créé (presque toutes les fonctionnalités ASIO en dépendent, même les sockets de synchronisation), alors profitez-en.

Délai d'expiration d'une opération asio tcp bloquante

Délai d'expiration d'une opération asio udp bloquante

La documentation ASIO a été mise à jour, alors consultez-la pour de nouveaux exemples sur la façon de surmonter certains des "pièges" qu'utilisait ASIO.

17
William Symionow

TL; DR

socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 200 });

RÉPONSE COMPLÈTE Cette question ne cesse d'être posée pendant de nombreuses années. Les réponses que j'ai vues jusqu'à présent sont assez médiocres. J'ajouterai cette information ici dans l'une des premières occurrences de cette question.

Tous ceux qui essaient d'utiliser ASIO pour simplifier leur code réseau seraient parfaitement satisfaits si l'auteur ajoutait simplement un délai d'expiration de paramètre facultatif à toutes les fonctions de synchronisation et d'async io. Malheureusement, il est peu probable que cela se produise (à mon humble avis, juste pour des raisons idéologiques, après tout, AS in ASIO est pour une raison).

Voilà donc les moyens de dépecer ce pauvre chat disponible jusqu'à présent, aucun d'entre eux n'est particulièrement appétissant. Disons que nous avons besoin d'un délai de 200 ms.

1) Bonne (mauvaise) ancienne API socket:

const int timeout = 200;
::setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof timeout);//SO_SNDTIMEO for send ops

Veuillez noter ces particularités: - const int pour timeout - sous Windows le type requis est en fait DWORD, mais l'ensemble actuel de compilateurs a heureusement le même, donc const int fonctionnera à la fois dans Win et Posix World. const char *) pour la valeur. Sous Windows, const char * est requis, Posix nécessite const void *, en C++ const char * sera converti en const void * silencieusement tandis que l'inverse n'est pas vrai.

Avantages: fonctionne et fonctionnera probablement toujours car l'API socket est ancienne et stable. Assez simple. Vite. Inconvénients: techniquement, cela pourrait nécessiter des fichiers d'en-tête appropriés (différents sur Win et même différentes versions UNIX) pour setsockopt et les macros, mais l'implémentation actuelle d'ASIO pollue de toute façon l'espace de noms global avec eux. Nécessite une variable pour la temporisation. Pas de type sûr. Sous Windows, nécessite que le socket soit en mode superposé pour fonctionner (que l'implémentation ASIO actuelle utilise heureusement, mais c'est toujours un détail d'implémentation). LAID!

2) Option de prise ASIO personnalisée:

typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; //somewhere in your headers to be used everywhere you need it
//...
socket.set_option(rcv_timeout_option{ 200 });

Avantages: Assez simple. Vite. Beau (avec typedef). Inconvénients: dépend des détails d'implémentation ASIO, qui pourraient changer (mais OTOH tout changera éventuellement, et ces détails sont moins susceptibles de changer que les API publiques soumises à la normalisation). Mais au cas où cela se produirait, vous devrez soit écrire une classe en fonction de --- (https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/SettableSocketOption.html = (qui est bien sûr un PITA majeur grâce à une ingénierie évidente de cette partie de l'ASIO) ou mieux encore revenir à 1.

3) Utilisez les fonctionnalités asynchrones/futures C++.

#include <future>
#include <chrono>
//...
auto status = std::async(std::launch::async, [&] (){ /*your stream ops*/ })
    .wait_for(std::chrono::milliseconds{ 200 });
switch (status)
    {
    case std::future_status::deferred:
    //... should never happen with std::launch::async
        break;
    case std::future_status::ready:
    //...
        break;
    case std::future_status::timeout:
    //...
        break;
    }

Avantages: standard. Inconvénients: démarre toujours un nouveau thread (en pratique), qui est relativement lent (peut être suffisant pour les clients, mais conduira à une vulnérabilité DoS pour les serveurs car les threads et les sockets sont des ressources "coûteuses"). N'essayez pas d'utiliser std :: launch :: deferred au lieu de std :: launch :: async pour éviter le lancement d'un nouveau thread car wait_for retournera toujours future_status :: deferred sans essayer d'exécuter le code.

4) La méthode prescrite par ASIO - utilise uniquement des opérations asynchrones (ce qui n'est pas vraiment la réponse à la question).

Avantages: assez bon pour les serveurs également si une grande évolutivité pour les transactions courtes n'est pas requise. Inconvénients: assez verbeux (donc je ne vais même pas inclure d'exemples - voir les exemples ASIO). Nécessite une gestion très minutieuse de la durée de vie de tous vos objets utilisés à la fois par les opérations asynchrones et leurs gestionnaires de complétion, ce qui nécessite en pratique que toutes les classes contenant et utilisant ces données dans les opérations asynchrones soient dérivées de enable_shared_from_this, ce qui nécessite que toutes ces classes soient allouées sur le tas, ce qui signifie ( au moins pour les opérations courtes), cette évolutivité commencera à diminuer progressivement après environ 16 threads, car chaque allocation/désaffectation de tas utilisera une barrière de mémoire.

12
Pavel Verevkin

Vous pouvez faire un async_read et également définir une minuterie pour le délai d'attente souhaité. Ensuite, si le minuteur se déclenche, appelez cancel sur votre objet socket. Sinon, si votre lecture se produit, vous pouvez annuler votre minuterie. Cela vous oblige à utiliser un objet io_service bien sûr.

edit: trouvé un extrait de code pour vous qui fait cela

http://lists.boost.org/Archives/boost/2007/04/120339.php

9
grepsedawk

J'avais la même question, et après quelques recherches, la solution la plus simple et la plus propre que j'ai pu trouver était d'obtenir le socket natif sous-jacent et de faire une sélection jusqu'à ce qu'il y ait des données à lire. Sélectionnez prendra un paramètre de temporisation. Bien sûr, travailler avec le socket natif commence à aller à l'encontre du point d'utiliser asio en premier lieu, mais encore une fois, cela semble être le moyen le plus propre. Pour autant que je sache, asio ne fournit pas un moyen de le faire facilement pour une utilisation synchrone. Code:

        // socket here is:  boost::shared_ptr<boost::asio::ip::tcp::socket> a_socket_ptr

        // Set up a timed select call, so we can handle timeout cases.

        fd_set fileDescriptorSet;
        struct timeval timeStruct;

        // set the timeout to 30 seconds
        timeStruct.tv_sec = 30;
        timeStruct.tv_usec = 0;
        FD_ZERO(&fileDescriptorSet);

        // We'll need to get the underlying native socket for this select call, in order
        // to add a simple timeout on the read:

        int nativeSocket = a_socket_ptr->native();

        FD_SET(nativeSocket,&fileDescriptorSet);

        select(nativeSocket+1,&fileDescriptorSet,NULL,NULL,&timeStruct);

        if(!FD_ISSET(nativeSocket,&fileDescriptorSet)){ // timeout

                std::string sMsg("TIMEOUT on read client data. Client IP: ");

                sMsg.append(a_socket_ptr->remote_endpoint().address().to_string());

                throw MyException(sMsg);
        }

        // now we know there's something to read, so read
        boost::system::error_code error;
        size_t iBytesRead = a_socket_ptr->read_some(boost::asio::buffer(myVector), error);

        ...

Cela sera peut-être utile pour votre situation.

8
Ty Hoffman

Suite à ce que grepsedawk a mentionné. Il y a quelques exemples montrant comment annuler des opérations asynchrones de longue durée après un certain temps, sous la section Timeouts dans asio doco. Boost Asio Exemples . Async TCP m'a le plus aidé.

Happy Asyncing :)

3
denn

Même des années après la question initiale, il n'y a toujours pas de réponse satisfaisante.

Utiliser manuellement select n'est pas une bonne option

  1. le numéro du descripteur de fichier doit être inférieur à 1024
  2. FD peut être faussement signalé comme prêt en raison d'une somme de contrôle incorrecte.

Appeler io_service.run_one() est également une mauvaise idée, car il peut y avoir d'autres options asynchrones qui nécessitent un io_service pour toujours run(). Et le document de boost sur le blocage du client TCP est difficile à comprendre.

Voici donc ma solution. L'idée clé est la suivante:

{
    Semaphore r_sem;
    boost::system::error_code r_ec;
    boost::asio::async_read(s,buffer,
                            [this, &r_ec, &r_sem](const boost::system::error_code& ec_, size_t) {
                                r_ec=ec_;
                                r_sem.notify();
                            });
    if(!r_sem.wait_for(std::chrono::seconds(3))) // wait for 3 seconds
    {
        s.cancel();
        r_sem.wait();
        throw boost::system::system_error(boost::asio::error::try_again);
    }
    else if(r_ec)
        throw boost::system::system_error(r_ec);
}

Ici Semaphore n'est qu'un mutex et une variable_condition.
wait_for est implémenté par http://en.cppreference.com/w/cpp/thread/condition_variable/wait_for

Le code complet est à https://github.com/scinart/cpplib/blob/master/include/asio.hpp
Exemple: https://github.com/scinart/cpplib/blob/6e9a1690bf68971b809be34dfe432949d9a9f727/standalone_example/boost_block_tcp_client_server.cpp

- update - Exemple de lien mis à jour.

1
scinart

SO_RCVTIMEO et SO_SNDTIMEO prendre une structure timeval de "sys/time.h" au lieu d'un int. Ainsi, l'option 1. de @Pavel Verevkin devrait prendre un timeval au lieu d'un int et l'option 2. nécessiterait l'implémentation d'une classe depuis boost::asio::detail::socket_option::integer ne stocke qu'une seule valeur entière.

0
dtsull