2016-03-04 75 views
2

我试图提供connect()的超时。我搜索了四处,发现了几篇与此相关的文章。我已经编写了我认为应该工作的内容,但不幸的是,我没有从getsockopt()中报告错误。但是当我来写()它失败,错误107 - ENOTCONN。连接到套接字时似乎无法获取超时工作

几点。我正在Fedora 23上运行。connect()的文档表示,它应该返回失败,并返回EINPROGRESS的errno,但对于尚未完成的连接,但是我遇到了EAGAIN,因此我将其添加到了我的检查中。目前我的套接字服务器在listen()调用中将backlog设置为零。许多调用都成功了,但那些失败的调用都失败了,我在write()调用中提到了107-ENOTCONN。

我希望我只是失去了一些东西,但到目前为止无法弄清楚什么。

int domain_socket_send(const char* socket_name, unsigned char* buffer, 
     unsigned int length, unsigned int timeout) 
{ 
    struct sockaddr_un addr; 
    int fd = -1; 
    int result = 0; 

    // Create socket. 

    fd = socket(AF_UNIX, SOCK_STREAM, 0); 
    if (fd == -1) 
     { 
     result = -1; 
     goto done; 
     } 

    if (timeout != 0) 
     { 

     // Enabled non-blocking. 

     int flags; 
     flags = fcntl(fd, F_GETFL); 
     fcntl(fd, F_SETFL, flags | O_NONBLOCK); 
     } 

    // Set socket name. 

    memset(&addr, 0, sizeof(addr)); 
    addr.sun_family = AF_UNIX; 
    strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 

    // Connect. 

    result = connect(fd, (struct sockaddr*) &addr, sizeof(addr)); 
    if (result == -1) 
     { 

     // If some error then we're done. 

     if ((errno != EINPROGRESS) && (errno != EAGAIN)) 
      goto done; 

     fd_set write_set; 
     struct timeval tv; 

     // Set timeout. 

     tv.tv_sec = timeout/1000000; 
     tv.tv_usec = timeout % 1000000; 

     unsigned int iterations = 0; 
     while (1) 
      { 
      FD_ZERO(&write_set); 
      FD_SET(fd, &write_set); 

      result = select(fd + 1, NULL, &write_set, NULL, &tv); 
      if (result == -1) 
       goto done; 
      else if (result == 0) 
       { 
       result = -1; 
       errno = ETIMEDOUT; 
       goto done; 
       } 
      else 
       { 
       if (FD_ISSET(fd, &write_set)) 
        { 
        socklen_t len; 
        int socket_error; 
        len = sizeof(socket_error); 

        // Get the result of the connect() call. 

        result = getsockopt(fd, SOL_SOCKET, SO_ERROR, 
          &socket_error, &len); 
        if (result == -1) 
         goto done; 

        // I think SO_ERROR will be zero for a successful 
        // result and errno otherwise. 

        if (socket_error != 0) 
         { 
         result = -1; 
         errno = socket_error; 
         goto done; 
         } 

        // Now that the socket is writable issue another connect. 

        result = connect(fd, (struct sockaddr*) &addr, 
          sizeof(addr)); 
        if (result == 0) 
         { 
         if (iterations > 1) 
          { 
          printf("connect() succeeded on iteration %d\n", 
            iterations); 
          } 
         break; 
         } 
        else 
         { 
         if ((errno != EAGAIN) && (errno != EINPROGRESS)) 
          { 
          int err = errno; 
          printf("second connect() failed, errno = %d\n", 
            errno); 
          errno = err; 
          goto done; 
          } 
         iterations++; 
         } 
        } 
       } 
      } 
     } 

    // If we put the socket in non-blocking mode then put it back 
    // to blocking mode. 

    if (timeout != 0) 
     { 

     // Turn off non-blocking. 

     int flags; 
     flags = fcntl(fd, F_GETFL); 
     fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); 
     } 

    // Write buffer. 

    result = write(fd, buffer, length); 
    if (result == -1) 
     { 
     int err = errno; 
     printf("write() failed, errno = %d\n", err); 
     errno = err; 
     goto done; 
     } 

done: 
    if (result == -1) 
     result = errno; 
    else 
     result = 0; 
    if (fd != -1) 
     { 
     shutdown(fd, SHUT_RDWR); 
     close(fd); 
     } 
    return result; 
} 

UPDATE 2016年4月5日:

我渐渐明白,也许我需要调用connect()多次,直到成功,毕竟这是非阻塞IO不是异步IO。就像在read()中遇到EAGAIN之后有数据需要读取时,必须再次调用read()。另外,我发现下面的SO问题:

Using select() for non-blocking sockets to connect always returns 1

其中EJP的回答说,你需要发出多个连接()的。另外,从书中引用EJP:

https://books.google.com/books?id=6H9AxyFd0v0C&pg=PT681&lpg=PT681&dq=stevens+and+wright+tcp/ip+illustrated+non-blocking+connect&source=bl&ots=b6kQar6SdM&sig=kt5xZubPZ2atVxs2VQU4mu7NGUI&hl=en&sa=X&ved=0ahUKEwjmp87rlfbLAhUN1mMKHeBxBi8Q6AEIIzAB#v=onepage&q=stevens%20and%20wright%20tcp%2Fip%20illustrated%20non-blocking%20connect&f=false

这似乎表明你需要发出多个连接()的。我修改了这个问题中的代码片段来调用connect(),直到它成功。我可能仍然需要对可能更新传递给select()的超时值进行更改,但那不是我的直接问题。

