When writing a network application you can use non-blocking mode or blocking mode. Non blocking mode is more flexible and required when the application has to do multiple things, like servicing multiple sockets. If however the application is doing only 1 thing, for example reading from a socket and writing the data to a file or queue then using blocking mode can reduce your application’s complexity significantly. There is one small problem with blocking mode, if something goes wrong with the connection the application may never know; it will wait forever for data that will never arrive.

 

How can that happen? Imagine that your application has called recv and is waiting for data from a client. The client’s network connection is unreliable and the client application is experiencing multiple retransmissions. At some point the client’s TCP stack decides that the connection must be terminated; it notifies the client application and cleans up the socket. Your application is left waiting for data on a connection that as far as the client is concerned has been closed. The only way to clear the problem is to manually terminate your application and restart it. Terminating the application leaves the connected socket in a “TIME_WAIT” state that, unless you have set the REUSEADDR socket option, will prevent you from immediately restarting the application and having it bind to the listening socket.

 

You can however tell OpenVOS to set a time limit on how long to wait for data during the recv call. The result is that after the time limit expires the call will return a -1 and errno will be set to e$timeout (1081). Your application can then make a decision on how to proceed. It can terminate the connection, give the client another chance, send something to the client to test the connection, or any other application appropriate response. The point is that the diagnoses of the situation and the response are under your control.
The program timeout_recv.c in figure 2 is an example of how to do this. It takes 2 arguments, a port number to listen on and a timeout in units of 1/1024 of a second. Figure 1 shows an example execution, the command line (bold and underlined) is echoed because I am firm believer that all arguments should be echoed by interactive programs. Note I have set a 10 second (10240 / 1024) timeout. The program reports when it calls accept and if accept returns a timeout. While I didn’t mention it in the previous paragraph the timeout will work on the accept call as well and the program demonstrates that. After a connection is made it reports when it calls recv and the time that recv returned with either a timeout or characters. I have highlighted all the timeout messages.

 

Note that the calls to set the time limits are OpenVOS  (and VOS) specific; this approach will not work under other operating systems.

 

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 – Example execution of 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

© 2019 Stratus Technologies.