2011-02-07 3 views
17

J'ai un client et un serveur utilisant boost::asio de manière asynchrone. Je veux ajouter quelques délais pour fermer la connexion et potentiellement réessayer si quelque chose ne va pas. Ma première pensée était que chaque fois que j'appelle une fonction async_ je devrais également commencer un deadline_timer pour expirer après que j'attende l'opération asynchrone pour terminer. Maintenant, je me demande si cela est strictement nécessaire dans tous les cas.Est-ce que les appels boost :: asio async s'arrêtent automatiquement?

Par exemple:

  • async_resolve utilise probablement le résolveur du système qui a timeouts construit en elle (par exemple RES_TIMEOUT en resolv.h éventuellement substituée par la configuration en /etc/resolv.conf). En ajoutant ma propre minuterie, je peux entrer en conflit avec la façon dont l'utilisateur veut que son résolveur fonctionne.

  • Pour async_connect, le connect(2) syscall a une sorte de délai d'attente construit en elle

  • etc.

Alors que (le cas échéant) async_ appels sont garantis pour appeler leurs gestionnaires au sein d'un " raisonnable "délai? Et si une opération [peut | does] timeout serait le gestionnaire d'être passé l'erreur basic_errors::timed_out ou autre chose?

+0

J'ai dû m'en tenir à créer moi-même 'deadline_timer', donc je serais très intéressé de voir si c'est inutile. – chrisaycock

+0

+1 question intéressante. Je pense que la réponse dépendrait largement de la plateforme. Je suppose que vous vous souciez de Linux? –

Répondre

29

J'ai donc fait quelques tests. Sur la base de mes résultats, il est clair qu'ils dépendent de la mise en œuvre du système d'exploitation sous-jacent. Pour référence, j'ai testé ceci avec un noyau Fedora en stock: 2.6.35.10-74.fc14.x86_64.

L'essentiel est que async_resolve() semble être le seul cas où vous pourriez être en mesure de sortir sans mettre un deadline_timer. Il est pratiquement nécessaire dans tous les autres cas pour un comportement raisonnable.


async_resolve()

Un appel à async_resolve() a donné lieu à des requêtes 4 5 secondes d'intervalle. Le gestionnaire a été appelé 20 secondes après la demande avec l'erreur boost::asio::error::host_not_found.

Mon résolveur prend par défaut un délai de 5 secondes avec 2 tentatives (resolv.h), il semble donc envoyer deux fois le nombre de requêtes configurées. Le comportement est modifiable en définissant options timeout et options attempts dans /etc/resolv.conf. Dans tous les cas, le nombre de requêtes envoyées était double quel que soit attempts et le gestionnaire a été appelé avec l'erreur host_not_found par la suite.

Pour le test, le serveur de noms unique configuré était routé en noir.


async_connect()

Appel async_connect() avec une destination noir routé trou a donné lieu dans le gestionnaire appelé à l'erreur boost::asio::error::timed_out après 189 secondes ~.

La pile a envoyé les SYN et 5 tentatives initiaux. La première tentative a été envoyée après 3 secondes, avec le délai de nouvelle tentative doublant chaque fois (3 + 6 + 12 + 24 + 48 + 96 = 189). Le nombre de réitérations peut être modifié:

% sysctl net.ipv4.tcp_syn_retries 
net.ipv4.tcp_syn_retries = 5 

La valeur par défaut de 5 est choisi pour se conformer à la norme RFC 1122 (4.2.3.5):

[Les temporisateurs de retransmission] pour un segment SYN DOIT être définir assez grand pour fournir retransmission du segment pendant au moins 3 minutes. L'application peut fermer la connexion (c.-à-d., Abandonner sur la tentative ouverte) plus tôt, bien sûr.

3 minutes = 180 secondes, bien que la RFC ne semble pas spécifier de limite supérieure. Rien n'empêche une implémentation de réessayer pour toujours.


async_write()

Tant que le tampon d'envoi du socket n'a pas été complète, ce gestionnaire a toujours été appelé tout de suite.

Mon test a établi une connexion TCP et a défini un temporisateur pour appeler async_write() une minute plus tard. Au cours de la minute où la connexion a été établie, mais avant l'appel async_write(), j'ai essayé toutes sortes de Mayhem:

  • Définition d'un routeur en aval pour le trafic ultérieur trou noir vers la destination.
  • Suppression de la session dans un pare-feu en aval afin qu'elle réponde avec des RST falsifiés à partir de la destination.
  • Ethernet
  • mon Débranchement
  • Exécution /etc/init.d/network stop

Peu importe ce que je faisais, la prochaine async_write() serait immédiatement appeler son gestionnaire de signaler le succès.

Dans le cas où le pare-feu falsifié la TVD, la connexion a été fermée immédiatement, mais je n'avais aucun moyen de savoir que jusqu'à ce que je tentais la prochaine opération (qui immédiatement rapport boost::asio::error::connection_reset). Dans les autres cas, la connexion resterait ouverte et ne me rapporterait pas d'erreurs jusqu'à ce qu'elle prenne fin 17-18 minutes plus tard.

Le pire des cas pour async_write() est que l'hôte est en train de retransmettre et que le tampon d'envoi est plein.Si le tampon est plein, async_write() n'appellera pas son gestionnaire tant que les retransmissions n'auront pas expiré. Linux par défaut à 15: retransmissions

% sysctl net.ipv4.tcp_retries2 
net.ipv4.tcp_retries2 = 15 

Le temps entre les retransmissions augmente après chaque (et repose sur de nombreux facteurs tels que le temps aller-retour estimé de la connexion spécifique) mais est fixée à 2 minutes. Donc, avec les 15 retransmissions par défaut et le délai le plus défavorable de 2 minutes, la limite supérieure est de 30 minutes pour que le gestionnaire async_write() soit appelé. Lorsqu'elle est appelée, l'erreur est définie sur boost::asio::error::timed_out.


async_read()

Cela ne devrait jamais appeler son gestionnaire tant que la connexion est établie et aucune donnée est reçue. Je n'ai pas eu le temps de le tester.

+2

Celui-ci devrait être la réponse ... donne beaucoup plus de détails pertinents que ce que ma réponse courte était. – diverscuba23

10

Ces deux appels PEUVENT avoir des délais d'attente qui sont propulsés à vos gestionnaires, mais vous pourriez être supprimé pendant le temps qu'il prend avant l'expiration de ces délais. (Je sais que j'ai laissé une connexion juste s'asseoir et essayer de se connecter sur un seul appel de connexion pendant plus de 10 minutes avec boost::asio avant de tuer le processus). Les appels async_read et async_write ne sont pas associés à des délais d'attente. Par conséquent, si vous souhaitez avoir des délais d'attente pour vos lectures et écritures, vous aurez toujours besoin d'un deadline_timer.

+1

+ 1 en cas de doute, soyez explicite. Ajoutez vos propres minuteurs si le comportement n'est pas documenté. –

+0

Oui. C'est à peu près ce que j'ai trouvé. Bien que 'async_read' et' async_write' * aient * des délais d'attente dans le sens où des retransmissions excessives finissent par entraîner la fermeture de la connexion après des dizaines de minutes. – eater