轉自:庖丁解牛
/**
* connect_timeout - 帶超時的connect(方法中已執行connect)
* @fd:文件描述符
* @addr:地址結構體指針
* @wait_seconds:等待超時秒數,如果為0表示不檢測超時
* 成功返回0.失敗返回-1,超時返回-1並且errno = ETIMEDOUT
* */
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret = 0;
//connect()函數是連接服務器,本來connect會阻塞,但是設置未阻塞之后,
//客戶端仍然會三次握手機制,如果三次握手失敗,那么客戶端一定無法向文件描述符中寫入數據
//如果連接成功,那么客戶端就可以向文件描述符寫入數據了,
//所以交給select監管的文件描述符如果可以寫,說明連接成功,不可以寫說明連接失敗
//設置當前文件描述符未阻塞--設置非阻塞之后,
//connect在網絡中非常耗時,所以需要設置成非阻塞,如果有讀事件,說明可能連接成功
//這樣有利於做超時限制
if (wait_seconds > 0)
{
if (activate_nonblock(fd) == -1)
return -1;
}
ret = connect(fd, (struct sockaddr *) addr, sizeof(struct sockaddr));
if (ret == -1 && errno == EINPROGRESS)
{
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(fd, &writefds);
struct timeval timeout;
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do
{
ret = select(fd + 1, NULL, &writefds, NULL, &timeout);
} while (ret == -1 && errno == EINTR);
//ret==-1 不需要處理,正好給ret賦值
//select()報錯,但是此時不能退出當前connect_timeout()函數
//因為還需要取消文件描述符的非阻塞
if (ret == 0)
{
errno = ETIMEDOUT;
ret = -1;
} else if (ret == 1)
{
//ret返回為1(表示套接字可寫),可能有兩種情況,一種是連接建立成功,一種是套接字產生錯誤,
//此時錯誤信息不會保存至errno變量中,因此,需要調用getsockopt來獲取。
int err = 0;
socklen_t len = sizeof(err);
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
if (ret == 0 && err != 0)
{
errno = err;
ret = -1;
}
//說明套接字沒有發生錯誤,成功
}
}
if (wait_seconds > 0)
{
if (deactivate_nonblock(fd) == -1)
return -1;
}
return ret;
}
ps:有人測試利用getsockopt方式判斷連接建立成功與否在linux環境下不可用,如下方式:
connect(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
int err = errno;
if (err == EISCONN)
{
printf("connect finished 111.\n");
ret = 0;
}