概述
调用connect连接一般的超时时间是75s, 但是在程序中我们一般不希望等这么长时间采取采取动作。 可以在调用connect之前设置套接字非阻塞,然后调用connect,此时connect会立刻返回, 如果连接成功则直接返回0(成功), 如果没有连接成功,也会立即返回并且会设置errno为EINPROCESS,这并不是一个致命错误,仅仅是告知你已经在连接了,你只要判断是它就继续执行后面的逻辑就行了,比如select.通过select设置超时来达到为connect设定超时的目的. 下面的代码显示这个过程。
bool timeout_connect(const string& _host, uint16_t _port, uint32t _timeout, int32_t& _sockfd)
{#define CLOSE_SOCK_AND_RETURN_FALSE(so) close(so); return false;
if (_sockfd != -1) return true;
int sockfd;
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(_port);
serv_addr.sin_addr.s_addr = inet_addr(_host.c_str());
if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
return false;
}
// get origin sockfd flag
int flags = fcntl(sockfd, F_GETFL);
if (flags == -1)
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
// set sockfd to non-block mode
int retcode = fcntl(sockfd, F_SETFL, flags|O_NONBLOCK);
if (retcode == -1)
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
if (::connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
{
if (errno == EINPROGRESS) // EINPROGRESS means connection is in progress, normally the socket connecting timeout is 75s. after the socket fd is ready to read.
// means the connecting of the socket fd is established.
int err;
int len = sizeof(int);
fd_set wds;
struct timeval tm;
tm.tv_sec = _timeout;
tm.tv_usec = 0;
FD_ZERO(&wds);
FD_SET(sockfd, &wds);
if (select(sockfd + 1, NULL, &wds, NULL, &tm) > 0) // ">0" means sockfd ready to read, "=0" means timeout cause retrun, "<0" means error.
{
retcode = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, (socklen_t *)&len);
if (retcode == -1 || err != 0)
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
}
else
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
}
else
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
}
retcode = fcntl(sockfd, F_SETFL, flags); // trun back the mode of sockfd to block.
if (retcode == -1)
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
struct timeval readtimeout;
readtimeout.tv_sec = _timeout;
readtimeout.tv_usec = 0;
retcode = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (void *)&readtimeout, sizeof(readtimeout)); // set socket read timeout.
if (retcode == -1)
{
CLOSE_SOCK_AND_RETURN_FALSE(sockfd);
}
#undef CLOSE_SOCK_AND_RETURN_FALSE
_sockfd = sockfd;
return true;
}
通过SO_RCVTIMEO 设置连接超时
SO_RCVTIMEO和SO_SNDTIMEO套接口选项可以给套接口的读和写,来设置超时时间,
一、在unix网络编程中,说是他们只能用于读和写,而像 accept和connect都不能用他们来设置.
可是我在阅读内核源码的过程中看到,在linux中,accept和connect可以分别用 SO_RCVTIMEO和SO_SNDTIMEO套接口来设置超时,这里他们的超时时间也就是sock的sk_rcvtimeo和sk_sndtimeo 域.accept和connect的相关代码我前面都介绍过了,这里再提一下.其中accept的相关部分在inet_csk_accept中,会调用 sock_rcvtimeo来取得超时时间(如果是非阻塞则忽略超时间).而connect的相关代码在inet_stream_connect中通过调用sock_sndtimeo来取得超时时间(如果非阻塞则忽略超时时间).
SO_RCVTIMEO和SO_SNDTIMEO ,它们分别用来设置socket接收数据超时时间和发送数据超时时间。
因此,这两个选项仅对与数据收发相关的系统调用有效,这些系统调用包括:send, sendmsg, recv, recvmsg, accept, connect 。
这两个选项设置后,若超时, 返回-1,并设置errno为EAGAIN或EWOULDBLOCK.
其中connect超时的话,也是返回-1, 但errno设置为EINPROGRESS
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//超时连接
int timeout_connect(const char *ip, int port, int time);
int main(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr, "Usage: %s ip portn", argv[0]);
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int sockfd = timeout_connect(ip, port, 10);
if (sockfd < 0)
return 1;
return 0;
}
int timeout_connect(const char *ip, int port, int time)
{
int ret = 0;
int error;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
return -1;
//超时时间
struct timeval timeout;
timeout.tv_sec = time;
timeout.tv_usec = 0;
socklen_t len = sizeof(timeout);
ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, len);
if (ret == -1) {
error = errno;
while ((close(sockfd) == -1) && (errno == EINTR));
errno = error;
return -1;
}
ret = connect(sockfd, (struct sockaddr*)&address, sizeof(address));
if (ret == -1) {
if (errno == EINPROGRESS) {
printf("connecting timeoutn");
return -1;
}
printf("error occur when connecting to servern");
return -1;
}
char buffer[1024];
memset(buffer, '