调用connect()多次似乎已经解决了我原来的问题,这是我调用write()时得到ENOTCONN,我猜是因为套接字没有连接。但是,您可以从代码中看到,我正在跟踪通过选择循环多少次,直到connect()成功。我已经看到了数以千计的数字。这让我担心我处于忙碌的等待循环中。为什么套接字可写,即使它不处于connect()将会成功的状态?调用connect()清除那个可写状态,并且由于某种原因它被操作系统重新设置,或者我真的处于繁忙的等待循环中?

感谢, 尼克

+0

您确保'函数strncpy()'不砍掉从什么是作为'socket_name'传递什么? – alk

+0

所示的代码是否适用于'timeout == 0',会在阻塞模式下表示? – alk

+0

当超时时间为零时,代码没有问题。实际上,代码始于没有超时值。然后,我添加了timeout和“if(timeout!= 0)”语句,并且在select()之后添加了result = -1时的所有代码。 – nickdu

回答

0

http://lxr.free-electrons.com/source/net/unix/af_unix.c

441 static int unix_writable(const struct sock *sk) 
442 { 
443   return sk->sk_state != TCP_LISTEN && 
444    (atomic_read(&sk->sk_wmem_alloc) << 2) <= sk->sk_sndbuf; 
445 } 

我不知道什么被比较这些缓冲区,但它看起来很明显,连接状态的套接字未被检查。因此,除非这些缓冲区在套接字连接时被修改,否则会出现我的unix套接字将始终标记为可写,因此我无法使用select()来确定何时完成非阻塞connect()。

基于从http://lxr.free-electrons.com/source/net/unix/af_unix.c这个片段:

1206 static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr, 
1207        int addr_len, int flags) 
. 
. 
. 
1230   timeo = sock_sndtimeo(sk, flags & O_NONBLOCK); 
. 
. 
. 
1271   if (unix_recvq_full(other)) { 
1272     err = -EAGAIN; 
1273     if (!timeo) 
1274       goto out_unlock; 
1275 
1276     timeo = unix_wait_for_peer(other, timeo); 
. 
. 
. 

它的出现设置发送超时可能能够超时的连接的。这也与在http://man7.org/linux/man-pages/man7/socket.7.html的SO_SNDTIMEO文档相匹配。

感谢, 尼克

0

你的错误处理上select()可以使用一些清理工作。除非设置了except_set,否则您并不需要查询SO_ERROR。如果select()返回> 0,则设置write_set和/或except_set,并且如果没有设置except_set,则连接成功。

尝试一些更喜欢这个:

int domain_socket_send(const char* socket_name, unsigned char* buffer, 
    unsigned int length, unsigned int timeout) 
{ 
    struct sockaddr_un addr; 
    int fd; 
    int result; 

    // Create socket. 

    fd = socket(AF_UNIX, SOCK_STREAM, 0); 
    if (fd == -1) 
     return errno; 

    if (timeout != 0) 
     { 

     // Enabled non-blocking. 

     int flags = fcntl(fd, F_GETFL); 
     fcntl(fd, F_SETFL, flags | O_NONBLOCK); 
     } 

    // Set socket name. 

    memset(&addr, 0, sizeof(addr)); 
    addr.sun_family = AF_UNIX; 
    strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 

    // Connect. 

    result = connect(fd, (struct sockaddr*) &addr, sizeof(addr)); 
    if (result == -1) 
     { 

     // If some error then we're done. 

     if ((errno != EINPROGRESS) && (errno != EAGAIN)) 
      goto done; 

     // Now select() to find out when connect() has finished. 

     fd_set write_set; 
     fd_set except_set; 

     FD_ZERO(&write_set); 
     FD_ZERO(&write_set); 
     FD_SET(fd, &write_set); 
     FD_SET(fd, &except_set); 

     struct timeval tv; 

     // Set timeout. 

     tv.tv_sec = timeout/1000000; 
     tv.tv_usec = timeout % 1000000; 

     result = select(fd + 1, NULL, &write_set, &except_set, &tv); 
     if (result == -1) 
      { 
      goto done; 
      } 
     else if (result == 0) 
      { 
      result = -1; 
      errno = ETIMEDOUT; 
      goto done; 
      } 
     else if (FD_ISSET(fd, &except_set)) 
      { 
      int socket_error; 
      socklen_t len = sizeof(socket_error); 

      // Get the result of the connect() call. 

      result = getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &len); 
      if (result != -1) 
       { 
       result = -1; 
       errno = socket_error; 
       } 

      goto done; 
      } 
     else 
      { 
      // connected 
      } 
     } 

    // If we put the socket in non-blocking mode then put it back 
    // to blocking mode. 

    if (timeout != 0) 
     { 
     int flags = fcntl(fd, F_GETFL); 
     fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); 
     } 

    // Write buffer. 

    result = write(fd, buffer, length); 

done: 
    if (result == -1) 
     result = errno; 
    else 
     result = 0; 

    if (fd != -1) 
     { 
     shutdown(fd, SHUT_RDWR); 
     close(fd); 
     } 

    return result; 
} 
+0

谢谢。不幸的是,我不认为这会改变任何事情,至少在解决我的主要问题方面。最初,我只是通过一个write_set,因为这是建议。在我的测试中,我看到fd在write_set中设置,并且在socket_error中没有报告错误,但write()在ENOTCONN时失败。 – nickdu