實現超時的三種方式:服務器
一、SIGALARM信號網絡
void handler(int sig)函數
{測試
return 0;spa
}code
signal(SIGALRM,handler);blog
alarm(5);接口
int ret=read(fd, buf, sizeof(buf)); //可能會被打斷隊列
if(ret==-1 && errno == EINTR)事件
{
errno=ETIMEOUT;
}
else if (ret>=0)
{
alarm(0);
}
二、
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, 5);
int ret=read(sock, buf, sizeof(buf)); //超時返回-1
if(ret==-1&&errno==EWOULDBLOCK)
{
...
}
三、select(用select實現超時)
一、read_timeout 函數封裝
二、write_timeout函數封裝
三、accept_timeot函數封裝
四、connect_timeout函數封裝
/* select實現超時檢測函數 */ #include<sysutil.h> //測試代碼 int ret; ret=read_timeout(fd,5); if(ret==0) { read(fd,...); //不會阻塞 } else if(ret==-1&&errno==ETIMEDOUT) { timeout... } else { ERR_EXIT("red_timeout"); } /** *read_timeout讀超時檢測函數,不含讀操做 *wait_seconds 等待超時秒數。若是爲0,不檢測超時 *成功(返回0,未超時),失敗返回-1(超時)而且errno=ETIMDEOUT *僅僅檢測IO是否產生了超時。 */ 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); }while(ret<0&&errno==EINTR); if(ret==0)//無事件可讀,超時 { ret=-1; errno=ETIMEDOUT; } else if(ret==1)//檢測到一個可讀事件 ret=0; } return ret;//wait_seconds==0 直接返回 } /* 寫超時檢測函數,不含寫操做 成功未超時返回0,失敗超時返回-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)//檢測到一個事件 ret=0; } return ret;//wait_seconds==0 直接返回 } /* accept_timeout 帶超時的accept函數(包含accept操做)...select在若是是監聽套接口(服務器),已完成鏈接隊列不爲空時返回 fd:套接字 addr:輸出參數,返回對方地址 wait_seconds:等待返回的秒數。若是爲0表示正常模式 成功未超時返回已鏈接套接字,超時返回-1而且errno=ETIMEDOUT int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); */ int accept_timeout(int fd,struct sockaddr_in * addr,unsigned int wait_seconds) { int ret; socklen_t addrlen=sizeof(sockaddr_in); if(wait_seconds>0) { fd_set accept_fdset; struct timeval timeout; FD_ZERO(&accept_fdset); FD_SET(fd,&accept_fdset); timeout.tv_sev=wait_seconds; timeout.tv_usec=0; do { ret=select(fd+1,&accept_set,NULL,NULL,&timeout); }while(ret==-1&&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("accept error"); return ret; } /**鏈接創建三次時,客戶端收到ack返回。一次握手往返的時間稱爲RTT,三次握手 時如有擁塞,系統默認connect返回時間75s超時
鏈接超時函數 *connect:創建三次握手時connect在服務器返回確認時就返回了。設定本身的鏈接超時時間。 *fd:套接字 *addr:要鏈接的對端服務器地址 *wait_seconds:等待超時秒數,若是爲0表示正常模式 *成功(未超時)返回0,失敗超時返回-1,而且errno=ETIMEDOUT **/ //將套接口設置爲非阻塞模式,防止直接調用connect阻塞 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"); } //將套接口還原爲阻塞模式 void deactivate_nonblock(int fd) { int ret; int flags=fcntl(fd,F_GETFL); if(flags==-1) ERR_EXIT("fcntl"); flags&=~O_NONBLOCK; ret=fcntl(fd,F_SETFL,flags); if(ret==-1) ERR_EXIT("fcntl"); } int connect_timeout(int fd,struct sockadd_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);//已經將套接口設置爲非阻塞了,若是不可以當即鏈接成功,則直接返回EINPROGRESS錯誤。 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 { //一旦connect創建鏈接,套接口就能夠寫了。 ret=select(fd+1,NULL,&connect_fdset,NULL,&timeout); }while(ret<0&&errno==EINTR) if(ret==0) { ret=-1; errno=ETIMEDOUT; } else if(ret<0) { return -1; } else if(ret==1) { /*ret返回1,可能有兩種狀況。一種是fd有事件發生,connect創建鏈接可寫了。 *另外一種狀況是套接字自己產生錯誤.套接口上發生一個錯誤待處理,錯誤能夠經過getsockopt指定SO_ERROR選項來獲取 *可是select函數沒有出錯,因此錯誤信息不能保存到errno *變量中。只有經過getsockopt來獲取套接口fd的錯誤。 */ int err; socklen_t socklen=sizeof(err); int sockoptret=getsockopt(fd,SOL_SOCKET,SO_ERROR,&err,&socklen);//成功返回0,錯誤返回-1 if(sockoptret==-1) { return -1; } if(err==0)//套接字沒有錯誤 ret=0;//返回0成功未超時 else//套接字產生錯誤 { errno=err;//套接字錯誤代碼 ret=-1; } } }
//網絡情況良好,直接成功了,返回ret==0 if(wait_seconds>0) { deactivate_nonblock(fd);//重置爲阻塞 } return ret; }