为 SO_RCVTIMEO 和 SO_SNDTIMEO 正确使用 getsockopt 和 setsockopt

Proper use of getsockopt and setsockopt for SO_RCVTIMEO and SO_SNDTIMEO

由于各种原因,我想在读取和写入服务器中的套接字时实现超时,但未能实现 运行,因此请您深入了解问题可能出在哪里。

为了设置读取和写入套接字的超时时间,我尝试使用函数 setsocketopt()getsocketopt()。但是,我一定是做错了什么,因为 return 值表明发生了问题并且错误输出 "Invalid argument"。奇怪的是,错误并不总是发生在第一次使用 setsocketopt()getsocketopt() 时,这让我有点困惑。

以下服务器和客户端代码重现了我的问题(使用 gcc 编译)

服务器代码:

#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;
}

客户代码:

#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;
}

在本示例中,服务器中对 getsockopts() 的第二次调用失败,导致调用 perror("3")。但是,如果我将这一行以及对 setsockopts() 的最后一次调用注释掉,那么对 getsockopts() 的第一次调用将失败(以前似乎有效)。

感谢任何对我哪里出错的见解

您没有将 ntsndntrcv 初始化为 getsockopt 可用的缓冲区大小。因此,如果那里的随机值大于或等于所需的大小,调用就会成功。否则它将失败并返回 EINVAL。来自手册页:

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.

要修复此问题,请将它们都初始化为 sizeof(struct timeval)

您需要将ntsndntrcv变量分别设置为sizeof ntsndsizeof ntrcv,或者sizeof struct timeval.