连接到套接字时似乎无法使超时工作

我正在尝试为connect()提供超时。 我四处搜寻,发现了几篇与此相关的文章。 我编写了我认为应该工作的内容,但不幸的是我没有从getsockopt()报告错误。 但是当我来到write()时,它失败了,错误是107 – ENOTCONN。

几点。 我在Fedora 23上运行.connect()的文档说它应该返回失败的EINPROGRESS错误,因为连接尚未完成但是我遇到了EAGAIN所以我将其添加到我的支票中。 目前我的套接字服务器在listen()调用中将backlog设置为零。 许多调用成功但是那些失败的调用都失败了107 – 我在write()调用中提到的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; } 

2016年4月4日更新:

我突然想到,也许我需要多次调用connect()直到成功,毕竟这是非阻塞而不是async io。 就像我在read()遇到EAGAIN后要读取数据时再次调用read()一样。 另外,我发现了以下SO问题:

使用select()进行非阻塞套接字连接始终返回1

其中EJP的回答说你需要发出多个connect()。 另外,从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 =史蒂文斯%20于是%20wright%20tcp%2Fip%20illustrated%20non阻挡%20connect&F =假

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

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

谢谢,尼克

您对select()error handling可能会使用一些清理。 除非设置了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; } 

来自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文档相匹配。

谢谢,尼克