【问题标题】:Proper use of getsockopt and setsockopt for SO_RCVTIMEO and SO_SNDTIMEO为 SO_RCVTIMEO 和 SO_SNDTIMEO 正确使用 getsockopt 和 setsockopt
【发布时间】:2015-03-30 09:53:46
【问题描述】:

由于各种原因,我想在服务器中对套接字进行读取和写入超时,但未能使其运行,因此请深入了解问题可能存在的位置。

为了设置对套接字的读写超时,我尝试使用函数setsocketopt()getsocketopt()。但是我一定是做错了什么,因为返回值表明发生了问题并且 perror 输出“无效参数”。奇怪的是,第一次使用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() 的第一次调用会失败(以前似乎可行)。

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

【问题讨论】:

  • 我能问一下您设置这些套接字选项的方法吗?您首先将SO_RCVTIMEO 的值设置为tout(即您要应用的值),但在休眠一秒钟后,您将其设置回trcv(原始值)。我看不到这样做的目的。你能详细说明一下吗?
  • 嗨 Gleerman,抱歉不准确,sleep(1) 行只是在我的测试期间使用的,应该用服务器的任何任务替换(以及之前对 printf() 的调用)应该执行。然后一旦完成,我将 SO_RCVTIMEO 重置为其原始值。

标签: c sockets timeout setsockopt


【解决方案1】:

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

参数optvaloptlen 用于访问setsockopt() 的选项值。对于getsockopt(),它们标识了一个缓冲区,请求选项的值将在该缓冲区中返回。对于getsockopt()optlen 是一个值结果参数,最初包含optval 指向的缓冲区的大小,并在返回时修改以指示返回值的实际大小。如果不提供或返回选项值,optval 可能是NULL

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

【讨论】:

  • @cpaitor:没问题 - 表示感谢的最佳方式是接受答案 :-)
  • 好吧,在你接受之前似乎有一些时间限制,我的第一次尝试只是显示一个红色框,上面写着“还有 x 分钟你可以接受答案”(或类似的)跨度>
  • 请对代码使用代码格式,对引号使用引号格式。
  • @EJP:我在 meta 上发布了有关此内容的信息,这是您上次了解此内容的时候。我没有使用代码格式。我使用了 PRE 标签。这是关于元数据应该做什么的共识。见meta.stackoverflow.com/questions/284689/…
  • PRE格式化is代码格式化:效果一样;因此它适用于代码。还有很多其他的元讨论都这么说,但很明显,
【解决方案2】:

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

【讨论】:

  • 谢谢,认为这基本上是@abligh 所说的,当我第一次阅读手册页时错过了那条信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-19
  • 2014-05-19
  • 1970-01-01
  • 2015-06-13
  • 1970-01-01
  • 2012-10-21
  • 1970-01-01
相关资源
最近更新 更多