Lors de l'écriture d'une application réseau, vous pouvez utiliser le mode non bloquant ou le mode bloquant. Le mode non bloquant est plus flexible et nécessaire lorsque l'application doit faire plusieurs choses, comme par exemple l'entretien de plusieurs prises. Toutefois, si l'application ne fait qu'une seule chose, par exemple lire à partir d'une socket et écrire les données dans un fichier ou une file d'attente, l'utilisation du mode bloquant peut réduire considérablement la complexité de votre application. Il y a un petit problème avec le mode de blocage : si quelque chose ne va pas avec la connexion, l'application ne le saura peut-être jamais ; elle attendra éternellement des données qui n'arriveront jamais.
Comment cela peut-il se produire ? Imaginez que votre demande ait appelé le service de recouvrement et qu'elle attende les données d'un client. La connexion réseau du client n'est pas fiable et l'application cliente subit de multiples retransmissions. À un moment donné, la pile TCP du client décide que la connexion doit être interrompue ; elle en informe l'application cliente et nettoie la socket. Votre application est laissée en attente de données sur une connexion qui, en ce qui concerne le client, a été fermée. La seule façon de résoudre le problème est de terminer manuellement votre application et de la redémarrer. La fermeture de l'application laisse la socket connectée dans un état "TIME_WAIT" qui, à moins que vous n'ayez activé l'option REUSEADDR socket, vous empêchera de redémarrer immédiatement l'application et de la lier à la socket d'écoute.
Vous pouvez toutefois demander à OpenVOS de fixer un délai d'attente pour les données pendant l'appel de récupération. Le résultat est qu'à l'expiration du délai, l'appel renvoie un -1 et errno est réglé sur e$timeout (1081). Votre demande peut alors prendre une décision sur la manière de procéder. Elle peut mettre fin à la connexion, donner une autre chance au client, envoyer quelque chose au client pour tester la connexion, ou toute autre réponse appropriée à l'application. Le fait est que le diagnostic de la situation et la réponse sont sous votre contrôle.
Le programme timeout_recv.c de la figure 2 est un exemple de la façon de procéder. Il faut 2 arguments, un numéro de port à écouter et un délai d'attente en unités de 1/1024 de seconde. La figure 1 montre un exemple d'exécution, la ligne de commande (en gras et soulignée) est répétée car je suis convaincu que tous les arguments doivent être répétés par les programmes interactifs. Notez que j'ai fixé un délai d'attente de 10 secondes (10240 / 1024). Le programme signale quand il appelle accept et si accept, il renvoie un délai d'attente. Bien que je ne l'aie pas mentionné dans le paragraphe précédent, le délai d'attente fonctionne également pour l'appel d'acceptation et le programme le démontre. Une fois la connexion établie, le programme signale le moment où il appelle recv et le temps que recv retourne avec un délai d'attente ou des caractères. J'ai mis en évidence tous les messages de timeout.
Notez que les appels pour fixer les délais sont spécifiques à OpenVOS (et VOS) ; cette approche ne fonctionnera pas sous d'autres systèmes d'exploitation.
timeout_recv 5647 10240 command line was: timeout_recv 5647 10240 timeout_recv: 12:14:27 calling accept timeout_recv: 12:14:37 accept returned a timeout timeout_recv: 12:14:37 calling accept timeout_recv: 12:14:47 accept returned a timeout timeout_recv: 12:14:47 calling accept timeout_recv: 12:14:48 calling recv timeout_recv: 12:14:58 recv returned a timeout timeout_recv: 12:14:58 calling recv timeout_recv: 12:15:00 recv return 1 characters timeout_recv: 12:15:00 calling recv timeout_recv: 12:15:10 recv returned a timeout timeout_recv: 12:15:10 calling recv timeout_recv: 12:15:16 recv return 1 characters timeout_recv: 12:15:16 calling recv timeout_recv: 12:15:23 recv return 1 characters timeout_recv: 12:15:23 calling recv timeout_recv: 12:15:30 recv return 1 characters timeout_recv: 12:15:30 calling recv timeout_recv: 12:15:38 recv return 1 characters timeout_recv: 12:15:38 calling recv timeout_recv: 12:15:46 recv return 1 characters timeout_recv: 12:15:46 calling recv timeout_recv: 12:15:56 recv returned a timeout timeout_recv: 12:15:56 calling recv timeout_recv: client terminated connection at 12:16:00 ready 12:16:00 |
Figure 1 - Exemple d'exécution de timeout_recv
/* ***************************************************************** timeout_recv written by NSDavids Stratus CAC 10-04-23 version 1.0 initial release This is a demonstration of timing out blocking accept and recv calls and returning control to the program. ***************************************************************** */ #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define bzero(s, len) memset((char *)(s), 0, len) int errno; void s$set_io_time_limit(short int *, long int *, short int *); int s$c_get_portid_from_fildes(int file_des); /* this routine returns the current time as a string */ char * GetCurrentTime (char * szT) { time_t tTime; struct tm *tmLT; tTime = time ((time_t *) 0); tmLT = localtime (&tTime); sprintf (szT, "%02ld:%02ld:%02ld", tmLT -> tm_hour, tmLT -> tm_min, tmLT -> tm_sec); return (szT); } /* this routine sets the time out value on the port associated with the socket. These calls are VOS specific, this program is not portable to other environments. i.e. Windows or Linux */ int SetTimeOut (int iSocket, int iTimeLimit, char * szType) { long lPortID; short sPortID; long lTimeLimit; short sError; if ((lPortID = s$c_get_portid_from_fildes(iSocket)) == -1) { printf ("timeout_recv: Error getting port ID of %s socketn", szType); exit (-1); } sPortID = (short)lPortID; if (iTimeLimit > 0) { lTimeLimit = iTimeLimit; s$set_io_time_limit (&sPortID, &lTimeLimit, &sError); if (sError != 0) { printf ("timeout_recv: Error %d setting time out of %s socketn", szType, sError); exit (sError); } } return (0); } main (argc, argv) int argc; char *argv []; { int iCliLen; struct sockaddr_in sockCliAddr, sockServAddr; short sPortNo; int iBytes; int iTimeLimit; char szT [32]; int iListenSock, iAcceptedSock; #define BUFFERLEN 100 char szBuffer [BUFFERLEN]; /* process the arguments */ if (argc == 3) { sPortNo = atoi (argv [1]); iTimeLimit = atoi (argv [2]); printf ("command line was: timeout_recv %d %dnn", sPortNo, iTimeLimit); } else { printf ("nUsage: timeout_recv <port_number> <time_limit in 1/1024s of a sec)n"); exit (-1); } /* set up a listening socket */ if ((iListenSock = socket (AF_INET, SOCK_STREAM, 0)) < 0) printf ("timeout_recv: Error %d can't open stream socket", errno); bzero ( (char *) &sockServAddr, sizeof (sockServAddr)); sockServAddr.sin_family = AF_INET; sockServAddr.sin_addr.s_addr = htonl (INADDR_ANY); sockServAddr.sin_port = htons (sPortNo); if (bind (iListenSock, (struct sockaddr *) &sockServAddr, sizeof (sockServAddr)) < 0) { printf ("timeout_recv: Error %d can't bind local addressn", errno); exit (errno); } listen (iListenSock, 5); SetTimeOut (iListenSock, iTimeLimit, "Listening"); /* In most cases I expect that an application will just block waiting for a connection. However, I wanted to show that you can also timeout the call to accept */ iAcceptedSock = 0; while (iAcceptedSock < 1) { printf ("timeout_recv: %s calling acceptn", GetCurrentTime (szT)); iAcceptedSock = accept (iListenSock, (struct sockaddr *) &sockCliAddr, &iCliLen); if (iAcceptedSock < 0) { if (errno == 1081) printf ("timeout_recv: %s accept returned a timeoutn", GetCurrentTime (szT)); else { printf ("timeout_recv: %s accept returned the unexpected error %dn", GetCurrentTime (szT), errno); exit (errno); } iAcceptedSock = 0; } } /* set the timeout on the newly accepted socket */ SetTimeOut (iAcceptedSock, iTimeLimit, "Accepted"); /* main loop, call recv and process the return value. If the return value is -1 then it is an error. An errno of 1081 (e$timeout) is expected, report it and continue. Any other error is unexpected and fatal. If the return value is 0 it means the client terminated the connection, report it and exit. If the return value is > 0 it indicates the number of characters received, report it and continue the loop. */ while (1) { printf ("timeout_recv: %s calling recvn", GetCurrentTime (szT)); iBytes = recv (iAcceptedSock, szBuffer, BUFFERLEN, 0); if (iBytes == -1) { if (errno == 1081) printf ("timeout_recv: %s recv returned a timeoutn", GetCurrentTime (szT)); else { printf ("timeout_recv: %s recv returned the unexpected error %dn", GetCurrentTime (szT), errno); exit (errno); } } else if (iBytes == 0) { printf ("timeout_recv: client terminated connection at %sn", GetCurrentTime (szT)); exit (0); } else printf ("timeout_recv: %s recv return %d charactersn", GetCurrentTime (szT), iBytes); } } |
Figure 2 - timeout_recv.c