socket程式設計I/O超時函式select封裝
阿新 • • 發佈:2018-11-01
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h>//使用signal函式 #include <sys/wait.h>//使用wait函式 #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE);\ }while(0) /* 函式只進行讀超時檢測,不進行讀操作 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); }while(ret<0 &&errno == EINTR);//訊號中斷引起的失敗,需要重啟檢測 if(ret==0)//沒有檢測到事件發生,即時間超時 { ret=-1; errno = ETIMEDOUT; } else if(ret == 1) ret =0; } return ret; } /* 函式只進行讀超時檢測,不含寫操作 fd為檔案描述符 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) ret =0; } return ret; } /* accept_timeout - 帶超時的accept fd為套接字 addr:輸出引數,返回對方地址 wait_seconds:等待超時秒數,如果為0表示正常模式 成功(未超時)返回已連線套接字,超時返回-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; } } //進行下方程式碼時,ret初始為1 if(addr != NULL)//地址不為空 { ret = accept(fd, (struct sockaddr*)addr, &addrlen); } else { ret=accept(fd, NULL, NULL);//地址為空 } if(ret=-1) ERR_EXIT("accept"); return ret; } /* activate_nonblock —— 設定I/O為非阻塞模式 */ void activate_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"); } /* deactivate_nonblock —— 設定I/O為阻塞模式 */ 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"); } /* connect_timeout —— connect addr為要連線的對方地址 wait_seconds:等待超時秒數,如果為0表示正常模式 成功(未超時)返回0,超時返回-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{ //一旦連線建立,套接字可寫,所以fd放進可寫集合 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,有兩種情況,一種是連線建立成功,一種是套接字產生錯誤*/ /*此時錯誤資訊不會儲存至errno變數中,因此,需要呼叫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; }