一、使用alarm 函數設定逾時
void handler(int sig){}signal(SIGALRM, handler); alarm(5);int ret = read(fd, buf, sizeof(buf));if (ret == -1 && errno == EINTR) errno = ETIMEOUT;else if (ret >= 0) alarm(0);.................
程式大概架構如上所示,如果read在5s內被SIGALRM訊號中斷而返回,則表示逾時,否則未超 時已讀取到資料,取消鬧鐘。但這種方法不常用,因為有時可能在其他地方使用了alarm會造成混亂。
二、使用套接 字選項SO_SNDTIMEO、SO_RCVTIMEO
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, 5); int ret = read(sock, buf, sizeof(buf));if (ret == -1 && errno == EWOULDBLOCK) errno = ETIMEOUT;..........
即使用setsockopt 函數進行設定,但這種方法可移植性比較差,不是每種系統實現都有這些選項。
三、使用select 實現逾時
下面程式包含read_timeout、write_timeout、accept_timeout、 connect_timeout 四個函數封裝
/************************************************************************* > File Name: sysutil.c > Author: Simba > Mail: dameng34@163.com > Created Time: Sat 02 Mar 2013 10:53:06 PM CST ************************************************************************/ #include "sysutil.h" /* read_timeout - 讀逾時檢測函數,不含讀操作 * fd:檔案描述符 * wait_seconds:等待逾時秒數, 如果為0表示不檢測逾時; * 成功(未逾時)返回0,失敗返回-1,逾時返回-1並且errno = ETIMEDOUT */ int read_timeout(int fd, unsigned int wait_seconds){ int ret = 0; if (wait_seconds > 0) { fd_set read_fdset; struct timeval timeout; FD_ZERO(&read_fdset); FD_SET(fd, &read_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout); //select會阻塞直到檢測到事件或者逾時 // 如果select檢測到可讀事件發送,則此時調用read不會阻塞 } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) return 0; } return ret;} /* write_timeout - 寫逾時檢測函數,不含寫操作 * fd:檔案描述符 * wait_seconds:等待逾時秒數, 如果為0表示不檢測逾時; * 成功(未逾時)返回0,失敗返回-1,逾時返回-1並且errno = ETIMEDOUT */ int write_timeout(int fd, unsigned int wait_seconds){ int ret = 0; if (wait_seconds > 0) { fd_set write_fdset; struct timeval timeout; FD_ZERO(&write_fdset); FD_SET(fd, &write_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) return 0; } return ret;} /* accept_timeout - 帶逾時的accept * fd: 通訊端 * addr: 輸出參數,返回對方地址 * wait_seconds: 等待逾時秒數,如果為0表示正常模式 * 成功(未逾時)返回已串連通訊端,失敗返回-1,逾時返回-1並且errno = ETIMEDOUT */ int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds){ int ret; socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > 0) { fd_set accept_fdset; struct timeval timeout; FD_ZERO(&accept_fdset); FD_SET(fd, &accept_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == -1) return -1; else if (ret == 0) { errno = ETIMEDOUT; return -1; } } if (addr != NULL) ret = accept(fd, (struct sockaddr *)addr, &addrlen); else ret = accept(fd, NULL, NULL); if (ret == -1) ERR_EXIT("accpet error"); return ret;} /* activate_nonblock - 設定IO為非阻塞模式 * fd: 檔案描述符 */void activate_nonblock(int fd){ int ret; int flags = fcntl(fd, F_GETFL); if (flags == -1) ERR_EXIT("fcntl error"); flags |= O_NONBLOCK; ret = fcntl(fd, F_SETFL, flags); if (ret == -1) ERR_EXIT("fcntl error");} /* deactivate_nonblock - 設定IO為阻塞模式 * fd: 檔案描述符 */void deactivate_nonblock(int fd){ int ret; int flags = fcntl(fd, F_GETFL); if (flags == -1) ERR_EXIT("fcntl error"); flags &= ~O_NONBLOCK; ret = fcntl(fd, F_SETFL, flags); if (ret == -1) ERR_EXIT("fcntl error");} /* connect_timeout - 帶逾時的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; socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > 0) activate_nonblock(fd); ret = connect(fd, (struct sockaddr *)addr, addrlen); if (ret < 0 && errno == EINPROGRESS) { fd_set connect_fdset; struct timeval timeout; FD_ZERO(&connect_fdset); FD_SET(fd, &connect_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { /* 一旦串連建立,通訊端就可寫 */ ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { errno = ETIMEDOUT; return -1; } else if (ret < 0) return -1; else if (ret == 1) { /* ret返回為1,可能有兩種情況,一種是串連建立成功,一種是通訊端產生錯誤 * 此時錯誤資訊不會儲存至errno變數中(select沒出錯),因此,需要調用 * getsockopt來擷取 */ int err; socklen_t socklen = sizeof(err); int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen); if (sockoptret == -1) return -1; if (err == 0) ret = 0; else { errno = err; ret = -1; } } } if (wait_seconds > 0) deactivate_nonblock(fd); return ret;}