5

By various reasons I would like to implement timeout on reading and writing to socket in a server but fail to get it running and therefore kindly ask for some insight into wherein the problem may reside.

In order to set the timeout on the read and write to the socket I'm trying to use of the functions setsocketopt() and getsocketopt(). However I must be doing something wrong as the return value indicates that a problem have occurred and perror outputs "Invalid argument". Strangely enough the error does not always occur at the first usage of setsocketopt() and getsocketopt(), which puzzles me bit.

The following server and client codes reproduces my problems (compiled using gcc)

server code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <arpa/inet.h>  

int main() {
   struct sockaddr_in saddr,caddr; 
   socklen_t clen;
   int sock, csock, reuse = 1, ret=0;
   socklen_t ntrcv, ntsnd;
   struct timeval tout, tsnd, trcv;

   // create a new stream socket
   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("failed to create socket");
   else {          
      // enable the socket to reuse the address
      if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) perror("failed allowing server socket to reuse address");
      else {
         // set up the server address
         memset((char *) &saddr, 0, sizeof(saddr));
         saddr.sin_family = AF_INET;
         saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
         saddr.sin_port = htons(45454);

         // bind socket to address
         if (bind(sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) perror("failed to bind");
         else {
            // listen to the socket for connections 
            if (listen(sock,5) < 0) perror("failed to listen");
            else {
               clen = sizeof(caddr);
               if ((csock = accept(sock, (struct sockaddr *) &caddr, &clen)) < 0) perror("failed to accept");
               else {
                  tout.tv_sec=0;
                  tout.tv_usec=10000;

                  // check value of errno prior to setting timeout
                  perror("errno prior to timeout");

                  if (getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &trcv, &ntrcv) < 0) perror("2");
                  else if (getsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tsnd, &ntsnd) < 0) perror("3");
                  else if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tout, sizeof(tout)) < 0) perror("4");
                  else if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tout, sizeof(tout)) < 0) perror("5");
                  else {
                     printf ("all ok so far in server\n");
                 sleep(1);
                     if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &trcv, ntrcv) < 0) perror("6");
                     else if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tsnd, ntsnd) < 0) perror("7");
                  }
                  if (close(csock) < 0) perror("failed to close csock");
               }
            }
         }
      }
   }
   if (close(sock) < 0) perror("failed to close sock");
   return ret;
}

client code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
   struct sockaddr_in addr;     
   struct hostent *server; 
   int sock = 0;

   // resolve server name
   if (!(server = gethostbyname("127.0.0.1"))) perror("failed to resolve host");
   else {
      // prepare the server address
      memset((char *) &addr, 0, sizeof(addr));
      addr.sin_family = AF_INET;
      bcopy((char *)server->h_addr, (char *)&addr.sin_addr.s_addr, server->h_length);
      addr.sin_port = htons(45454);

      // create a socket and connect to the server
      if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) perror("failed to create client socket");
      else {
         if (connect(sock,(struct sockaddr *) &addr,sizeof(addr)) < 0) perror("failed to connect to server");
         else {
            printf("Connection is established will sleep now\n");
            sleep(3);
            printf("done sleeping\n");
            close(sock);
            printf("socket is closed\n");
         }
      }
   }
   return 0;
}

In the present example the second call to getsockopts() in the server fails resulting in perror("3") to be called. If I however comment this line out as well as the last call to setsockopts() the first call to getsockopts() fails (which previously seemed to work).

Any insights to where I go wrong are appreciated

cpaitor
  • 423
  • 1
  • 3
  • 16
  • Can I ask about your approach for setting these socket options? You first set the value for `SO_RCVTIMEO` to `tout` (being the value you'd like to be applied), but after a second of sleeping, you set it back to `trcv` (the original value). I'm failing to see the purpose of that. Can you elaborate? – gleerman Nov 24 '16 at 13:02
  • Hi Gleerman, sorry for being unprecise, the line sleep(1) was just intended during my test and should be replaced (as well as the preceeding call to printf() ) with whatever task the server is supposed to peform. Then once done I reset SO_RCVTIMEO to its original value. – cpaitor Jan 18 '17 at 08:09

2 Answers2

5

You are not initialising ntsnd and ntrcv to the size of the buffer available to getsockopt. Therefore if the random value there greater or equal to the size needed, the call will succeed. Else it will fail with EINVAL. From the man page:

The arguments optval and optlen are used to access option values for setsockopt(). For getsockopt() they identify a buffer in which the value for the requested option(s) are to be returned. For getsockopt(), optlen is a value-result argument, initially containing the size of the buffer pointed to by optval, and modified on return to indicate the actual size of the value returned. If no option value is to be supplied or returned, optval may be NULL.

To fix this intialize them both to sizeof(struct timeval).

abligh
  • 24,573
  • 4
  • 47
  • 84
  • Well, there's seem to be some time limit before you can accept, my first attempts simply displayed a red box with the text "there's x minutes untilm you can accept the answer" (or similar) – cpaitor Jan 30 '15 at 20:49
  • Please use code formatting for code, and quote formatting for quotes. – user207421 Jan 30 '15 at 22:30
  • @EJP: I posted about this on meta the last time you picked up on this. I did not use code formatting. I used PRE tags. That was the consensus on meta as to what should be done. See http://meta.stackoverflow.com/questions/284689/quoting-manpages-in-markdown – abligh Jan 30 '15 at 23:32
  • PRE formatting *is* code formatting: same effect; and it is therefore for code. There are plenty of other meta discussions that say so, but it's obvious, – user207421 Dec 04 '15 at 22:44
2

You need to set the ntsnd and ntrcv variables to sizeof ntsnd and sizeof ntrcv respectively, or sizeof struct timeval.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • Thanks, think this is basically what @abligh said, missed that piece of information when I read the man page the first time. – cpaitor Jan 31 '15 at 21:47