在使用tcp的connect調用時,默認是使用阻塞方式,當服務器當前不可用時,connect會等待(內部在重試?)直到超時時間到達,而這個超時時間是系統內核規定的,不能使用setSocketOpt來設置。
在碰到服務器不可用,上層邏輯進行重試時,如果超時時間過長,會產生卡死的感覺,用戶體驗也不佳,所以需要控制connect的超時時間。
參考網絡上的資料,這里使用select。實現方式是:將socket設置為非阻塞方式,使用select來輪詢socket,在select里指定超時時間,根據socket來判斷連接狀態。最后恢復socket的阻塞方式。
代碼如下(linux):
int connect_with_timeout(int socket, const struct sockaddr *address, socklen_t address_len, int time_out) { int flag, old_flag; old_flag = flag = fcntl(socket, F_GETFL, 0); flag |= O_NONBLOCK; fcntl(socket, F_SETFL, flag); int ret = -1; ret = ::connect(socket, (struct sockaddr*)address, address_len); if (ret != 0) { if (errno != EINPROGRESS) { LOG("connect failed,err(%d)", errno); } else { struct timeval tm; tm.tv_sec = time_out; tm.tv_usec = 0; fd_set set,rset; FD_ZERO(&set); FD_ZERO(&rset); FD_SET(socket, &set); FD_SET(socket, &rset); int res; res = ::select(socket+1, &rset, &set, NULL, &tm); if (res < 0) { LOG("select:network error in connect.errno:%d", errno); } else if(res == 0) { LOG("select:connect timeout.errno:%d", errno); } else if (res == 1) { if (FD_ISSET(socket, &set)) { LOG("select success"); ret = 0; } } else { LOG("other error when select: %s", strerror(errno)); } } } fcntl(socket, F_SETFL, old_flag); return ret; }
::connect在非阻塞模式下會立即返回,如果沒有其他錯誤,返回值等於0。
當::connect不能立即建立連接時,會返回EINPROGRESS,表示正在連接的過程中,這時可以使用select去輪詢套接口,而select超時時間由參數指定 。
select返回值小於0,表明connect出錯;等於0,表明connect超時;等於1,並且套接口的狀態是可寫,則表明connect已經成功建立。
最后恢復socket的阻塞屬性。
參考:
http://olive101.blog.163.com/blog/static/2051263201011221915696/